UNPKG

key-sentry

Version:

A secure JWT token generation and management library

150 lines (133 loc) 5.12 kB
const jwt = require("jsonwebtoken"); const crypto = require("crypto"); class KeySentry { #secretKey; #expiresIn; #algorithm; #refreshSecretKey; #refreshExpiresIn; /** * Creates a JWT service instance * @param {Object} options - JWT configuration options * @param {string} options.secretKey - Secret key for signing tokens * @param {string} options.expiresIn - Token expiration time (e.g. "1h", "7d") * @param {string} options.algorithm - Algorithm used for signing (default: "HS256") * @param {string} options.refreshSecretKey - Secret key for refresh tokens (defaults to different key) * @param {string} options.refreshExpiresIn - Refresh token expiration (e.g. "7d", "30d") */ constructor( secretKey, expiresIn, algorithm, refreshSecretKey, refreshExpiresIn ) { this.#secretKey = secretKey || this.#generateSecretKey(); this.#expiresIn = expiresIn || "1h"; this.#algorithm = algorithm || "HS256"; this.#refreshSecretKey = refreshSecretKey || this.#generateSecretKey(); this.#refreshExpiresIn = refreshExpiresIn || "7d"; } #generateSecretKey() { return crypto.randomBytes(32).toString("hex"); } #decodeToken(token) { return jwt.decode(token); } /** * Generates access token and optionally a refresh token * @param {Object} payload - Data to be included in the token * @param {boolean} [withRefreshToken=true] - Whether to generate a refresh token alongside the access token * @returns {Object} - Object containing generated tokens and metadata * @returns {string} - return.accessToken - The generated JWT access token * @returns {number} - return.accessTokenExpiresAt - Access token expiration timestamp in milliseconds * @returns {number} - return.issuedAt - Token issuance timestamp in milliseconds * @returns {Object} - return.payload - The payload data included in the token * @returns {string} - [return.refreshToken] - The refresh token (if withRefreshToken is true) * @returns {number} - [return.refreshTokenExpiresAt] - Refresh token expiration timestamp (if withRefreshToken is true) * @returns {number} - [return.refreshIssuedAt] - Refresh token issuance timestamp (if withRefreshToken is true) * @throws {Error} - If payload is invalid or token generation fails */ generateToken(payload, withRefreshToken = true) { if (!payload) { throw new Error("Payload is required to generate a token."); } try { const accessToken = jwt.sign(payload, this.#secretKey, { expiresIn: this.#expiresIn, algorithm: this.#algorithm, }); const decodedAccess = this.#decodeToken(accessToken); const result = { accessToken, accessTokenExpiresAt: decodedAccess.exp * 1000, issuedAt: decodedAccess.iat * 1000, payload: { ...payload }, }; if (!withRefreshToken) { return result; } const refreshPayload = { ...payload, jti: crypto.randomBytes(16).toString("hex"), }; const refreshToken = jwt.sign(refreshPayload, this.#refreshSecretKey, { expiresIn: this.#refreshExpiresIn, algorithm: this.#algorithm, }); const decodedRefresh = this.#decodeToken(refreshToken); result.refreshToken = refreshToken; result.refreshTokenExpiresAt = decodedRefresh.exp * 1000; result.refreshIssuedAt = decodedRefresh.iat * 1000; return result; } catch (error) { throw new Error(`Failed to generate token: ${error.message}`); } } /** * Verifies a token's authenticity and expiration status * @param {string} token - The token to verify * @param {string} tokenType - Type of token ('access' or 'refresh') * @returns {Object} - Verification result with validity, expiration status and payload * @throws {Error} - If token is invalid or verification fails */ verifyToken(token, tokenType = "access") { const validTokenTypes = ["access", "refresh"]; if (!validTokenTypes.includes(tokenType)) { throw new Error( `Invalid token type. Expected one of ${validTokenTypes.join(", ")}` ); } if (!token || typeof token !== "string") { throw new Error("Token must be a non-empty string"); } const secretKey = tokenType === "refresh" ? this.#refreshSecretKey : this.#secretKey; try { const payload = jwt.verify(token, secretKey, { algorithms: [this.#algorithm], }); return { valid: true, expired: false, payload, }; } catch (error) { if (error instanceof jwt.TokenExpiredError) { const payload = this.#decodeToken(token); return { valid: false, expired: true, payload, }; } return { valid: false, expired: false, payload: null, error: error.message, }; } } } module.exports = KeySentry;