Fix project isolation: Make loadChatHistory respect active project sessions
- Modified loadChatHistory() to check for active project before fetching all sessions - When active project exists, use project.sessions instead of fetching from API - Added detailed console logging to debug session filtering - This prevents ALL sessions from appearing in every project's sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
292
middleware/validation.js
Normal file
292
middleware/validation.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* Validation Middleware
|
||||
*
|
||||
* Provides validation middleware for route parameters and request bodies.
|
||||
*/
|
||||
|
||||
// Session ID validation pattern
|
||||
// 10-64 characters, alphanumeric, underscore, hyphen
|
||||
// Updated to support timestamp-based session IDs like: session-1769029589191-gjqeg0i0i
|
||||
const SESSION_ID_PATTERN = /^[a-zA-Z0-9_-]{10,64}$/;
|
||||
|
||||
// Terminal ID validation pattern
|
||||
const TERMINAL_ID_PATTERN = /^term-[0-9]{13,}-[a-zA-Z0-9]{4,}$/;
|
||||
|
||||
/**
|
||||
* Validate session ID format
|
||||
* @param {string} sessionId - Session ID to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
function validateSessionIdFormat(sessionId) {
|
||||
if (!sessionId) {
|
||||
return false;
|
||||
}
|
||||
return SESSION_ID_PATTERN.test(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate terminal ID format
|
||||
* @param {string} terminalId - Terminal ID to validate
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
function validateTerminalIdFormat(terminalId) {
|
||||
if (!terminalId) {
|
||||
return false;
|
||||
}
|
||||
return TERMINAL_ID_PATTERN.test(terminalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to validate session ID parameter
|
||||
*/
|
||||
function validateSessionId(req, res, next) {
|
||||
const { sessionId } = req.params;
|
||||
|
||||
if (!sessionId) {
|
||||
return res.status(400).json({
|
||||
error: 'Session ID is required',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (!validateSessionIdFormat(sessionId)) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid session ID format',
|
||||
sessionId,
|
||||
expected: '10-64 characters, alphanumeric, underscore, hyphen',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
// Check session exists
|
||||
// Note: We access the global claudeService instance from app.locals
|
||||
const claudeService = req.app.locals.claudeService;
|
||||
if (!claudeService) {
|
||||
console.error('[ValidationError] claudeService not found in app.locals');
|
||||
return res.status(500).json({
|
||||
error: 'Service configuration error',
|
||||
statusCode: 500
|
||||
});
|
||||
}
|
||||
|
||||
const session = claudeService.getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return res.status(404).json({
|
||||
error: 'Session not found',
|
||||
sessionId,
|
||||
hint: 'The session may have been deleted or never existed',
|
||||
statusCode: 404
|
||||
});
|
||||
}
|
||||
|
||||
// Attach session context to request
|
||||
req.sessionContext = session;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to validate terminal ID parameter
|
||||
*/
|
||||
function validateTerminalId(req, res, next) {
|
||||
const { terminalId } = req.params;
|
||||
|
||||
if (!terminalId) {
|
||||
return res.status(400).json({
|
||||
error: 'Terminal ID is required',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (!validateTerminalIdFormat(terminalId)) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid terminal ID format',
|
||||
terminalId,
|
||||
expected: 'Format: term-{timestamp}-{random}',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
// Check terminal exists
|
||||
// Note: We access the global terminalService instance from app.locals
|
||||
const terminalService = req.app.locals.terminalService;
|
||||
if (!terminalService) {
|
||||
console.error('[ValidationError] terminalService not found in app.locals');
|
||||
return res.status(500).json({
|
||||
error: 'Service configuration error',
|
||||
statusCode: 500
|
||||
});
|
||||
}
|
||||
|
||||
const result = terminalService.getTerminal(terminalId);
|
||||
|
||||
if (!result.success) {
|
||||
return res.status(404).json({
|
||||
error: 'Terminal not found',
|
||||
terminalId,
|
||||
hint: 'The terminal may have been closed',
|
||||
statusCode: 404
|
||||
});
|
||||
}
|
||||
|
||||
// Attach terminal context to request
|
||||
req.terminalContext = result.terminal;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to validate command/prompt body
|
||||
*/
|
||||
function validateCommand(req, res, next) {
|
||||
const { command } = req.body;
|
||||
|
||||
if (!command || typeof command !== 'string') {
|
||||
return res.status(400).json({
|
||||
error: 'Command is required and must be a string',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (command.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
error: 'Command cannot be empty',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (command.length > 100000) {
|
||||
return res.status(400).json({
|
||||
error: 'Command too large (max 100KB)',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to validate operations array
|
||||
*/
|
||||
function validateOperations(req, res, next) {
|
||||
const { operations } = req.body;
|
||||
|
||||
if (!operations || !Array.isArray(operations)) {
|
||||
return res.status(400).json({
|
||||
error: 'Operations array is required',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (operations.length === 0) {
|
||||
return res.status(400).json({
|
||||
error: 'Operations array cannot be empty',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (operations.length > 1000) {
|
||||
return res.status(400).json({
|
||||
error: 'Too many operations (max 1000)',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
// Validate each operation
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const op = operations[i];
|
||||
|
||||
if (!op.type) {
|
||||
return res.status(400).json({
|
||||
error: `Operation at index ${i} missing 'type' field`,
|
||||
operation: op,
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
const validTypes = ['read', 'write', 'delete', 'list', 'search'];
|
||||
if (!validTypes.includes(op.type)) {
|
||||
return res.status(400).json({
|
||||
error: `Invalid operation type at index ${i}: ${op.type}`,
|
||||
validTypes,
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to validate response for operations preview
|
||||
*/
|
||||
function validateResponse(req, res, next) {
|
||||
const { response } = req.body;
|
||||
|
||||
if (!response || typeof response !== 'string') {
|
||||
return res.status(400).json({
|
||||
error: 'Response is required and must be a string',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (response.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
error: 'Response cannot be empty',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
if (response.length > 10000000) {
|
||||
return res.status(400).json({
|
||||
error: 'Response too large (max 10MB)',
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handler middleware
|
||||
*/
|
||||
function errorHandler(err, req, res, next) {
|
||||
console.error('[ErrorHandler]', err);
|
||||
|
||||
// Handle validation errors
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
error: 'Validation error',
|
||||
details: err.message,
|
||||
statusCode: 400
|
||||
});
|
||||
}
|
||||
|
||||
// Handle not found errors
|
||||
if (err.name === 'NotFoundError') {
|
||||
return res.status(404).json({
|
||||
error: 'Resource not found',
|
||||
details: err.message,
|
||||
statusCode: 404
|
||||
});
|
||||
}
|
||||
|
||||
// Default error response
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: err.message,
|
||||
statusCode: 500
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateSessionIdFormat,
|
||||
validateTerminalIdFormat,
|
||||
validateSessionId,
|
||||
validateTerminalId,
|
||||
validateCommand,
|
||||
validateOperations,
|
||||
validateResponse,
|
||||
errorHandler
|
||||
};
|
||||
Reference in New Issue
Block a user