mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-12 19:57:34 +00:00
* fix: remove unnecessary websocket.js file and replace its usage directly in `WebSocketContext` * fix: connect() doesn't need to be async * fix: update WebSocket context import to use useWebSocket hook * fix: use `useRef` for WebSocketContext The main issue with using states was, previously the websocket never closed properly on unmount, so multiple connections could be opened. This was because the useEffect cleanup function was closing an old websocket (that was initialized to null) instead of the current one. We could have fixed this by adding `ws` to the useEffect dependency array, but this was unnecessary since `ws` doesn't affect rendering so we shouldn't use a state. * fix: replace `WebSocketContext` default value with null and add type definitions * fix: add type definition for WebSocket URL and remove redundant protocol declaration * fix: Prevent WebSocket reconnection attempts after unmount Right now, when the WebSocketContext component unmounts, there is still a pending reconnection attempt that tries to reconnect the WebSocket after 3 seconds. * refactor: Extract WebSocket URL construction into a separate function * refactor: Centralize platform mode detection using IS_PLATFORM constant; use `token` from Auth context in WebSocket connection * refactor: Use IS_PLATFORM constant for platform detection in authenticatedFetch function (backend) * refactor: move IS_PLATFORM to config file for both frontend and backend The reason we couldn't place it in shared/modelConstants.js is that the frontend uses Vite which requires import.meta.env for environment variables, while the backend uses process.env. Therefore, we created separate config files for the frontend (src/constants/config.ts) and backend (server/constants/config.js). * refactor: update import path for IS_PLATFORM constant to use config file * refactor: replace `messages` with `latestMessage` in WebSocket context and related components Why? Because, messages was only being used to access the latest message in the components it's used in. * refactor: optimize WebSocket connection handling with useCallback and useMemo * refactor: comment out debug log for render count in AppContent component * refactor(backend): update environment variable handling and replace VITE_IS_PLATFORM with IS_PLATFORM constant * refactor: update WebSocket connection effect to depend on token changes for reconnection ---------
117 lines
3.0 KiB
JavaScript
117 lines
3.0 KiB
JavaScript
import jwt from 'jsonwebtoken';
|
|
import { userDb } from '../database/db.js';
|
|
import { IS_PLATFORM } from '../constants/config.js';
|
|
|
|
// Get JWT secret from environment or use default (for development)
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'claude-ui-dev-secret-change-in-production';
|
|
|
|
// 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();
|
|
};
|
|
|
|
// JWT authentication middleware
|
|
const authenticateToken = async (req, res, next) => {
|
|
// Platform mode: use single database user
|
|
if (IS_PLATFORM) {
|
|
try {
|
|
const user = userDb.getFirstUser();
|
|
if (!user) {
|
|
return res.status(500).json({ error: 'Platform mode: No user found in database' });
|
|
}
|
|
req.user = user;
|
|
return next();
|
|
} catch (error) {
|
|
console.error('Platform mode error:', error);
|
|
return res.status(500).json({ error: 'Platform mode: 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;
|
|
}
|
|
|
|
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.' });
|
|
}
|
|
|
|
req.user = user;
|
|
next();
|
|
} catch (error) {
|
|
console.error('Token verification error:', error);
|
|
return res.status(403).json({ error: 'Invalid token' });
|
|
}
|
|
};
|
|
|
|
// Generate JWT token (never expires)
|
|
const generateToken = (user) => {
|
|
return jwt.sign(
|
|
{
|
|
userId: user.id,
|
|
username: user.username
|
|
},
|
|
JWT_SECRET
|
|
// No expiration - token lasts forever
|
|
);
|
|
};
|
|
|
|
// WebSocket authentication function
|
|
const authenticateWebSocket = (token) => {
|
|
// Platform mode: bypass token validation, return first user
|
|
if (IS_PLATFORM) {
|
|
try {
|
|
const user = userDb.getFirstUser();
|
|
if (user) {
|
|
return { 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);
|
|
return decoded;
|
|
} catch (error) {
|
|
console.error('WebSocket token verification error:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export {
|
|
validateApiKey,
|
|
authenticateToken,
|
|
generateToken,
|
|
authenticateWebSocket,
|
|
JWT_SECRET
|
|
}; |