secure-time-token
Version:
A lightweight package for generating and validating time-based secure tokens.
97 lines (82 loc) • 3.8 kB
JavaScript
const CryptoJS = require('crypto-js');
class ScureTimeToken {
constructor() {
if (ScureTimeToken.instance) {
return ScureTimeToken.instance; // Ensure singleton behavior
}
this.decodedPayload = null;
// Bind methods to ensure `this` remains the class instance
this.generateToken = this.generateToken.bind(this);
this.validateToken = this.validateToken.bind(this);
this.getDecodedPayload = this.getDecodedPayload.bind(this);
ScureTimeToken.instance = this;
}
/**
* Generate a token with a specified TTL (in seconds).
* The token contains a payload that includes an expiration time and optional additional data,
* signed using HMAC for integrity and security.
*
* @param {number} ttlInSeconds - Time-to-live for the token in seconds.
* @param {string} secretKey - Secret key used to sign the token.
* @param {object} options - Additional optional payload data to include in the token.
* @returns {string} A string representing the generated token.
*/
generateToken(ttlInSeconds, secretKey, options = {}) {
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const expirationTime = currentTime + ttlInSeconds;
let payload = {
gen: currentTime,
exp: expirationTime
};
if (Object.keys(options).length > 0) {
payload = { ...payload, ...options };
}
const strPayload = JSON.stringify(payload);
// Use CryptoJS to generate the HMAC-SHA256 signature
const signature = CryptoJS.HmacSHA256(strPayload, secretKey).toString(CryptoJS.enc.Hex);
const token = Buffer.from(strPayload).toString('base64') + '.' + signature;
return token;
}
/**
* Validate a token by verifying its HMAC signature and checking if it has expired.
* If the token is valid, the payload is stored in the class instance for further use.
*
* @param {string} token - The token to validate, in the format "payload.signature".
* @param {string} secretKey - Secret key used to verify the token's HMAC signature.
* @returns {boolean} Returns true if the token is valid.
* @throws {Error} Throws an error if the token format is invalid, the signature is incorrect, or the token has expired.
*/
validateToken(token, secretKey) {
try {
const [encodedPayload, signature] = token.split('.');
if (!encodedPayload || !signature) {
throw new Error('Invalid token format');
}
const decodedPayload = Buffer.from(encodedPayload, 'base64').toString('utf8');
const payload = JSON.parse(decodedPayload);
// Recreate the signature using CryptoJS
const expectedSignature = CryptoJS.HmacSHA256(decodedPayload, secretKey).toString(CryptoJS.enc.Hex);
if (signature !== expectedSignature) {
throw new Error('Invalid token signature');
}
const currentTimestamp = Math.floor(Date.now() / 1000);
if (currentTimestamp > payload.exp) {
throw new Error('Token has expired');
}
this.decodedPayload = payload;
return true;
} catch (error) {
throw error;
}
}
/**
* Retrieve the most recently decoded payload from the last validated token.
*
* @returns {object|null} The payload object of the last validated token, or null if no token has been validated yet.
*/
getDecodedPayload() {
return this.decodedPayload;
}
}
// Export a single instance of the ScureTimeToken class (singleton pattern)
module.exports = new ScureTimeToken();