import jwt from 'jsonwebtoken'; import { userDb, appConfigDb } from '../database/db.js'; import { IS_PLATFORM, DISABLE_LOCAL_AUTH } from '../constants/config.js'; // Use env var if set, otherwise auto-generate a unique secret per installation const JWT_SECRET = process.env.JWT_SECRET || appConfigDb.getOrCreateJwtSecret(); // Optional API key middleware const validateApiKey = (req, res, next) => { // Skip API key validation if not configured if (!process.env.API_KEY) { return next(); } const apiKey = req.headers['x-api-key']; if (apiKey !== process.env.API_KEY) { return res.status(401).json({ error: 'Invalid API key' }); } next(); }; function extractDashboardRefererToken(req) { const refererHeader = req.headers.referer || req.headers.referrer; if (!refererHeader || Array.isArray(refererHeader)) { return null; } try { const refererUrl = new URL( refererHeader, `${req.protocol}://${req.get('host')}`, ); if (!refererUrl.pathname.startsWith('/memory-dashboard')) { return null; } return refererUrl.searchParams.get('token'); } catch { return null; } } // JWT authentication middleware const authenticateToken = async (req, res, next) => { // Platform mode: use single database user if (IS_PLATFORM || DISABLE_LOCAL_AUTH) { try { const user = userDb.getFirstUser(); if (!user) { return res.status(500).json({ error: 'No user found in database (restart server after DB init)' }); } req.user = user; return next(); } catch (error) { console.error('Auth bypass mode error:', error); return res.status(500).json({ error: 'Failed to fetch user' }); } } // Normal OSS JWT validation const authHeader = req.headers['authorization']; let token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN // Also check query param for SSE endpoints (EventSource can't set headers) if (!token && req.query.token) { token = req.query.token; } // Memory dashboard static assets inherit the iframe document URL as Referer, // but do not inherit the query string onto app.js/app.css requests. if (!token) { token = extractDashboardRefererToken(req); } if (!token) { return res.status(401).json({ error: 'Access denied. No token provided.' }); } try { const decoded = jwt.verify(token, JWT_SECRET); // Verify user still exists and is active const user = userDb.getUserById(decoded.userId); if (!user) { return res.status(401).json({ error: 'Invalid token. User not found.' }); } // Auto-refresh: if token is past halfway through its lifetime, issue a new one if (decoded.exp && decoded.iat) { const now = Math.floor(Date.now() / 1000); const halfLife = (decoded.exp - decoded.iat) / 2; if (now > decoded.iat + halfLife) { const newToken = generateToken(user); res.setHeader('X-Refreshed-Token', newToken); } } req.user = user; next(); } catch (error) { console.error('Token verification error:', error); return res.status(403).json({ error: 'Invalid token' }); } }; // Generate JWT token const generateToken = (user) => { return jwt.sign( { userId: user.id, username: user.username }, JWT_SECRET, { expiresIn: '7d' } ); }; // WebSocket authentication function const authenticateWebSocket = (token) => { // Platform mode: bypass token validation, return first user if (IS_PLATFORM || DISABLE_LOCAL_AUTH) { try { const user = userDb.getFirstUser(); if (user) { return { id: user.id, userId: user.id, username: user.username }; } return null; } catch (error) { console.error('Platform mode WebSocket error:', error); return null; } } // Normal OSS JWT validation if (!token) { return null; } try { const decoded = jwt.verify(token, JWT_SECRET); // Verify user actually exists in database (matches REST authenticateToken behavior) const user = userDb.getUserById(decoded.userId); if (!user) { return null; } return { userId: user.id, username: user.username }; } catch (error) { console.error('WebSocket token verification error:', error); return null; } }; export { validateApiKey, authenticateToken, generateToken, authenticateWebSocket, JWT_SECRET };