import { Repository } from 'typeorm'; import { IUser } from '../interfaces/entities.interface'; import { BaseUser, BaseLoginAttempt, BaseChallengeSession } from '../entities'; import { PasswordService } from './password.service'; import { SessionService } from './session.service'; import { EmailVerificationService } from './email-verification.service'; import { PhoneVerificationService } from './phone-verification.service'; import { ClientInfoService } from './client-info.service'; import { ChallengeService } from './challenge.service'; import { AuthChallengeHelperService } from './auth-challenge-helper.service'; import { AccountLockoutStorageService } from '../storage/account-lockout-storage.service'; import { InternalAuthAuditService as AuthAuditService } from './auth-audit.service'; import { TrustedDeviceService } from './trusted-device.service'; import { MFAService } from './mfa.service'; import { HookRegistryService } from './hook-registry.service'; import { AuthAuditEventType } from '../enums/auth-audit-event-type.enum'; import { ChallengeResponseData, CollectPhoneResponse, VerifyPhoneResponse, VerifyMFACodeResponse, VerifyMFAPasskeyResponse, MFASetupResponse } from '../dto/challenge-response.dto'; import { AuthResponseDTO } from '../dto/auth-response.dto'; import { UserUpdateDTO } from '../dto/user-update.dto'; import { NAuthConfig } from '../interfaces/config.interface'; import { NAuthLogger } from '../utils/nauth-logger'; /** * Internal helper service for AuthService * * Contains private utility methods for challenge handling, validation, * password management, and login tracking. This class is NOT exported from * the package and should only be used internally by AuthService. * * INTERNAL USE ONLY - DO NOT IMPORT DIRECTLY * * @internal */ export declare class AuthServiceInternalHelpers { private readonly userRepository; private readonly loginAttemptRepository; private readonly emailVerificationService; private readonly phoneVerificationService; private readonly challengeService; private readonly challengeHelper; private readonly clientInfoService; private readonly sessionService; private readonly accountLockoutStorage; private readonly config; private readonly logger; private readonly hookRegistry; constructor(userRepository: Repository, loginAttemptRepository: Repository, emailVerificationService: EmailVerificationService, phoneVerificationService: PhoneVerificationService | undefined, challengeService: ChallengeService, challengeHelper: AuthChallengeHelperService, clientInfoService: ClientInfoService, sessionService: SessionService, accountLockoutStorage: AccountLockoutStorageService, config: NAuthConfig, logger: NAuthLogger, hookRegistry: HookRegistryService); /** * Execute a callback with a specific user bound into CURRENT_USER context. * * MFA providers must derive the user from request-scoped context. During challenge flows * we bind the resolved user explicitly to avoid taking user identity from consumer inputs. * * @param user - User to bind into context * @param callback - Callback to execute * @returns Callback result */ private withUserContext; /** * Handle VERIFY_EMAIL challenge * * @param challengeSession - Challenge session with user * @param code - Email verification code * @returns Authentication response with tokens or next challenge */ handleVerifyEmail(challengeSession: BaseChallengeSession & { user?: BaseUser; }, code: string): Promise; /** * Handle VERIFY_PHONE challenge * * @param challengeSession - Challenge session with user * @param data - Phone verification data (phone number or code) * @returns Authentication response with tokens or next challenge */ handleVerifyPhone(challengeSession: BaseChallengeSession & { user?: BaseUser; }, data: VerifyPhoneResponse | CollectPhoneResponse): Promise; /** * Handle MFA_REQUIRED challenge * * @param challengeSession - Challenge session with user * @param data - MFA verification data * @param mfaService - MFA service (passed from AuthService) * @param trustedDeviceService - Trusted device service (optional, passed from AuthService) * @param auditService - Audit service (optional, passed from AuthService) * @returns Authentication response with tokens or next challenge */ handleMFAVerification(challengeSession: BaseChallengeSession & { user?: BaseUser; }, data: VerifyMFACodeResponse | VerifyMFAPasskeyResponse, mfaService: MFAService | undefined, trustedDeviceService: TrustedDeviceService | undefined, auditService: AuthAuditService | undefined): Promise; /** * Handle FORCE_CHANGE_PASSWORD challenge * * @param challengeSession - Challenge session with user * @param newPassword - New password * @param passwordService - Password service (passed from AuthService) * @param auditService - Audit service (optional, passed from AuthService) * @returns Authentication response with tokens or next challenge */ handleForceChangePassword(challengeSession: BaseChallengeSession & { user?: BaseUser; }, newPassword: string, passwordService: PasswordService, auditService: AuthAuditService | undefined): Promise; /** * Handle MFA_SETUP_REQUIRED challenge * * @param challengeSession - Challenge session with user * @param data - MFA setup data * @param mfaService - MFA service (passed from AuthService) * @param auditService - Audit service (optional, passed from AuthService) * @returns Authentication response with tokens or next challenge */ handleMFASetup(challengeSession: BaseChallengeSession & { user?: BaseUser; }, data: MFASetupResponse, mfaService: MFAService | undefined, _auditService: AuthAuditService | undefined): Promise; /** * Validate that response type matches expected challenge type * * @param expected - Expected challenge type * @param provided - Provided challenge type * @throws {NAuthException} If types don't match */ validateChallengeTypeMatch(expected: string, provided: string): void; /** * Validate parameters for challenge type * * Service-level validation ensures Express/other frameworks get same validation as NestJS. * This is critical for non-DTO-based applications. * * @param type - Challenge type * @param data - Challenge response data * @throws {NAuthException} If validation fails */ validateChallengeParams(type: string, data: ChallengeResponseData): void; /** * Checks if the login identifier matches the specified allowed type. * * Determines if the given identifier is a valid email, username, phone, or allowed hybrid, * according to the configured identifier type restriction. * * @param identifier - The login identifier to check (email, username, or phone) * @param allowedType - The permitted identifier type ('email', 'username', 'phone', or 'email_or_username') * @returns True if the identifier conforms to the allowed type, otherwise false */ validateIdentifierType(identifier: string, allowedType: 'email' | 'username' | 'phone' | 'email_or_username'): boolean; /** * Ensures email, phone, and username are unique for other users before update. * * Throws if another user already has the specified email, phone, or username. * Phone uniqueness check respects `config.signup.allowDuplicatePhones` setting: * - If `allowDuplicatePhones` is true, phone uniqueness is not checked * - If `allowDuplicatePhones` is false or undefined, phone must be unique * * @param userId - Internal numeric user ID (excluded from check) * @param updateData - User fields to check for uniqueness * @throws {NAuthException} If a unique constraint is violated for email, phone, or username */ validateUniquenessConstraints(userId: number, updateData: UserUpdateDTO): Promise; /** * Retrieves a user entity by login identifier. * * Performs a lookup for a user by email, username, or phone number. * The search respects the identifierType restriction when provided, limiting which fields are queried. * * **Case Sensitivity:** * - Email: Case-insensitive (normalized to lowercase, matches signup behavior) * - Username: Case-insensitive (normalized to lowercase, matches signup behavior) * - Phone: Case-sensitive (no normalization) * * @param identifier - Login credential (email, username, or phone) * @param identifierType - Restricts search to a specific identifier type ('email', 'username', 'phone', or 'email_or_username') * @returns The user entity if found, otherwise null */ findUserByIdentifier(identifier: string, identifierType?: 'email' | 'username' | 'phone' | 'email_or_username'): Promise; /** * Centralized password update flow used by: * - changePassword() * - confirmForgotPassword() * - adminSetPassword() * - FORCE_CHANGE_PASSWORD challenge handler * * WHY: * - Prevent logic drift between different password-changing entrypoints * - Ensure consistent validation, history enforcement, persistence, session revocation, and audit trails * * @param params - Password update parameters * @param passwordService - Password service (passed from AuthService) * @param auditService - Audit service (optional, passed from AuthService) * @returns Sessions revoked count (0 when not revoked) * @throws {NAuthException} WEAK_PASSWORD | PASSWORD_REUSED | NOT_FOUND */ updateUserPassword(params: { user: IUser; newPassword: string; mustChangePassword: boolean; revokeSessions: boolean; revokeReason: string; beforePersist?: () => Promise; audit?: { eventType: AuthAuditEventType; eventStatus: 'SUCCESS' | 'FAILURE' | 'INFO' | 'SUSPICIOUS'; reason?: string; description?: string; authMethod?: string; metadata?: Record; }; }, passwordService: PasswordService, auditService: AuthAuditService | undefined): Promise<{ sessionsRevoked: number; }>; /** * Handles a failed login by recording the attempt, applying IP-based lockout policy, * and invoking relevant hooks. * * @param identifier - User identifier (email/username/phone) * @param reason - Optional reason for failure */ handleFailedLogin(identifier: string, reason?: string): Promise; /** * Records a login attempt with client context. * * @param email - User's email address * @param success - True if login succeeded, false if failed * @param failureReason - Optional reason for failure * @param userId - Optional internal user ID (only for successful logins) */ recordLoginAttempt(email: string, success: boolean, failureReason?: string, userId?: number): Promise; /** * Clear authentication cookies from response * * @param response - HTTP response object with clearCookie method * @param forgetDevice - Whether to also clear device token cookie */ clearAuthCookies(response: { clearCookie?: (name: string, options?: unknown) => void; }, forgetDevice: boolean): void; /** * Mask email address for privacy (show first char and domain) * * Uses centralized ChallengeService which respects config.security.maskSensitiveData. * * @param email - Email address to mask * @returns Masked email (e.g., 'u***r@example.com') */ maskEmail(email: string): string; /** * Mask phone number for privacy (show last 4 digits) * * Uses centralized ChallengeService which respects config.security.maskSensitiveData. * * @param phone - Phone number to mask * @returns Masked phone (e.g., '***-***-1234') */ maskPhone(phone: string): string; /** * Validate reCAPTCHA token only when explicitly required via @RequireRecaptcha(). * * Validation runs solely when the route has @RequireRecaptcha(). * If the decorator is not present, any recaptchaToken in the request is ignored. * * Logic: * 1. Skip if reCAPTCHA not enabled in config * 2. Skip if route does not have @RequireRecaptcha() (ignore token) * 3. If @RequireRecaptcha(): require token and validate (throws if missing/invalid) * * @param token - reCAPTCHA token from client (optional) * @param clientIp - Client IP address for validation (optional) * @param action - Action name for verification and per-action score lookup (e.g., 'login', 'signup') * * @throws {NAuthException} RECAPTCHA_REQUIRED - Token required but not provided * @throws {NAuthException} RECAPTCHA_PROVIDER_MISSING - Provider not configured * @throws {NAuthException} RECAPTCHA_VALIDATION_FAILED - Token validation failed * @throws {NAuthException} RECAPTCHA_SCORE_TOO_LOW - Score below minimum (v3/Enterprise) * * @example * ```typescript * // In controller: * @RequireRecaptcha() * @Post('login') * async login(@Body() dto: LoginDTO) { ... } * * // In AuthService.login(): * await this.helpers.validateRecaptchaIfNeeded(dto.recaptchaToken, clientInfo.ipAddress, 'login'); * ``` */ validateRecaptchaIfNeeded(token: string | undefined, clientIp?: string, action?: string): Promise; /** * Verify reCAPTCHA token with Google's API * * @param token - reCAPTCHA token from client * @param clientIp - Client IP address (optional but recommended) * @param action - Action name for Enterprise expectedAction validation and per-action score lookup * * @throws {NAuthException} RECAPTCHA_PROVIDER_MISSING - Provider not configured * @throws {NAuthException} RECAPTCHA_VALIDATION_FAILED - Token validation failed * @throws {NAuthException} RECAPTCHA_SCORE_TOO_LOW - Score below minimum (v3/Enterprise) */ private verifyRecaptchaToken; } //# sourceMappingURL=auth-service-internal-helpers.d.ts.map