245 lines
9.4 KiB
JavaScript
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
|