import { Repository } from 'typeorm'; import { BaseMFADevice, BaseUser } from '../entities'; import { IUser, IMFADevice } from '../interfaces/entities.interface'; import { NAuthConfig } from '../interfaces/config.interface'; import { NAuthLogger } from '../utils/nauth-logger'; import { InternalAuthAuditService as AuthAuditService } from './auth-audit.service'; import { ClientInfoService } from './client-info.service'; import { IMFAProviderService } from '../interfaces/mfa-provider.interface'; import { ChallengeService } from './challenge.service'; import { HookRegistryService } from './hook-registry.service'; /** * Base MFA Provider Service * * Abstract base class that provides common functionality for all MFA providers. * Provider-specific services (TOTP, SMS, Passkey, etc.) should extend this class * and implement the IMFAProviderService interface methods. * * This base class handles: * - Device repository access * - User repository access * - Common device management operations * - Backup codes generation and verification * - MFA enforcement checks * - Helper methods * * **Key Design:** * - No hardcoded method names - works with any provider * - Provider config accessed dynamically via `methodName` * - Future developers can add new providers without modifying this class * * @example * ```typescript * @Injectable() * export class TOTPMFAProviderService extends BaseMFAProviderService implements IMFAProviderService { * readonly methodName = 'totp'; * * constructor( * // ... base dependencies injected via super() * private readonly totpService: TOTPService, * ) { * super(/* ... base dependencies *\/); * } * * async setup(user: IUser): Promise { * // TOTP-specific setup logic * } * * async verify(user: IUser, code: unknown): Promise { * // TOTP verification logic * } * } * ``` */ export declare abstract class BaseMFAProviderService implements IMFAProviderService { protected readonly mfaDeviceRepository: Repository; protected readonly userRepository: Repository; protected readonly config: NAuthConfig; protected readonly logger: NAuthLogger; protected readonly passwordService?: unknown | undefined; protected readonly challengeService?: ChallengeService | undefined; protected readonly auditService?: AuthAuditService | undefined; protected readonly clientInfoService?: ClientInfoService | undefined; protected readonly hookRegistry?: HookRegistryService | undefined; abstract readonly methodName: string; constructor(mfaDeviceRepository: Repository, userRepository: Repository, config: NAuthConfig, logger: NAuthLogger, passwordService?: unknown | undefined, // Optional - from @nauth-toolkit/core challengeService?: ChallengeService | undefined, auditService?: AuthAuditService | undefined, clientInfoService?: ClientInfoService | undefined, hookRegistry?: HookRegistryService | undefined); /** * Check if this MFA method is allowed by configuration * * @returns True if method is allowed */ isMethodAllowed(): boolean; /** * Resolve the current authenticated user from request context. * * Security: MFA providers must never accept user identity from consumer apps. * The user is derived from request-scoped context (AuthGuard / adapter sets CURRENT_USER). * * @returns Current authenticated user * @throws {NAuthException} FORBIDDEN when user context is missing */ protected getCurrentUserOrThrow(): IUser; abstract setup(setupData?: unknown): Promise; abstract verifySetup(verificationData: unknown, deviceName?: string): Promise; abstract verify(code: unknown, deviceId?: number): Promise; /** * Get user's MFA devices * * @param userId - Internal user ID * @returns Array of MFA devices * * @protected */ protected getUserDevices(userId: number): Promise; /** * Create MFA device for user * * Creates a new MFA device with transaction safety. * * NAuth supports multiple devices per MFA method (e.g., multiple TOTP apps, multiple passkeys). * Some methods still behave like singletons (e.g., SMS/Email) and should enable de-duplication * explicitly via the `options.dedupeWhere` parameter. * * **Race Condition Prevention:** * - Optionally checks for an existing device before creation (provider-controlled) * - Wraps in transaction with pessimistic write lock on user row * - Database unique constraint provides final safety net * * **Transaction Flow:** * 1. Lock user row (prevents concurrent MFA setup) * 2. (Optional) Check for existing device matching de-duplication criteria * 3. Create device if none exists / de-dup disabled * 4. Update user MFA flags * 5. Commit transaction * * @param userId - Internal user ID * @param deviceData - Device data to create * @param options - Optional creation options (e.g., de-duplication criteria) * @returns Created device (or existing device if already present) * @protected * * @example * ```typescript * const device = await this.createDevice(user.id, { * name: 'SMS Phone', * phoneNumber: '+1234567890', * isActive: true, * isPrimary: !user.mfaEnabled, * }); * ``` */ protected createDevice(userId: number, deviceData: Partial, options?: { /** * Optional de-duplication criteria. * * - When provided (even as an empty object), `createDevice` will first attempt to find an * existing device for this user/method and return it. * - Providers can add method-specific keys (e.g., `{ credentialId }` for passkeys). * * SECURITY: Only use stable identifiers here (never secrets). * * @example { credentialId: 'base64url-credential-id' } */ dedupeWhere?: Record; }): Promise; /** * Find active device for user by method * * @param userId - Internal user ID * @param deviceId - Optional device ID * @returns Device if found, null otherwise * @protected */ protected findDevice(userId: number, deviceId?: number): Promise; /** * Update device usage statistics * * @param deviceId - Device ID * @protected */ protected updateDeviceUsage(deviceId: number): Promise; /** * Enable MFA for user * * Sets mfaEnabled flag and updates mfaMethods array. * Called automatically when first device is registered. * Automatically clears MFA_SETUP_REQUIRED challenges if they exist. * * @param user - User to enable MFA for * @protected */ protected enableMFAForUser(user: IUser): Promise; /** * Generate backup codes for user * * Creates single-use recovery codes that can be used when MFA devices are unavailable. * Exposed as optional method in IMFAProviderService interface. * * @param user - User to generate codes for * @returns Generated backup codes (plain text - shown only once) */ generateBackupCodes(): Promise; /** * Verify backup code * * Validates backup code and removes it after use (single-use). * * @param user - User being authenticated * @param code - Backup code to verify * @returns True if code is valid * @protected */ protected verifyBackupCode(user: IUser, code: string): Promise; /** * Generate random alphanumeric code * * @param length - Code length * @returns Random code * @protected */ protected generateRandomCode(length: number): string; /** * Mask phone number for display * * Uses centralized ChallengeService if available (respects config.security.maskSensitiveData). * Falls back to default masking logic if ChallengeService not available. * * @param phone - Phone number * @returns Masked phone number * @protected */ protected maskPhone(phone: string): string; /** * Mask email address for display * * Uses centralized ChallengeService if available (respects config.security.maskSensitiveData). * Falls back to default masking logic if ChallengeService not available. * * Masks the local part of the email while showing the domain. * Example: user@example.com → u***r@example.com * * @param email - Email address * @returns Masked email address * @protected * * @example * ```typescript * const masked = this.maskEmail('user@example.com'); * // Returns: 'u***r@example.com' * ``` */ protected maskEmail(email: string): string; /** * Check if MFA is required for a user * * Determines MFA requirement based on: * - User-level MFA exemption (admin override) * - Global enforcement policy (OPTIONAL, REQUIRED, ADAPTIVE) * - Grace period for REQUIRED enforcement * - User's MFA enrollment date * * ADAPTIVE enforcement currently behaves like REQUIRED (placeholder for future risk-based logic) * * @param user - User to check * @returns True if MFA is required * @protected */ protected isMFARequired(user: IUser): Promise; } //# sourceMappingURL=mfa-base.service.d.ts.map