102 lines
3.8 KiB
JavaScript
102 lines
3.8 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.JWTService = void 0;
|
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
class JWTService {
|
|
constructor() {
|
|
this.secretKey = process.env.JWT_SECRET || 'your-secret-key';
|
|
let expiry = 86400;
|
|
if (process.env.JWT_EXPIRY) {
|
|
expiry = parseInt(process.env.JWT_EXPIRY);
|
|
}
|
|
else if (process.env.JWT_EXPIRATION) {
|
|
expiry = this.parseDuration(process.env.JWT_EXPIRATION);
|
|
}
|
|
this.tokenExpiry = expiry;
|
|
this.cookieName = 'auth_token';
|
|
if (process.env.NODE_ENV === 'production' && (!process.env.JWT_SECRET || process.env.JWT_SECRET === 'your-secret-key')) {
|
|
throw new Error('JWT_SECRET environment variable must be set in production');
|
|
}
|
|
}
|
|
create(payload, res) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const payloadWithTimestamps = {
|
|
...payload,
|
|
iat: now,
|
|
exp: now + this.tokenExpiry
|
|
};
|
|
// Don't use expiresIn option since we're manually setting exp in payload
|
|
const options = {};
|
|
const token = jsonwebtoken_1.default.sign(payloadWithTimestamps, this.secretKey, options);
|
|
res.cookie(this.cookieName, token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'strict',
|
|
maxAge: this.tokenExpiry * 1000, // Convert to milliseconds
|
|
});
|
|
return token;
|
|
}
|
|
verify(req) {
|
|
try {
|
|
const token = req.cookies[this.cookieName];
|
|
if (!token)
|
|
return null;
|
|
const decoded = jsonwebtoken_1.default.verify(token, this.secretKey);
|
|
return decoded;
|
|
}
|
|
catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
// Check if token needs refresh (within 25% of expiry time)
|
|
shouldRefreshToken(payload) {
|
|
if (!payload.exp || !payload.iat)
|
|
return false;
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const tokenAge = now - payload.iat;
|
|
const tokenLifetime = payload.exp - payload.iat;
|
|
const refreshThreshold = tokenLifetime * 0.75; // Refresh when 75% of lifetime has passed
|
|
return tokenAge >= refreshThreshold;
|
|
}
|
|
// Conditionally refresh token only if needed
|
|
refreshIfNeeded(payload, res) {
|
|
if (this.shouldRefreshToken(payload)) {
|
|
// Create new token with fresh timestamps, but same user data
|
|
const freshPayload = {
|
|
userId: payload.userId,
|
|
authLevel: payload.authLevel,
|
|
userStatus: payload.userStatus,
|
|
orgId: payload.orgId
|
|
};
|
|
this.create(freshPayload, res);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Parse duration string to seconds (e.g., "24h", "7d", "30m")
|
|
* @param duration Duration string
|
|
* @returns Duration in seconds
|
|
*/
|
|
parseDuration(duration) {
|
|
const match = duration.match(/^(\d+)([smhd])$/);
|
|
if (!match) {
|
|
throw new Error(`Invalid duration format: ${duration}. Use format like '24h', '7d', '30m'`);
|
|
}
|
|
const [, value, unit] = match;
|
|
const num = parseInt(value);
|
|
switch (unit) {
|
|
case 's': return num; // seconds
|
|
case 'm': return num * 60; // minutes
|
|
case 'h': return num * 60 * 60; // hours
|
|
case 'd': return num * 60 * 60 * 24; // days
|
|
default:
|
|
throw new Error(`Unsupported duration unit: ${unit}`);
|
|
}
|
|
}
|
|
}
|
|
exports.JWTService = JWTService;
|
|
//# sourceMappingURL=JWTService.js.map
|