import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import { getDb } from '../config/database.js'; import redis from '../config/redis.js'; import { log } from '../config/logger.js'; if (!process.env.JWT_SECRET) { log.auth.error('FATAL: JWT_SECRET environment variable is not set.'); process.exit(1); } const JWT_SECRET = process.env.JWT_SECRET; export async function authenticateToken(req, res, next) { const authHeader = req.headers.authorization; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Authentication required' }); } try { const decoded = jwt.verify(token, JWT_SECRET); // Check JWT blacklist in DragonflyDB (revoked tokens via logout) if (decoded.jti) { try { const revoked = await redis.get(`blacklist:${decoded.jti}`); if (revoked) { return res.status(401).json({ error: 'Token has been revoked' }); } } catch (redisErr) { // Graceful degradation: if Redis is unavailable, allow the request log.auth.warn(`Redis blacklist check skipped: ${redisErr.message}`); } } const db = getDb(); const user = await db.get('SELECT id, name, display_name, email, role, theme, language, avatar_color, avatar_image, email_verified FROM users WHERE id = ?', [decoded.userId]); if (!user) { return res.status(401).json({ error: 'User not found' }); } req.user = user; next(); } catch (err) { return res.status(403).json({ error: 'Invalid token' }); } } export function requireAdmin(req, res, next) { if (req.user.role !== 'admin') { return res.status(403).json({ error: 'Admin rights required' }); } next(); } export function generateToken(userId) { const jti = uuidv4(); return jwt.sign({ userId, jti }, JWT_SECRET, { expiresIn: '7d' }); } /** * Build the public base URL for the application. * Prefers APP_URL env var. Falls back to X-Forwarded-Proto + Host header * so that links are correct behind a TLS-terminating reverse proxy. */ export function getBaseUrl(req) { if (process.env.APP_URL) return process.env.APP_URL.replace(/\/+$/, ''); const proto = req.get('x-forwarded-proto')?.split(',')[0]?.trim() || req.protocol; return `${proto}://${req.get('host')}`; }