import { ISession, IUser } from '../interfaces/entities.interface'; import { Repository } from 'typeorm'; import { BaseSession } from '../entities'; import { StorageAdapter } from '../interfaces/storage-adapter.interface'; import { InternalAuthAuditService as AuthAuditService } from './auth-audit.service'; import { ClientInfoService } from './client-info.service'; import { NAuthLogger } from '../utils/nauth-logger'; import { NAuthConfig } from '../interfaces/config.interface'; /** * Session Service * * Manages user sessions and device tracking including: * - Creating new sessions with device information * - Finding sessions by ID or refresh token * - Updating session activity and tokens (rotation) * - Revoking individual or all user sessions * - Token family management for reuse detection * - Token reuse detection with storage tracking * - Cleanup of expired sessions * * Security Features: * - Token family tracking for reuse detection * - Used refresh token tracking (prevents reuse attacks) * - Session expiration management * - Device fingerprinting support * - Revocation with reason tracking * - Activity timestamp updates * * @example * ```typescript * // Create session * const session = await sessionService.createSession({ * userId: user.id, // Internal ID (integer) * accessTokenHash: 'hash1', * refreshTokenHash: 'hash2', * tokenFamily: 'family-abc', * // Client info (ipAddress, userAgent, etc.) automatically extracted from ClientInfoService * expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), * }); * * // Revoke all user sessions (global signout) * const revokedCount = await sessionService.revokeAllUserSessions( * user.id, // Internal ID (integer) * 'User requested global signout' * ); * ``` */ export declare class SessionService { private readonly sessionRepository; private readonly storageAdapter; private readonly clientInfoService; private readonly config; private readonly logger; private readonly auditService?; constructor(sessionRepository: Repository, storageAdapter: StorageAdapter, clientInfoService: ClientInfoService, config: NAuthConfig, logger: NAuthLogger, auditService?: AuthAuditService | undefined); /** * Calculate session expiration date from config * * Parses session.maxLifetime config (e.g., '30d', '7d', '5h') and returns * the expiration Date. Defaults to 30 days if not configured. * * @returns Session expiration date */ getSessionExpirationDate(): Date; /** * Parse maxLifetime from string or number * * @param maxLifetime - Max lifetime (e.g., '30d', '7d', '5h', 2592000) * @returns Max lifetime in seconds * @private */ private parseMaxLifetime; /** * Create a new session * * Creates a session record with token hashes, device information, * and expiration time. Used during login and signup. * * @param data - Session creation data * @param data.userId - Internal user ID (integer, not sub) * @param data.accessTokenHash - SHA-256 hash of access token * @param data.refreshTokenHash - SHA-256 hash of refresh token * @param data.tokenFamily - Token family ID for rotation detection * @param data.deviceId - Optional device identifier (UUID). Auto-generated if not provided. * @param data.deviceName - Optional device name. Falls back to parsed value from ClientInfoService if not provided. * @param data.deviceType - Optional device type (mobile, desktop, tablet). Falls back to parsed value from ClientInfoService if not provided. * @param data.expiresAt - Session expiration date * @remarks Client info (ipAddress, ipCountry, ipCity, userAgent, platform, browser) is automatically extracted from ClientInfoService context * @param data.isTrustedDevice - Whether device is trusted (may skip MFA) * @param data.authMethod - Authentication method: 'password', 'google', 'facebook', 'github', etc. * @returns Created session * * @example * ```typescript * const session = await sessionService.createSession({ * userId: user.id, // Internal ID (integer) * accessTokenHash: jwtService.hashToken(accessToken), * refreshTokenHash: jwtService.hashToken(refreshToken), * tokenFamily: jwtService.generateTokenFamily(), * // Client info (ipAddress, userAgent, etc.) automatically extracted from ClientInfoService * expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), * }); * ``` */ createSession(data: { userId: number; accessTokenHash: string; refreshTokenHash: string; tokenFamily: string; deviceId?: string; deviceName?: string; deviceType?: string; expiresAt: Date; isTrustedDevice?: boolean; authMethod?: string; }): Promise; /** * Find session by ID * @param sessionId - Session ID (can be string from JWT or number) */ findById(sessionId: string | number): Promise; /** * Find session by ID with minimal fields (hot-path) * @param sessionId - Session ID (string or number) */ findByIdLight(sessionId: string | number): Promise | null>; /** * Load session light + user auth context in a single DB query (hot-path) * * WHY: * - Normal request authentication performs: * 1) session validation query * 2) user load query * 3) optional session revalidation query (TOCTOU protection) * - This method safely collapses (1) + (2) into a single query while keeping (3) available. * * Security: * - Returns a sanitized user object (no passwordHash/totpSecret/backupCodes/passwordHistory) * - Enforces user active status (throws ACCOUNT_INACTIVE) * * @param sessionId - Session ID (string or number) * @returns Session light + safe user object, or null if session not found * @throws {NAuthException} When user is missing/inactive (data integrity / security) * * @example * ```typescript * const ctx = await sessionService.findAuthContextBySessionId('123'); * if (ctx) { * console.log(ctx.session.id, ctx.user.sub); * } * ``` */ findAuthContextBySessionId(sessionId: string | number): Promise<{ session: Pick; user: IUser; } | null>; /** * Build a safe user object for request auth context * * Removes secrets and computes `hasPasswordHash` while ensuring `passwordHash` * never escapes the hot-path request context. * * @param user - User entity (may include passwordHash) * @returns Sanitized user object * @private */ private buildSafeUserForAuthContext; /** * Find session by refresh token hash */ findByRefreshToken(refreshTokenHash: string): Promise; /** * Find all active sessions for a user * @param userId - Internal user ID (integer) * @returns Array of active sessions */ findUserSessions(userId: number): Promise; /** * Update session activity timestamp * @param sessionId - Session ID (can be string from JWT or number) */ updateActivity(sessionId: string | number): Promise; /** * Update session with new tokens (for rotation) * @param sessionId - Session ID (can be string from JWT or number) */ updateTokens(sessionId: string | number, accessTokenHash: string, refreshTokenHash: string): Promise; /** * Create a session and update token hashes atomically within one transaction * * Uses a callback to generate token hashes after obtaining the session ID. * This allows callers to embed sessionId in JWTs, then persist hashes atomically. */ createSessionAtomic(data: { userId: number; tokenFamily: string; deviceId?: string; deviceName?: string; deviceType?: string; expiresAt: Date; isTrustedDevice?: boolean; authMethod?: string; }, generateHashes: (sessionId: number) => Promise<{ accessTokenHash: string; refreshTokenHash: string; extra?: T; }>): Promise<{ session: ISession; extra?: T; }>; /** * Revoke a single session * @param sessionId - Session ID (can be string from JWT or number) * @param reason - Optional reason for revocation * @param metadata - Optional metadata to include in audit trail */ revokeSession(sessionId: string | number, reason?: string, metadata?: Record): Promise; /** * Revoke all sessions for a user (global signout) * @param userId - Internal user ID (integer) * @param reason - Optional reason for revocation * @returns Number of sessions revoked */ revokeAllUserSessions(userId: number, reason?: string): Promise; /** * Revoke all sessions for a user with a specific deviceId * * Used to prevent duplicate sessions when a user logs in on the same device. * Only revokes active (non-revoked, non-expired) sessions to avoid unnecessary updates. * * @param userId - Internal user ID (integer) * @param deviceId - Device identifier to match * @param reason - Optional reason for revocation * @returns Number of sessions revoked * * @example * ```typescript * // Revoke existing sessions before creating new one for same device * const revokedCount = await sessionService.revokeUserSessionsByDeviceId( * user.id, * deviceId, * 'New login on same device' * ); * ``` */ revokeUserSessionsByDeviceId(userId: number, deviceId: string, reason?: string): Promise; /** * Revoke all sessions in a token family (for reuse detection) */ revokeTokenFamily(tokenFamily: string, reason?: string): Promise; /** * Cleanup expired sessions */ cleanupExpiredSessions(): Promise; /** * Count active sessions for a user * @param userId - Internal user ID (integer) * @returns Number of active sessions */ countUserSessions(userId: number): Promise; /** * Mark a refresh token as used * * Stores the token hash in cache with expiration matching the refresh token TTL. * Used to detect token reuse attacks where stolen tokens are reused multiple times. * * SECURITY CRITICAL: This prevents token replay attacks * * @param tokenHash - SHA-256 hash of the refresh token * @param ttlSeconds - Time to live in seconds (should match refresh token expiry) * * @example * ```typescript * // Mark token as used during refresh * await sessionService.markRefreshTokenAsUsed(tokenHash, 30 * 24 * 60 * 60); * ``` */ markRefreshTokenAsUsed(tokenHash: string, ttlSeconds: number): Promise; /** * Check if a refresh token has been used before * * If token has been used, it indicates a token reuse attack and the entire * token family should be revoked immediately. * * @param tokenHash - SHA-256 hash of the refresh token * @returns True if token has been used before, false otherwise * * @example * ```typescript * const isReused = await sessionService.isRefreshTokenUsed(tokenHash); * if (isReused) { * // TOKEN REUSE DETECTED - SECURITY BREACH! * await sessionService.revokeTokenFamily(session.tokenFamily); * throw new UnauthorizedException('Token reuse detected'); * } * ``` */ isRefreshTokenUsed(tokenHash: string): Promise; /** * Acquire a distributed lock for token refresh * * Uses atomic set-if-not-exists (NX) operation to prevent concurrent refresh attempts. * Lock is automatically released after TTL expires, or manually via releaseRefreshLock. * * @param lockKey - Lock key (e.g., `session-refresh:${sessionId}` or `refresh-lock:${tokenHash}`) * @param ttlMs - Lock TTL in milliseconds (default: 10000ms) * @returns True if lock was acquired, false if already locked by another request * * @example * ```typescript * const lockKey = `session-refresh:${sessionId}`; * const lockAcquired = await sessionService.acquireRefreshLock(lockKey, 10000); * if (!lockAcquired) { * throw new Error('Refresh already in progress'); * } * try { * // ... perform refresh ... * } finally { * await sessionService.releaseRefreshLock(lockKey); * } * ``` */ acquireRefreshLock(lockKey: string, ttlMs?: number): Promise; /** * Release a distributed lock for token refresh * * @param lockKey - Lock key (must match the key used in acquireRefreshLock) */ releaseRefreshLock(lockKey: string): Promise; } //# sourceMappingURL=session.service.d.ts.map