UNPKG

shjwt

Version:
106 lines (89 loc) 3.61 kB
/** * @fileoverview shJWT module. This is a library that provides methods to encode * and decode data using JSON Web Tokens(JWT) and HMAC with SHA256 for signing. * @module shjwt */ const crypto = require('crypto'); /** * Base64URL encode a string according to the JWT standard. * @param {Buffer} buffer - The data to encode. * @return {string} Base64URL encoded string. */ const base64UrlEncode = (buffer) => { return buffer.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); }; /** * Base64URL decode a string according to the JWT standard. * @param {string} str - The string to decode. * @return {Buffer} Decoded Buffer. */ const base64UrlDecode = (str) => { const padding = 4 - (str.length % 4); const base64 = str.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padding); return Buffer.from(base64, 'base64'); }; /** * Encode the payload into a JWT format. * @param {Object} payload - The payload that will be encoded. * @param {string} secret - The secret key for generating the HMAC. * @return {Promise<string>} A Promise that resolves with the JWT token. * @throws Will throw an error if an issue occurs during encoding. */ exports.encode = async (payload, secret) => { try { if (!secret || secret.length < 32) { throw new Error('Secret key must be at least 32 characters long'); } const header = { typ: 'JWT', alg: 'HS256' }; payload.iat = Math.floor(Date.now() / 1000); // Issued at timestamp const base64Header = base64UrlEncode(Buffer.from(JSON.stringify(header))); const base64Payload = base64UrlEncode(Buffer.from(JSON.stringify(payload))); const signature = crypto.createHmac('sha256', secret) .update(`${base64Header}.${base64Payload}`) .digest(); const base64Signature = base64UrlEncode(signature); return `${base64Header}.${base64Payload}.${base64Signature}`; } catch (error) { throw new Error(`Error encoding token: ${error.message}`); } }; /** * Decode the JWT into the payload. * @param {string} jwt - The JWT to be decoded. * @param {string} secret - The secret key for verifying the HMAC. * @return {Promise<Object>} A promise that resolves with the decoded payload. * @throws Will throw an error if the JWT is not well formatted or if the * verification failed. */ exports.decode = async (jwt, secret) => { try { if (!secret || secret.length < 32) { throw new Error('Secret key must be at least 32 characters long'); } const [base64Header, base64Payload, base64Signature] = jwt.split('.'); if (!base64Header || !base64Payload || !base64Signature) { throw new Error('Invalid token format'); } const header = JSON.parse(base64UrlDecode(base64Header).toString('utf8')); if (header.alg !== 'HS256') { throw new Error('Unsupported algorithm'); } const payload = JSON.parse(base64UrlDecode(base64Payload).toString('utf8')); const expectedSignature = crypto.createHmac('sha256', secret) .update(`${base64Header}.${base64Payload}`) .digest(); if (!crypto.timingSafeEqual(Buffer.from(base64UrlDecode(base64Signature)), expectedSignature)) { throw new Error('Signature verification failed'); } // Expiration check if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) { throw new Error('Token has expired'); } return payload; } catch (error) { throw new Error(`Invalid token: ${error.message}`); } };