/** * 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'; } } }