import jwt from 'jsonwebtoken'; import fs from 'fs'; import path from 'path'; import os from 'os'; // JWT keys location - can be configured via environment variable // In production, prefer using a secret management service (Vault, AWS Secrets Manager, etc.) const secretFilePath = import.meta.env.JWT_KEYS_PATH || path.join( os.homedir(), '.config', 'api_jwt_keys.json' ); // Warn if using default location in production if (!import.meta.env.JWT_KEYS_PATH && !import.meta.env.DEV) { console.warn( '[SECURITY WARNING] JWT_KEYS_PATH not set. Using default location ~/.config/api_jwt_keys.json. ' + 'Consider using a secret management service in production.' ); } interface JwtKeyFile { keys: Record; } export interface JwtPayload { sub?: string; exp?: number; iat?: number; roles?: string[]; [key: string]: unknown; } // Load and parse keys JSON once at startup let keyFileData: JwtKeyFile; try { keyFileData = JSON.parse(fs.readFileSync(secretFilePath, 'utf-8')); } catch (err) { console.error(`[CRITICAL] Failed to load JWT keys from ${secretFilePath}:`, (err as Error).message); throw new Error('JWT keys file not found or invalid. Set JWT_KEYS_PATH environment variable.'); } export function verifyToken(token: string | null | undefined): JwtPayload | null { if (!token) { return null; } try { const decoded = jwt.decode(token, { complete: true }); if (!decoded?.header?.kid) { throw new Error('No kid in token header'); } const kid = decoded.header.kid; const key = keyFileData.keys[kid]; if (!key) { throw new Error(`Unknown kid: ${kid}`); } // Verify using the correct key and HS256 algo const payload = jwt.verify(token, key, { algorithms: ['HS256'] }) as JwtPayload; return payload; } catch (error) { console.error('JWT verification failed:', (error as Error).message); return null; } }