import { IUser, IChallengeSession } from '../interfaces/entities.interface'; import { Repository } from 'typeorm'; import { BaseChallengeSession } from '../entities'; import { AuthChallenge } from '../dto/auth-challenge.dto'; import { NAuthLogger } from '../utils/nauth-logger'; import { InternalAuthAuditService as AuthAuditService } from './auth-audit.service'; import { ClientInfoService } from './client-info.service'; import { NAuthConfig } from '../interfaces/config.interface'; /** * Challenge Session Service * * Manages authentication challenge sessions for the challenge-response flow. * Challenge sessions are temporary, short-lived sessions (typically 15 minutes) * that track pending authentication challenges similar to AWS Cognito. * * Handles: * - Challenge session creation and validation * - Session expiration and cleanup * - Attempt tracking and rate limiting * - Secure session token generation * * @example * ```typescript * // Create a challenge session * const session = await challengeService.createChallengeSession( * user, * AuthChallenge.VERIFY_EMAIL, * { email: user.email } * ); * * // Validate and consume a challenge session * const validSession = await challengeService.validateAndConsumeSession( * sessionToken, * AuthChallenge.VERIFY_EMAIL * ); * ``` */ export declare class ChallengeService { private readonly challengeSessionRepository; private readonly clientInfoService; private readonly logger; private readonly auditService?; private readonly config?; /** * Default challenge session expiration time (15 minutes) */ private readonly DEFAULT_EXPIRATION_MINUTES; /** * Default maximum attempts per challenge session */ private readonly DEFAULT_MAX_ATTEMPTS; constructor(challengeSessionRepository: Repository, clientInfoService: ClientInfoService, logger: NAuthLogger, auditService?: AuthAuditService | undefined, // Optional - audit trail service (enabled via config.auditLogs.enabled) config?: NAuthConfig | undefined); /** * Per-user cleanup throttle map to avoid frequent cleanup writes */ private readonly lastCleanupByUserId; /** * Create a new challenge session * * Generates a unique session token and stores challenge metadata. * The session token is returned to the client and must be submitted * when responding to the challenge. * * **Deduplication:** * If an active (non-completed, non-expired) session already exists for the same user * and challenge type, this method returns the existing session instead of creating * a duplicate. This prevents: * - Excessive `CHALLENGE_CREATED` audit events * - Database bloat from duplicate sessions * - User confusion from multiple active sessions for the same challenge * * @param user - User the challenge session belongs to * @param challengeName - Type of challenge (VERIFY_EMAIL, VERIFY_PHONE, etc.) * @param metadata - Challenge-specific data * @returns Challenge session with session token (new or existing) * @remarks Client info (ipAddress, userAgent) is automatically extracted from ClientInfoService context * * @example * ```typescript * const session = await challengeService.createChallengeSession( * user, * AuthChallenge.VERIFY_EMAIL, * { email: user.email, verificationTokenId: tokenId } * ); * // Returns: { sessionToken: 'uuid-here', expiresAt: Date, ... } * // If called again before completion, returns same session (no duplicate audit event) * ``` */ createChallengeSession(user: IUser, challengeName: AuthChallenge, metadata?: Record): Promise; /** * Validate a challenge session token for code requests * * Validates session for requesting new verification codes (SMS, email, etc.). * Skips max attempts check since requesting a new code is not a verification attempt. * This method is used internally by nauth when sending verification codes. * * @param sessionToken - Session token to validate * @param expectedChallenge - Expected challenge type (optional, for additional verification) * @returns Valid challenge session * @throws {UnauthorizedException} If session is invalid, expired, or already completed * * @example * ```typescript * // Used internally by nauth when sending verification codes * const session = await challengeService.validateSessionForCodeRequest( * 'session-token-123', * AuthChallenge.MFA_REQUIRED * ); * ``` */ validateSessionForCodeRequest(sessionToken: string, expectedChallenge?: AuthChallenge): Promise; /** * Validate a challenge session token * * Checks if the session token is valid, not expired, not completed, * and matches the expected challenge type. Does NOT consume the session. * Enforces max attempts check for verification attempts. * * @param sessionToken - Session token to validate * @param expectedChallenge - Expected challenge type (optional, for additional verification) * @returns Valid challenge session * @throws {UnauthorizedException} If session is invalid, expired, or already completed * * @example * ```typescript * try { * const session = await challengeService.validateSession( * 'session-token-123', * AuthChallenge.VERIFY_EMAIL * ); * // Session is valid, proceed with verification * } catch (error) { * // Session is invalid * } * ``` */ validateSession(sessionToken: string, expectedChallenge?: AuthChallenge): Promise; /** * Internal method to validate challenge session * * @param sessionToken - Session token to validate * @param expectedChallenge - Expected challenge type (optional) * @param skipMaxAttemptsCheck - If true, skip max attempts check (for code requests) * @returns Valid challenge session * @private */ private validateSessionInternal; /** * Increment attempt counter for a challenge session * * Tracks failed attempts to complete a challenge. * Used to prevent brute-force attacks on verification codes. * * @param session - Challenge session to increment * @returns Updated session * * @example * ```typescript * await challengeService.incrementAttempts(session); * ``` */ incrementAttempts(session: IChallengeSession): Promise; /** * Validate and consume a challenge session * * Validates the session and marks it as completed if validation succeeds. * This method should be called only after successful challenge completion. * * @param sessionToken - Session token to validate and consume * @param expectedChallenge - Expected challenge type * @returns Valid, completed challenge session with user * @throws {UnauthorizedException} If session is invalid * * @example * ```typescript * const session = await challengeService.validateAndConsumeSession( * 'session-token-123', * AuthChallenge.VERIFY_EMAIL * ); * // Session is now marked complete and cannot be reused * ``` */ validateAndConsumeSession(sessionToken: string, expectedChallenge: AuthChallenge): Promise; /** * Update challenge session metadata * * Updates the metadata field of an existing challenge session. * Used to store additional challenge-specific data (e.g., passkey challenge). * * @param sessionToken - Session token to update * @param metadata - Metadata to merge into existing metadata * @returns Updated challenge session * @throws {NAuthException} If session not found or invalid * * @example * ```typescript * await challengeService.updateMetadata('session-token-123', { * passkeyChallenge: 'base64-challenge-string' * }); * ``` */ updateMetadata(sessionToken: string, metadata: Record): Promise; /** * Clean up expired or completed challenge sessions for a user * * Removes old sessions to prevent database bloat. * Called automatically when creating new challenge sessions. * * @param userId - User ID to clean up sessions for * * @example * ```typescript * await challengeService.cleanupExpiredSessions(user.id); * ``` */ cleanupExpiredSessions(userId: number): Promise; /** * Clean up all expired challenge sessions (for all users) * * Should be called periodically (e.g., via cron job) to maintain * database health. * * @returns Number of sessions deleted * * @example * ```typescript * // In a scheduled job * const deleted = await challengeService.cleanupAllExpiredSessions(); * logger.log(`Cleaned up ${deleted} expired challenge sessions`); * ``` */ cleanupAllExpiredSessions(): Promise; /** * Delete challenge sessions by challenge name for a user * * Removes all active (not completed, not expired) challenge sessions * of the specified type for a user. Used to clean up phantom challenges * when user completes the requirement (e.g., sets up MFA). * * @param userId - Internal user ID * @param challengeName - Challenge type to delete * @returns Number of sessions deleted * * @example * ```typescript * // Clear MFA_SETUP_REQUIRED challenge when user sets up MFA * const deleted = await challengeService.deleteUserChallengeSessions( * user.id, * AuthChallenge.MFA_SETUP_REQUIRED * ); * ``` */ deleteUserChallengeSessions(userId: number, challengeName: AuthChallenge): Promise; /** * Mask email address for display in challenge parameters * * Shows first character and domain, hides the rest. * If maskSensitiveData config is false, returns the email unchanged. * * @param email - Email to mask * @returns Masked email or full email based on config * * @example * ```typescript * // With maskSensitiveData: true (default) * maskEmail('john.doe@example.com') * // Returns: 'j***@example.com' * * // With maskSensitiveData: false * maskEmail('john.doe@example.com') * // Returns: 'john.doe@example.com' * ``` */ maskEmail(email: string): string; /** * Mask phone number for display in challenge parameters * * Shows last 4 digits, hides the rest. * If maskSensitiveData config is false, returns the phone unchanged. * * @param phone - Phone to mask * @returns Masked phone or full phone based on config * * @example * ```typescript * // With maskSensitiveData: true (default) * maskPhone('+1234567890') * // Returns: '***-***-7890' * * // With maskSensitiveData: false * maskPhone('+1234567890') * // Returns: '+1234567890' * ``` */ maskPhone(phone: string): string; } //# sourceMappingURL=challenge.service.d.ts.map