Files
SerpentRace/SerpentRace_Backend/dist/Application/Services/TokenService.js
T

245 lines
9.4 KiB
JavaScript

"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