Files
gridpilot.gg/apps/website/lib/gateways/AuthGateway.ts
2026-01-01 12:10:35 +01:00

140 lines
3.5 KiB
TypeScript

/**
* 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<AuthGatewayConfig>;
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(),
isLoading: reason === '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.blocker.getReason() === '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();
}
}