/** * Gateway: AuthGateway * * Component-based gateway that manages authentication state and access control. * Follows clean architecture by orchestrating between auth context and blockers. * * Gateways are the entry point for component-level access control. * They coordinate between services, blockers, and the UI. */ import type { SessionViewModel } from '@/lib/view-models/SessionViewModel'; import type { AuthContextValue } from '@/lib/auth/AuthContext'; import { AuthorizationBlocker } from '@/lib/blockers/AuthorizationBlocker'; export interface AuthGatewayConfig { /** Required roles for access (empty array = any authenticated user) */ requiredRoles?: string[]; /** Whether to redirect if unauthorized */ redirectOnUnauthorized?: boolean; /** Redirect path if unauthorized */ unauthorizedRedirectPath?: string; } export class AuthGateway { private blocker: AuthorizationBlocker; private config: Required; constructor( private authContext: AuthContextValue, config: AuthGatewayConfig = {} ) { this.config = { requiredRoles: config.requiredRoles || [], redirectOnUnauthorized: config.redirectOnUnauthorized ?? true, unauthorizedRedirectPath: config.unauthorizedRedirectPath || '/auth/login', }; this.blocker = new AuthorizationBlocker(this.config.requiredRoles); } /** * Check if current user has access */ canAccess(): boolean { // Update blocker with current session this.blocker.updateSession(this.authContext.session); return this.blocker.canExecute(); } /** * Get the current access state */ getAccessState(): { canAccess: boolean; reason: string; isLoading: boolean; isAuthenticated: boolean; } { const reason = this.blocker.getReason(); return { canAccess: this.canAccess(), reason: this.blocker.getBlockMessage(), // Only show loading if auth context is still loading // If auth context is done but session is null, that's unauthenticated (not loading) isLoading: this.authContext.loading, isAuthenticated: this.authContext.session?.isAuthenticated ?? false, }; } /** * Enforce access control - throws if access denied * Used for programmatic access control */ enforceAccess(): void { if (!this.canAccess()) { const reason = this.blocker.getBlockMessage(); throw new Error(`Access denied: ${reason}`); } } /** * Redirect to unauthorized page if needed * Returns true if redirect was performed */ redirectIfUnauthorized(): boolean { if (this.canAccess()) { return false; } if (this.config.redirectOnUnauthorized) { // Note: We can't use router here since this is a pure class // The component using this gateway should handle the redirect return true; } return false; } /** * Get redirect path for unauthorized access */ getUnauthorizedRedirectPath(): string { return this.config.unauthorizedRedirectPath; } /** * Refresh the gateway state (e.g., after login/logout) */ refresh(): void { this.blocker.updateSession(this.authContext.session); } /** * Check if user is loading */ isLoading(): boolean { return this.authContext.loading; } /** * Check if user is authenticated */ isAuthenticated(): boolean { return this.authContext.session?.isAuthenticated ?? false; } /** * Get current session */ getSession(): SessionViewModel | null { return this.authContext.session; } /** * Get block reason for debugging */ getBlockReason(): string { return this.blocker.getReason(); } /** * Get user-friendly block message */ getBlockMessage(): string { return this.blocker.getBlockMessage(); } }