📦 chore: Bump Express.js to v5 (#10671)

* chore: update express to version 5.1.0 in package.json

* chore: update express-rate-limit to version 8.2.1 in package.json and package-lock.json

* fix: Enhance server startup error handling in experimental and index files

* Added error handling for server startup in both experimental.js and index.js to log errors and exit the process if the server fails to start.
* Updated comments in openidStrategy.js to clarify the purpose of the CustomOpenIDStrategy class and its relation to Express version changes.

* chore: Implement rate limiting for all POST routes excluding /speech, required for express v5

* Added middleware to apply IP and user rate limiters to all POST requests, ensuring that the /speech route remains unaffected.
* Enhanced code clarity with comments explaining the new rate limiting logic.

* chore: Enable writable req.query for mongoSanitize compatibility in Express 5

* chore: Ensure req.body exists in multiple middleware and route files for Express 5 compatibility
This commit is contained in:
Danny Avila 2025-11-25 17:01:19 -05:00
parent 3c54740074
commit 19b78ecd81
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
11 changed files with 433 additions and 422 deletions

View file

@ -58,9 +58,9 @@
"dedent": "^1.5.3",
"dotenv": "^16.0.3",
"eventsource": "^3.0.2",
"express": "^4.21.2",
"express": "^5.1.0",
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^7.4.1",
"express-rate-limit": "^8.2.1",
"express-session": "^1.18.2",
"express-static-gzip": "^2.2.0",
"file-type": "^18.7.0",

View file

@ -138,6 +138,7 @@ const listAssistantsForAzure = async ({ req, res, version, azureConfig = {}, que
/* The specified model is only necessary to
fetch assistants for the shared instance */
req.body = req.body || {}; // Express 5: req.body is undefined instead of {} when no body parser runs
req.body.model = currentModelTuples[0][0];
promises.push(listAllAssistants({ req, res, version, query }));
}

View file

@ -246,7 +246,22 @@ if (cluster.isMaster) {
app.use(noIndex);
app.use(express.json({ limit: '3mb' }));
app.use(express.urlencoded({ extended: true, limit: '3mb' }));
app.use(handleJsonParseError);
/**
* Express 5 Compatibility: Make req.query writable for mongoSanitize
* In Express 5, req.query is read-only by default, but express-mongo-sanitize needs to modify it
*/
app.use((req, _res, next) => {
Object.defineProperty(req, 'query', {
...Object.getOwnPropertyDescriptor(req, 'query'),
value: req.query,
writable: true,
});
next();
});
app.use(mongoSanitize());
app.use(cors());
app.use(cookieParser());
@ -328,7 +343,12 @@ if (cluster.isMaster) {
});
/** Start listening on shared port (cluster will distribute connections) */
app.listen(port, host, async () => {
app.listen(port, host, async (err) => {
if (err) {
logger.error(`Worker ${process.pid} failed to start server:`, err);
process.exit(1);
}
logger.info(
`Worker ${process.pid} started: Server listening at http://${
host == '0.0.0.0' ? 'localhost' : host

View file

@ -83,6 +83,20 @@ const startServer = async () => {
app.use(express.json({ limit: '3mb' }));
app.use(express.urlencoded({ extended: true, limit: '3mb' }));
app.use(handleJsonParseError);
/**
* Express 5 Compatibility: Make req.query writable for mongoSanitize
* In Express 5, req.query is read-only by default, but express-mongo-sanitize needs to modify it
*/
app.use((req, _res, next) => {
Object.defineProperty(req, 'query', {
...Object.getOwnPropertyDescriptor(req, 'query'),
value: req.query,
writable: true,
});
next();
});
app.use(mongoSanitize());
app.use(cors());
app.use(cookieParser());
@ -161,7 +175,12 @@ const startServer = async () => {
res.send(updatedIndexHtml);
});
app.listen(port, host, async () => {
app.listen(port, host, async (err) => {
if (err) {
logger.error('Failed to start server:', err);
process.exit(1);
}
if (host === '0.0.0.0') {
logger.info(
`Server listening on all interfaces at port ${port}. Use http://localhost:${port} to access it`,

View file

@ -16,6 +16,7 @@ async function abortRun(req, res) {
const conversation = await getConvo(req.user.id, conversationId);
if (conversation?.model) {
req.body = req.body || {}; // Express 5: ensure req.body exists
req.body.model = conversation.model;
}

View file

@ -89,6 +89,7 @@ async function buildEndpointOption(req, res, next) {
: buildFunction[endpointType ?? endpoint];
// TODO: use object params
req.body = req.body || {}; // Express 5: ensure req.body exists
req.body.endpointOption = await builder(endpoint, parsedBody, endpointType);
if (req.body.files && !isAgents) {

View file

@ -154,6 +154,7 @@ router.post('/:assistant_id', async (req, res) => {
router.delete('/:assistant_id/:action_id/:model', async (req, res) => {
try {
const { assistant_id, action_id, model } = req.params;
req.body = req.body || {}; // Express 5: ensure req.body exists
req.body.model = model;
const { openai } = await getOpenAIClient({ req, res });

View file

@ -29,7 +29,20 @@ const initialize = async () => {
router.use('/speech', speech);
const { fileUploadIpLimiter, fileUploadUserLimiter } = createFileLimiters();
router.post('*', fileUploadIpLimiter, fileUploadUserLimiter);
/** Apply rate limiters to all POST routes (excluding /speech which is handled above) */
router.use((req, res, next) => {
if (req.method === 'POST' && !req.path.startsWith('/speech')) {
return fileUploadIpLimiter(req, res, (err) => {
if (err) {
return next(err);
}
return fileUploadUserLimiter(req, res, next);
});
}
next();
});
router.post('/', upload.single('file'));
router.post('/images', upload.single('file'));
router.post('/images/avatar', upload.single('file'));

View file

@ -99,9 +99,14 @@ This violates RFC 7235 and may cause issues with strict OAuth clients. Removing
/** @typedef {Configuration | null} */
let openidConfig = null;
//overload currenturl function because of express version 4 buggy req.host doesn't include port
//More info https://github.com/panva/openid-client/pull/713
/**
* Custom OpenID Strategy
*
* Note: Originally overrode currentUrl() to work around Express 4's req.host not including port.
* With Express 5, req.host now includes the port by default, but we continue to use DOMAIN_SERVER
* for consistency and explicit configuration control.
* More info: https://github.com/panva/openid-client/pull/713
*/
class CustomOpenIDStrategy extends OpenIDStrategy {
currentUrl(req) {
const hostAndProtocol = process.env.DOMAIN_SERVER;