"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenService = void 0; const crypto = __importStar(require("crypto")); const Logger_1 = require("./Logger"); class TokenService { /** * Generate a secure random token * @param length - Length of the token in bytes (default: 32) * @returns Hexadecimal string token */ static generateSecureToken(length = TokenService.TOKEN_LENGTH) { try { return crypto.randomBytes(length).toString('hex'); } catch (error) { (0, Logger_1.logError)('TokenService.generateSecureToken error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to generate secure token'); } } /** * Generate email verification token with expiration * @returns VerificationToken object with token and expiration info */ static generateVerificationToken() { try { const token = this.generateSecureToken(); const createdAt = new Date(); const expiresAt = new Date(createdAt.getTime() + (this.VERIFICATION_TOKEN_EXPIRES_HOURS * 60 * 60 * 1000)); return { token, createdAt, expiresAt }; } catch (error) { (0, Logger_1.logError)('TokenService.generateVerificationToken error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to generate verification token'); } } /** * Generate password reset token with expiration * @returns PasswordResetToken object with token and expiration info */ static generatePasswordResetToken() { try { const token = this.generateSecureToken(); const createdAt = new Date(); const expiresAt = new Date(createdAt.getTime() + (this.PASSWORD_RESET_TOKEN_EXPIRES_HOURS * 60 * 60 * 1000)); return { token, createdAt, expiresAt }; } catch (error) { (0, Logger_1.logError)('TokenService.generatePasswordResetToken error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to generate password reset token'); } } /** * Check if a token has expired * @param expiresAt - Expiration date of the token * @returns True if token has expired, false otherwise */ static isTokenExpired(expiresAt) { try { return new Date() > expiresAt; } catch (error) { (0, Logger_1.logError)('TokenService.isTokenExpired error', error instanceof Error ? error : new Error(String(error))); return true; // Assume expired on error for security } } /** * Validate token format (basic validation) * @param token - Token to validate * @returns True if token format is valid, false otherwise */ static isValidTokenFormat(token) { try { if (!token || typeof token !== 'string') { return false; } // Check if token is hexadecimal and has expected length const hexRegex = /^[a-f0-9]+$/i; const expectedLength = this.TOKEN_LENGTH * 2; // Each byte becomes 2 hex characters return hexRegex.test(token) && token.length === expectedLength; } catch (error) { (0, Logger_1.logError)('TokenService.isValidTokenFormat error', error instanceof Error ? error : new Error(String(error))); return false; } } /** * Generate a verification URL with token * @param baseUrl - Base URL of the application * @param token - Verification token * @returns Complete verification URL */ static generateVerificationUrl(baseUrl, token) { try { // Remove trailing slash from baseUrl if present const cleanBaseUrl = baseUrl.replace(/\/$/, ''); return `${cleanBaseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`; } catch (error) { (0, Logger_1.logError)('TokenService.generateVerificationUrl error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to generate verification URL'); } } /** * Generate a password reset URL with token * @param baseUrl - Base URL of the application * @param token - Password reset token * @returns Complete password reset URL */ static generatePasswordResetUrl(baseUrl, token) { try { // Remove trailing slash from baseUrl if present const cleanBaseUrl = baseUrl.replace(/\/$/, ''); return `${cleanBaseUrl}/api/auth/reset-password?token=${encodeURIComponent(token)}`; } catch (error) { (0, Logger_1.logError)('TokenService.generatePasswordResetUrl error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to generate password reset URL'); } } /** * Hash a token for secure storage in database * @param token - Plain text token to hash * @returns Hashed token */ static async hashToken(token) { try { if (!token || typeof token !== 'string') { throw new Error('Token must be a non-empty string'); } return crypto.createHash('sha256').update(token).digest('hex'); } catch (error) { (0, Logger_1.logError)('TokenService.hashToken error', error instanceof Error ? error : new Error(String(error))); throw new Error('Failed to hash token'); } } /** * Verify a plain text token against a hashed token * @param plainToken - Plain text token to verify * @param hashedToken - Hashed token to compare against * @returns True if tokens match, false otherwise */ static async verifyToken(plainToken, hashedToken) { try { if (!plainToken || !hashedToken) { return false; } const hashedPlainToken = await this.hashToken(plainToken); return hashedPlainToken === hashedToken; } catch (error) { (0, Logger_1.logError)('TokenService.verifyToken error', error instanceof Error ? error : new Error(String(error))); return false; } } /** * Get token expiration info in human-readable format * @param expiresAt - Expiration date * @returns Human-readable expiration info */ static getExpirationInfo(expiresAt) { try { const now = new Date(); const expired = now > expiresAt; if (expired) { const timeAgo = Math.floor((now.getTime() - expiresAt.getTime()) / (1000 * 60)); return { expired: true, timeLeft: `Expired ${timeAgo} minute(s) ago` }; } const timeLeft = Math.floor((expiresAt.getTime() - now.getTime()) / (1000 * 60)); const hours = Math.floor(timeLeft / 60); const minutes = timeLeft % 60; let timeString = ''; if (hours > 0) { timeString = `${hours} hour(s)`; if (minutes > 0) { timeString += ` and ${minutes} minute(s)`; } } else { timeString = `${minutes} minute(s)`; } return { expired: false, timeLeft: `Expires in ${timeString}` }; } catch (error) { (0, Logger_1.logError)('TokenService.getExpirationInfo error', error instanceof Error ? error : new Error(String(error))); return { expired: true, timeLeft: 'Unable to determine expiration' }; } } } exports.TokenService = TokenService; TokenService.VERIFICATION_TOKEN_EXPIRES_HOURS = 24; TokenService.PASSWORD_RESET_TOKEN_EXPIRES_HOURS = 1; TokenService.TOKEN_LENGTH = 32; //# sourceMappingURL=TokenService.js.map