| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- 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
- };
|