Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | 3x 3x 42x 42x 42x 42x 42x 42x 1x 1x 1x 1x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 3x | const jwt = require('jsonwebtoken');
const crypto = require('crypto');
class TokenManager {
constructor(config = {}) {
this.secret = config.secret || process.env.VALIDATION_TOKEN_SECRET || this.generateSecret();
this.expiresIn = config.expiresIn || '24h';
this.algorithm = config.algorithm || 'HS256';
this.issuer = config.issuer || 'validation-core';
this.usedTokens = new Set(); // Track used tokens for single-use enforcement
}
/**
* Generate a secure secret if none provided
* @returns {string} Generated secret
*/
generateSecret() {
return crypto.randomBytes(64).toString('hex');
}
/**
* Generate a pre-validation token
* @param {object} payload - Token payload
* @returns {Promise<string>} Generated token
*/
async generateToken(payload) {
const tokenId = crypto.randomBytes(16).toString('hex');
const tokenPayload = {
...payload,
jti: tokenId, // JWT ID for tracking single use
iat: Math.floor(Date.now() / 1000),
iss: this.issuer,
type: payload.type || 'pre-validation'
};
const options = {
expiresIn: this.expiresIn,
algorithm: this.algorithm
};
return jwt.sign(tokenPayload, this.secret, options);
}
/**
* Verify and decode a token
* @param {string} token - Token to verify
* @returns {Promise<object>} Decoded payload
*/
async verifyToken(token) {
try {
// Verify token signature and expiration
const decoded = jwt.verify(token, this.secret, {
algorithms: [this.algorithm],
issuer: this.issuer
});
// Check if token has already been used (single-use enforcement)
Iif (decoded.jti && this.usedTokens.has(decoded.jti)) {
throw new Error('Token has already been used');
}
// Mark token as used
Eif (decoded.jti) {
this.usedTokens.add(decoded.jti);
// Clean up old tokens after expiry (prevent memory leak)
Iif (this.usedTokens.size > 10000) {
this.cleanupUsedTokens();
}
}
return decoded;
} catch (error) {
Iif (error.name === 'TokenExpiredError') {
throw new Error('Token has expired');
} else Eif (error.name === 'JsonWebTokenError') {
throw new Error('Invalid token');
}
throw error;
}
}
/**
* Generate a validation request token (for service-to-validator communication)
* @param {object} preValidationResult - Pre-validation results
* @param {string} serviceName - Service requesting validation
* @returns {Promise<string>} Request token
*/
async generateRequestToken(preValidationResult, serviceName) {
if (!preValidationResult.success) {
throw new Error('Cannot generate request token for failed pre-validation');
}
return this.generateToken({
type: 'validation-request',
serviceName,
preValidation: {
timestamp: preValidationResult.timestamp,
checks: preValidationResult.checks
},
expiresIn: '1h' // Shorter expiry for request tokens
});
}
/**
* Verify a validation request token
* @param {string} token - Request token
* @returns {Promise<object>} Token payload
*/
async verifyRequestToken(token) {
const decoded = await this.verifyToken(token);
if (decoded.type !== 'validation-request') {
throw new Error('Invalid token type for validation request');
}
if (!decoded.preValidation) {
throw new Error('Missing pre-validation data in request token');
}
return decoded;
}
/**
* Clean up expired tokens from used tokens set
*/
cleanupUsedTokens() {
// In a production environment, this would check expiry times
// For now, we'll clear half of the oldest tokens
const tokensToKeep = Array.from(this.usedTokens).slice(-5000);
this.usedTokens = new Set(tokensToKeep);
}
/**
* Revoke a token (add to blacklist)
* @param {string} tokenOrJti - Token or JWT ID to revoke
*/
revokeToken(tokenOrJti) {
try {
// Try to decode if it's a full token
const decoded = jwt.decode(tokenOrJti);
if (decoded && decoded.jti) {
this.usedTokens.add(decoded.jti);
} else {
// Assume it's a JTI directly
this.usedTokens.add(tokenOrJti);
}
} catch {
// If decode fails, assume it's a JTI
this.usedTokens.add(tokenOrJti);
}
}
/**
* Check if a token is revoked/used
* @param {string} token - Token to check
* @returns {boolean} True if revoked/used
*/
isTokenRevoked(token) {
try {
const decoded = jwt.decode(token);
return decoded && decoded.jti && this.usedTokens.has(decoded.jti);
} catch {
return false;
}
}
/**
* Generate a fingerprint for validation data
* @param {object} validationData - Data to fingerprint
* @returns {string} Fingerprint hash
*/
generateFingerprint(validationData) {
const sortedData = JSON.stringify(validationData, Object.keys(validationData).sort());
return crypto.createHash('sha256').update(sortedData).digest('hex');
}
}
module.exports = TokenManager; |