From 3434a361d95d3d9768043fc688ab50d5d1e90805 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 28 Jan 2026 22:46:02 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Update=20JWT=20utility=20functio?= =?UTF-8?q?ns=20for=20improved=20token=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokedex.online/server/utils/jwt-utils.js | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 code/websites/pokedex.online/server/utils/jwt-utils.js diff --git a/code/websites/pokedex.online/server/utils/jwt-utils.js b/code/websites/pokedex.online/server/utils/jwt-utils.js new file mode 100644 index 0000000..416895a --- /dev/null +++ b/code/websites/pokedex.online/server/utils/jwt-utils.js @@ -0,0 +1,136 @@ +/** + * 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'); +}