/** * JWT Authentication Utilities * * Provides functions for creating, validating, and decoding JWT tokens * Used by both backend OAuth proxy and frontend storage/validation */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Try to use native crypto, fallback to import let jwt; try { jwt = await import('jsonwebtoken'); } catch (err) { console.warn('jsonwebtoken not installed, using mock implementation'); jwt = createMockJWT(); } function createMockJWT() { return { sign: (payload, secret, options) => { // Mock JWT: base64(header).base64(payload).base64(signature) const header = Buffer.from( JSON.stringify({ alg: 'HS256', typ: 'JWT' }) ).toString('base64'); const body = Buffer.from(JSON.stringify(payload)).toString('base64'); const signature = Buffer.from('mock-signature').toString('base64'); return `${header}.${body}.${signature}`; }, verify: (token, secret) => { try { const parts = token.split('.'); if (parts.length !== 3) throw new Error('Invalid token format'); const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString()); // Check expiration if (payload.exp && Date.now() >= payload.exp * 1000) { throw new Error('Token expired'); } return payload; } catch (err) { throw new Error(`Invalid token: ${err.message}`); } }, decode: token => { const parts = token.split('.'); if (parts.length !== 3) return null; return JSON.parse(Buffer.from(parts[1], 'base64').toString()); } }; } /** * Create a JWT token for admin access * @param {Object} payload - Token payload (user data, permissions, etc.) * @param {string} secret - Secret key for signing * @param {number} expiresIn - Expiration time in seconds (default: 7 days) * @returns {string} JWT token */ export function createToken(payload, secret, expiresIn = 7 * 24 * 60 * 60) { const now = Math.floor(Date.now() / 1000); return jwt.sign( { ...payload, iat: now, exp: now + expiresIn }, secret, { algorithm: 'HS256' } ); } /** * Verify and decode a JWT token * @param {string} token - JWT token to verify * @param {string} secret - Secret key for verification * @returns {Object} Decoded token payload * @throws {Error} If token is invalid or expired */ export function verifyToken(token, secret) { return jwt.verify(token, secret); } /** * Decode a JWT token without verification (use with caution) * @param {string} token - JWT token to decode * @returns {Object|null} Decoded token payload or null if invalid */ export function decodeToken(token) { return jwt.decode(token); } /** * Check if a token is expired * @param {string} token - JWT token * @returns {boolean} True if token is expired */ export function isTokenExpired(token) { try { const decoded = jwt.decode(token); if (!decoded || !decoded.exp) return true; return Date.now() >= decoded.exp * 1000; } catch { return true; } } /** * Get remaining time before token expiration * @param {string} token - JWT token * @returns {number} Milliseconds until expiration, or 0 if expired */ export function getTokenExpiresIn(token) { try { const decoded = jwt.decode(token); if (!decoded || !decoded.exp) return 0; const remaining = decoded.exp * 1000 - Date.now(); return Math.max(0, remaining); } catch { return 0; } } /** * Generate a random secret for testing (NOT for production) * @returns {string} Random secret */ export function generateSecret() { return Buffer.from(Math.random().toString()).toString('base64'); }