import { existsSync } from 'fs'; import { readFile, writeFile } from 'node:fs/promises'; import { generateKeyPairSync, createHash, createPublicKey, createPrivateKey } from 'node:crypto'; import jwt from 'jsonwebtoken'; let privateKey, publicKey, fingerprint; /** * * @returns {Promise} */ export async function initJwt() { const secretPath = process.env.JWT_SECRET_PATH; if (!secretPath || !existsSync(secretPath)) { throw new Error( 'JWT secret path is not defined in environment variables, or the directory does not exist.' ); } console.log(`[JWT] Initializing JWT with secret path: ${secretPath}`); const privateKeyPath = `${secretPath}/jwt.key`; const publicKeyPath = `${secretPath}/jwt.key.pub`; if (!existsSync(privateKeyPath)) { console.log('[JWT] Generating new keypair'); const keyPair = generateKeyPairSync('ec', { namedCurve: 'secp521r1' }); privateKey = keyPair.privateKey; publicKey = keyPair.publicKey; await Promise.all([ writeFile( privateKeyPath, privateKey.export({ format: 'pem', type: 'sec1' }) ), writeFile( publicKeyPath, publicKey.export({ format: 'pem', type: 'spki' }) ) ]); console.log('[JWT] Keypair generated successfully.'); } else { console.log('[JWT] Using existing keypair'); const loadedPrivateKey = await readFile(privateKeyPath, 'utf8'); const loadedPublicKey = await readFile(publicKeyPath, 'utf8'); privateKey = createPrivateKey(loadedPrivateKey); publicKey = createPublicKey(loadedPublicKey); } fingerprint = createHash('sha256') .update(publicKey.export({ format: 'pem', type: 'spki' })) .digest('hex'); } /** * @type {import('jsonwebtoken').JwtOptions} */ const jwtOptions = { algorithm: 'ES512' }; /** * * @param data {JwtData} * @returns {Promise} */ export async function generateJwtToken(data) { if (!privateKey) { throw new Error( 'JWT private key is not initialized. Please call initJwt() first.' ); } return new Promise((resolve, reject) => { jwt.sign(data, privateKey, jwtOptions, (err, token) => { if (err) { console.error('[JWT] Error generating token:', err); return reject(err); } resolve(token); }); }); } export async function validateJwtToken(token) { if (!publicKey) { throw new Error( 'JWT public key is not initialized. Please call initJwt() first.' ); } return new Promise((resolve, reject) => { jwt.verify(token, publicKey, jwtOptions, (err, decoded) => { if (err) { console.error('[JWT] Token validation failed:', err); return reject(err); } resolve(decoded); }); }); } export class JwtData { sub; type; iat = Math.floor(Date.now() / 1000); }