// Heavily based on: // https://github.com/TomDoesTech/REST-API-Tutorial-Updated/blob/7b5f040e1acd94d267df585516b33ee7e3b75f70/src/utils/jwt.utils.ts import jwt from 'jsonwebtoken'; import { DEFAULT_TOKEN_LIFETIME } from '../schemas/authSchema'; import * as env from './env'; export type JwtDecoded = { sub: number; role: number; iat: number; exp: number; }; export type JwtStatus = { valid: boolean; expired: boolean; decoded: JwtDecoded | null; // null if decoding failed }; /** * Sign a JWT containing sub (number), role (number, 0/1), iat/exp (unix timestamp) claims. * * @param {Object} object The object * @param {('accessTokenPrivateKey'|'refreshTokenPrivateKey')} keyName The key name * @param {} options?:jwt.SignOptions|undefined The sign options undefined * @return {string} JWT string */ export function signJwt( object: Object, keyName: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey', options?: jwt.SignOptions | undefined ): string { // refresh tokens aren't (yet) supported // const signingKey = Buffer.from( // process.env[keyName]!, // 'base64' // ).toString('utf8'); const secret: string = env.getString(keyName, true)!; // Use the default expiration time of 24 hours. if (options === undefined) options = { expiresIn: DEFAULT_TOKEN_LIFETIME }; return jwt.sign(object, secret, { ...options, // (options && options)? // algorithm: 'RS256', // requires a valid private key, not a secret }); } /** * Verify a JWT against one of the keys. * Returns JwtStatus, which contains fields for checking validity, expiry and decoded subject claim (id). * * @param {string} token The token * @param {('accessTokenPublicKey'|'refreshTokenPublicKey')} keyName The key name * @return {JwtStatus} JWT status. */ export function verifyJwt( token: string, keyName: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey' ): JwtStatus { // refresh tokens aren't (yet) supported // const publicKey = Buffer.from( // process.env[keyName]!, // 'base64' // ).toString('utf8'); const secret: string = env.getString(keyName, true)!; try { const decoded: jwt.JwtPayload | string = jwt.verify(token, secret); // TODO: Can this be done better, smarter? return { valid: true, expired: false, decoded: decoded as unknown as JwtDecoded }; } catch (e: any) { console.error('JWT verify error:', e); return { valid: e.message !== 'jwt malformed', expired: e.message === 'jwt expired', decoded: null }; } }