149 lines
3.7 KiB
TypeScript
149 lines
3.7 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(),
|
|
// 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();
|
|
}
|
|
} |