auth.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import jwt from 'jsonwebtoken';
  2. import { userDb, appConfigDb } from '../database/db.js';
  3. import { IS_PLATFORM, DISABLE_LOCAL_AUTH } from '../constants/config.js';
  4. // Use env var if set, otherwise auto-generate a unique secret per installation
  5. const JWT_SECRET = process.env.JWT_SECRET || appConfigDb.getOrCreateJwtSecret();
  6. // Optional API key middleware
  7. const validateApiKey = (req, res, next) => {
  8. // Skip API key validation if not configured
  9. if (!process.env.API_KEY) {
  10. return next();
  11. }
  12. const apiKey = req.headers['x-api-key'];
  13. if (apiKey !== process.env.API_KEY) {
  14. return res.status(401).json({ error: 'Invalid API key' });
  15. }
  16. next();
  17. };
  18. function extractDashboardRefererToken(req) {
  19. const refererHeader = req.headers.referer || req.headers.referrer;
  20. if (!refererHeader || Array.isArray(refererHeader)) {
  21. return null;
  22. }
  23. try {
  24. const refererUrl = new URL(
  25. refererHeader,
  26. `${req.protocol}://${req.get('host')}`,
  27. );
  28. if (!refererUrl.pathname.startsWith('/memory-dashboard')) {
  29. return null;
  30. }
  31. return refererUrl.searchParams.get('token');
  32. } catch {
  33. return null;
  34. }
  35. }
  36. // JWT authentication middleware
  37. const authenticateToken = async (req, res, next) => {
  38. // Platform mode: use single database user
  39. if (IS_PLATFORM || DISABLE_LOCAL_AUTH) {
  40. try {
  41. const user = userDb.getFirstUser();
  42. if (!user) {
  43. return res.status(500).json({ error: 'No user found in database (restart server after DB init)' });
  44. }
  45. req.user = user;
  46. return next();
  47. } catch (error) {
  48. console.error('Auth bypass mode error:', error);
  49. return res.status(500).json({ error: 'Failed to fetch user' });
  50. }
  51. }
  52. // Normal OSS JWT validation
  53. const authHeader = req.headers['authorization'];
  54. let token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
  55. // Also check query param for SSE endpoints (EventSource can't set headers)
  56. if (!token && req.query.token) {
  57. token = req.query.token;
  58. }
  59. // Memory dashboard static assets inherit the iframe document URL as Referer,
  60. // but do not inherit the query string onto app.js/app.css requests.
  61. if (!token) {
  62. token = extractDashboardRefererToken(req);
  63. }
  64. if (!token) {
  65. return res.status(401).json({ error: 'Access denied. No token provided.' });
  66. }
  67. try {
  68. const decoded = jwt.verify(token, JWT_SECRET);
  69. // Verify user still exists and is active
  70. const user = userDb.getUserById(decoded.userId);
  71. if (!user) {
  72. return res.status(401).json({ error: 'Invalid token. User not found.' });
  73. }
  74. // Auto-refresh: if token is past halfway through its lifetime, issue a new one
  75. if (decoded.exp && decoded.iat) {
  76. const now = Math.floor(Date.now() / 1000);
  77. const halfLife = (decoded.exp - decoded.iat) / 2;
  78. if (now > decoded.iat + halfLife) {
  79. const newToken = generateToken(user);
  80. res.setHeader('X-Refreshed-Token', newToken);
  81. }
  82. }
  83. req.user = user;
  84. next();
  85. } catch (error) {
  86. console.error('Token verification error:', error);
  87. return res.status(403).json({ error: 'Invalid token' });
  88. }
  89. };
  90. // Generate JWT token
  91. const generateToken = (user) => {
  92. return jwt.sign(
  93. {
  94. userId: user.id,
  95. username: user.username
  96. },
  97. JWT_SECRET,
  98. { expiresIn: '7d' }
  99. );
  100. };
  101. // WebSocket authentication function
  102. const authenticateWebSocket = (token) => {
  103. // Platform mode: bypass token validation, return first user
  104. if (IS_PLATFORM || DISABLE_LOCAL_AUTH) {
  105. try {
  106. const user = userDb.getFirstUser();
  107. if (user) {
  108. return { id: user.id, userId: user.id, username: user.username };
  109. }
  110. return null;
  111. } catch (error) {
  112. console.error('Platform mode WebSocket error:', error);
  113. return null;
  114. }
  115. }
  116. // Normal OSS JWT validation
  117. if (!token) {
  118. return null;
  119. }
  120. try {
  121. const decoded = jwt.verify(token, JWT_SECRET);
  122. // Verify user actually exists in database (matches REST authenticateToken behavior)
  123. const user = userDb.getUserById(decoded.userId);
  124. if (!user) {
  125. return null;
  126. }
  127. return { userId: user.id, username: user.username };
  128. } catch (error) {
  129. console.error('WebSocket token verification error:', error);
  130. return null;
  131. }
  132. };
  133. export {
  134. validateApiKey,
  135. authenticateToken,
  136. generateToken,
  137. authenticateWebSocket,
  138. JWT_SECRET
  139. };