98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import { AuthenticationState } from '../../automation-domain/value-objects/AuthenticationState';
|
|
import { Result } from '../../shared/result/Result';
|
|
import type { IAuthenticationService } from '../ports/IAuthenticationService';
|
|
import { SessionLifetime } from '../../automation-domain/value-objects/SessionLifetime';
|
|
|
|
/**
|
|
* Port for optional server-side session validation.
|
|
*/
|
|
export interface ISessionValidator {
|
|
validateSession(): Promise<Result<boolean>>;
|
|
}
|
|
|
|
/**
|
|
* 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 authService: IAuthenticationService,
|
|
private readonly sessionValidator?: ISessionValidator
|
|
) {}
|
|
|
|
/**
|
|
* 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>> {
|
|
// Step 1: File-based validation (fast)
|
|
const fileResult = await this.authService.checkSession();
|
|
if (fileResult.isErr()) {
|
|
return fileResult;
|
|
}
|
|
|
|
const fileState = fileResult.unwrap();
|
|
|
|
// Step 2: Check session expiry if authenticated
|
|
if (fileState === AuthenticationState.AUTHENTICATED) {
|
|
const expiryResult = await this.authService.getSessionExpiry();
|
|
if (expiryResult.isErr()) {
|
|
// 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()) {
|
|
return Result.ok(AuthenticationState.EXPIRED);
|
|
}
|
|
} catch {
|
|
// 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) {
|
|
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()) {
|
|
return Result.ok(AuthenticationState.EXPIRED);
|
|
}
|
|
}
|
|
// Don't block on page verification errors, continue with file-based state
|
|
}
|
|
|
|
// Step 4: Optional server-side validation
|
|
if (this.sessionValidator && fileState === AuthenticationState.AUTHENTICATED) {
|
|
const serverResult = await this.sessionValidator.validateSession();
|
|
|
|
// Don't block on server validation errors
|
|
if (serverResult.isOk()) {
|
|
const isValid = serverResult.unwrap();
|
|
if (!isValid) {
|
|
return Result.ok(AuthenticationState.EXPIRED);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result.ok(fileState);
|
|
}
|
|
} |