/** * Login Flow Controller * * Deterministic state machine for authentication flow. * Compliant with docs/architecture/website/LOGIN_FLOW_STATE_MACHINE.md */ import type { SessionViewModel } from '@/lib/view-models/SessionViewModel'; export enum LoginState { UNAUTHENTICATED = "UNAUTHENTICATED", AUTHENTICATED_WITH_PERMISSIONS = "AUTHENTICATED_WITH_PERMISSIONS", AUTHENTICATED_WITHOUT_PERMISSIONS = "AUTHENTICATED_WITHOUT_PERMISSIONS", POST_AUTH_REDIRECT = "POST_AUTH_REDIRECT" } export type LoginAction = | { type: 'SHOW_LOGIN_FORM' } | { type: 'REDIRECT'; path: string } | { type: 'SHOW_PERMISSION_ERROR' }; /** * LoginFlowController - Immutable, deterministic state machine * * Rules: * - Constructed from explicit inputs only * - No side effects * - Pure functions for state transitions * - Side effects (routing) executed outside */ export class LoginFlowController { // Immutable state private readonly session: SessionViewModel | null; private readonly returnTo: string; // State machine private state: LoginState; constructor(session: SessionViewModel | null, returnTo: string) { this.session = session; this.returnTo = returnTo; this.state = this.determineInitialState(); } private determineInitialState(): LoginState { if (!this.session) return LoginState.UNAUTHENTICATED; if (this.returnTo === '/dashboard') return LoginState.AUTHENTICATED_WITH_PERMISSIONS; return LoginState.AUTHENTICATED_WITHOUT_PERMISSIONS; } // Pure function - no side effects getState(): LoginState { return this.state; } // Pure function - returns action, does not execute getNextAction(): LoginAction { switch (this.state) { case LoginState.UNAUTHENTICATED: return { type: 'SHOW_LOGIN_FORM' }; case LoginState.AUTHENTICATED_WITH_PERMISSIONS: return { type: 'REDIRECT', path: '/dashboard' }; case LoginState.AUTHENTICATED_WITHOUT_PERMISSIONS: return { type: 'SHOW_PERMISSION_ERROR' }; case LoginState.POST_AUTH_REDIRECT: return { type: 'REDIRECT', path: this.returnTo }; } } // Transition called after authentication transitionToPostAuth(): void { if (this.session) { this.state = LoginState.POST_AUTH_REDIRECT; } } }