Files
gridpilot.gg/apps/website/lib/blockers/AuthorizationBlocker.ts
2026-01-01 22:46:59 +01:00

110 lines
2.8 KiB
TypeScript

/**
* Blocker: AuthorizationBlocker
*
* Frontend blocker that prevents unauthorized access to admin features.
* This is a UX improvement, NOT a security mechanism.
* Security is enforced by backend Guards.
*/
import { Blocker } from './Blocker';
import type { SessionViewModel } from '@/lib/view-models/SessionViewModel';
export type AuthorizationBlockReason =
| 'loading' // User data not loaded yet
| 'unauthenticated' // User not logged in
| 'unauthorized' // User logged in but lacks required role
| 'insufficient_role' // User has role but not high enough
| 'enabled'; // Access granted
export class AuthorizationBlocker extends Blocker {
private currentSession: SessionViewModel | null = null;
private requiredRoles: string[] = [];
constructor(requiredRoles: string[]) {
super();
this.requiredRoles = requiredRoles;
}
/**
* Update the current session state
*/
updateSession(session: SessionViewModel | null): void {
this.currentSession = session;
}
/**
* Get the current block reason
*/
getReason(): AuthorizationBlockReason {
if (!this.currentSession) {
// Session is null - this means unauthenticated (not loading)
// Loading state is handled by AuthContext
return 'unauthenticated';
}
if (!this.currentSession.isAuthenticated) {
return 'unauthenticated';
}
// If no roles are required, allow access
if (this.requiredRoles.length === 0) {
return 'enabled';
}
// Check if user has a role
if (!this.currentSession.role) {
return 'unauthorized';
}
// Check if user's role matches any of the required roles
if (this.requiredRoles.includes(this.currentSession.role)) {
return 'enabled';
}
// User has a role but it's not in the required list
return 'insufficient_role';
}
/**
* Check if user can execute (access admin area)
*/
canExecute(): boolean {
const reason = this.getReason();
return reason === 'enabled';
}
/**
* Block access (for testing/demo purposes)
*/
block(): void {
// Simulate blocking by setting session to null
this.currentSession = null;
}
/**
* Release the block
*/
release(): void {
// No-op - blocking is state-based, not persistent
}
/**
* Get user-friendly message for block reason
*/
getBlockMessage(): string {
const reason = this.getReason();
switch (reason) {
case 'unauthenticated':
return 'You must be logged in to access this area.';
case 'unauthorized':
return 'You do not have permission to access this area.';
case 'insufficient_role':
return `Access requires one of: ${this.requiredRoles.join(', ')}`;
case 'enabled':
return 'Access granted';
default:
return 'Access denied';
}
}
}