import { AuthenticationState } from '../../domain/value-objects/AuthenticationState'; import type { ILogger } from '../../../shared/src/logging/ILogger'; import { Result } from '../../../shared/result/Result'; import type { AuthenticationServicePort } from '../ports/AuthenticationServicePort'; import { SessionLifetime } from '../../domain/value-objects/SessionLifetime'; import type { SessionValidatorPort } from '../ports/SessionValidatorPort'; /** * Use case for checking if the user has a valid iRacing session. * * This validates the session before automation starts, allowing * the system to prompt for re-authentication if needed. * * Implements hybrid validation strategy: * - File-based validation (fast, always executed) * - Optional server-side validation (slow, requires network) */ export class CheckAuthenticationUseCase { constructor( private readonly logger: ILogger, private readonly authService: AuthenticationServicePort, private readonly sessionValidator?: SessionValidatorPort ) {} /** * Execute the authentication check. * * @param options Optional configuration for validation * @returns Result containing the current AuthenticationState */ async execute(options?: { requireServerValidation?: boolean; verifyPageContent?: boolean; }): Promise> { this.logger.debug('Executing CheckAuthenticationUseCase', { options }); try { // Step 1: File-based validation (fast) this.logger.debug('Performing file-based authentication check.'); const fileResult = await this.authService.checkSession(); if (fileResult.isErr()) { this.logger.error('File-based authentication check failed.', { error: fileResult.unwrapErr() }); return fileResult; } this.logger.info('File-based authentication check succeeded.'); const fileState = fileResult.unwrap(); this.logger.debug(`File-based authentication state: ${fileState}`); // Step 2: Check session expiry if authenticated if (fileState === AuthenticationState.AUTHENTICATED) { this.logger.debug('Session is authenticated, checking expiry.'); const expiryResult = await this.authService.getSessionExpiry(); if (expiryResult.isErr()) { this.logger.warn('Could not retrieve session expiry, proceeding with file-based state.', { error: expiryResult.unwrapErr() }); // Don't fail completely if we can't get expiry, use file-based state return Result.ok(fileState); } const expiry = expiryResult.unwrap(); if (expiry !== null) { try { const sessionLifetime = new SessionLifetime(expiry); if (sessionLifetime.isExpired()) { this.logger.info('Session has expired based on lifetime.'); return Result.ok(AuthenticationState.EXPIRED); } this.logger.debug('Session is not expired.'); } catch (error) { this.logger.error('Invalid expiry date encountered, treating session as expired.', { expiry, error }); // Invalid expiry date, treat as expired for safety return Result.ok(AuthenticationState.EXPIRED); } } } // Step 3: Optional page content verification if (options?.verifyPageContent && fileState === AuthenticationState.AUTHENTICATED) { this.logger.debug('Performing optional page content verification.'); const pageResult = await this.authService.verifyPageAuthentication(); if (pageResult.isOk()) { const browserState = pageResult.unwrap(); // If cookies valid but page shows login UI, session is expired if (!browserState.isFullyAuthenticated()) { this.logger.info('Page content verification indicated session expired.'); return Result.ok(AuthenticationState.EXPIRED); } this.logger.info('Page content verification succeeded.'); } else { this.logger.warn('Page content verification failed, proceeding with file-based state.', { error: pageResult.unwrapErr() }); } // Don't block on page verification errors, continue with file-based state } // Step 4: Optional server-side validation if (this.sessionValidator && fileState === AuthenticationState.AUTHENTICATED) { this.logger.debug('Performing optional server-side validation.'); const serverResult = await this.sessionValidator.validateSession(); // Don't block on server validation errors if (serverResult.isOk()) { const isValid = serverResult.unwrap(); if (!isValid) { this.logger.info('Server-side validation indicated session expired.'); return Result.ok(AuthenticationState.EXPIRED); } this.logger.info('Server-side validation succeeded.'); } else { this.logger.warn('Server-side validation failed, proceeding with file-based state.', { error: serverResult.unwrapErr() }); } } this.logger.info(`CheckAuthenticationUseCase completed successfully with state: ${fileState}`); return Result.ok(fileState); } catch (error) { this.logger.error('An unexpected error occurred during authentication check.', { error }); throw error; } } }