🔒 Update JWT utility functions for improved token handling
This commit is contained in:
136
code/websites/pokedex.online/server/utils/jwt-utils.js
Normal file
136
code/websites/pokedex.online/server/utils/jwt-utils.js
Normal file
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user