rename to core
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
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<Result<AuthenticationState>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user