Skip to content

Commit e0480df

Browse files
authored
feat: Upgrade to express 5.0.1 (parse-community#9530)
BREAKING CHANGE: This upgrades the internally used Express framework from version 4 to 5, which may be a breaking change. If Parse Server is set up to be mounted on an Express application, we recommend to also use version 5 of the Express framework to avoid any compatibility issues. Note that even if there are no issues after upgrading, future releases of Parse Server may introduce issues if Parse Server internally relies on Express 5-specific features which are unsupported by the Express version on which it is mounted. See the Express [migration guide](https://expressjs.com/en/guide/migrating-5.html) and [release announcement](https://expressjs.com/2024/10/15/v5-release.html#breaking-changes) for more info.
1 parent cc8dad8 commit e0480df

26 files changed

+997
-403
lines changed

package-lock.json

+924-341
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@
2828
"@parse/fs-files-adapter": "3.0.0",
2929
"@parse/push-adapter": "6.10.0",
3030
"bcryptjs": "2.4.3",
31-
"body-parser": "1.20.3",
3231
"commander": "13.0.0",
3332
"cors": "2.8.5",
3433
"deepcopy": "2.1.0",
35-
"express": "4.21.2",
34+
"express": "5.0.1",
3635
"express-rate-limit": "7.5.0",
3736
"follow-redirects": "1.15.9",
3837
"graphql": "16.9.0",
@@ -58,6 +57,7 @@
5857
"punycode": "2.3.1",
5958
"rate-limit-redis": "4.2.0",
6059
"redis": "4.7.0",
60+
"router": "2.0.0",
6161
"semver": "7.7.1",
6262
"subscriptions-transport-ws": "0.11.0",
6363
"tv4": "1.3.0",

spec/HTTPRequest.spec.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22

33
const httpRequest = require('../lib/request'),
44
HTTPResponse = require('../lib/request').HTTPResponse,
5-
bodyParser = require('body-parser'),
65
express = require('express');
76

87
const port = 13371;
98
const httpRequestServer = `http://localhost:${port}`;
109

1110
function startServer(done) {
1211
const app = express();
13-
app.use(bodyParser.json({ type: '*/*' }));
12+
app.use(express.json({ type: '*/*' }));
1413
app.get('/hello', function (req, res) {
1514
res.json({ response: 'OK' });
1615
});

spec/ParseHooks.spec.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const request = require('../lib/request');
44
const triggers = require('../lib/triggers');
55
const HooksController = require('../lib/Controllers/HooksController').default;
66
const express = require('express');
7-
const bodyParser = require('body-parser');
87
const auth = require('../lib/Auth');
98
const Config = require('../lib/Config');
109

@@ -17,7 +16,7 @@ describe('Hooks', () => {
1716
beforeEach(done => {
1817
if (!app) {
1918
app = express();
20-
app.use(bodyParser.json({ type: '*/*' }));
19+
app.use(express.json({ type: '*/*' }));
2120
server = app.listen(port, undefined, done);
2221
} else {
2322
done();

spec/vulnerabilities.spec.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,10 @@ describe('Vulnerabilities', () => {
250250

251251
it_id('e8b5f1e1-8326-4c70-b5f4-1e8678dfff8d')(it)('denies creating a hook with polluted data', async () => {
252252
const express = require('express');
253-
const bodyParser = require('body-parser');
254253
const port = 34567;
255254
const hookServerURL = 'http://localhost:' + port;
256255
const app = express();
257-
app.use(bodyParser.json({ type: '*/*' }));
256+
app.use(express.json({ type: '*/*' }));
258257
const server = await new Promise(resolve => {
259258
const res = app.listen(port, undefined, () => resolve(res));
260259
});

src/Controllers/AnalyticsController.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export class AnalyticsController extends AdaptableController {
55
appOpened(req) {
66
return Promise.resolve()
77
.then(() => {
8-
return this.adapter.appOpened(req.body, req);
8+
return this.adapter.appOpened(req.body || {}, req);
99
})
1010
.then(response => {
1111
return { response: response || {} };
@@ -18,7 +18,7 @@ export class AnalyticsController extends AdaptableController {
1818
trackEvent(req) {
1919
return Promise.resolve()
2020
.then(() => {
21-
return this.adapter.trackEvent(req.params.eventName, req.body, req);
21+
return this.adapter.trackEvent(req.params.eventName, req.body || {}, req);
2222
})
2323
.then(response => {
2424
return { response: response || {} };

src/ParseServer.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// ParseServer - open-source compatible API Server for Parse apps
22

33
var batch = require('./batch'),
4-
bodyParser = require('body-parser'),
54
express = require('express'),
65
middlewares = require('./middlewares'),
76
Parse = require('parse/node').Parse,
@@ -253,6 +252,7 @@ class ParseServer {
253252
var api = express();
254253
//api.use("/apps", express.static(__dirname + "/public"));
255254
api.use(middlewares.allowCrossDomain(appId));
255+
api.use(middlewares.allowDoubleForwardSlash);
256256
// File handling needs to be before default middlewares are applied
257257
api.use(
258258
'/',
@@ -273,15 +273,16 @@ class ParseServer {
273273

274274
api.use(
275275
'/',
276-
bodyParser.urlencoded({ extended: false }),
276+
express.urlencoded({ extended: false }),
277277
pages.enableRouter
278278
? new PagesRouter(pages).expressRouter()
279279
: new PublicAPIRouter().expressRouter()
280280
);
281281

282-
api.use(bodyParser.json({ type: '*/*', limit: maxUploadSize }));
282+
api.use(express.json({ type: '*/*', limit: maxUploadSize }));
283283
api.use(middlewares.allowMethodOverride);
284284
api.use(middlewares.handleParseHeaders);
285+
api.set('query parser', 'extended');
285286
const routes = Array.isArray(rateLimit) ? rateLimit : [rateLimit];
286287
for (const route of routes) {
287288
middlewares.addRateLimit(route, options);

src/PromiseRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Parse from 'parse/node';
99
import express from 'express';
1010
import log from './logger';
1111
import { inspect } from 'util';
12-
const Layer = require('express/lib/router/layer');
12+
const Layer = require('router/lib/layer');
1313

1414
function validateParameter(key, value) {
1515
if (key == 'className') {

src/Routers/AggregateRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import UsersRouter from './UsersRouter';
66

77
export class AggregateRouter extends ClassesRouter {
88
handleFind(req) {
9-
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
9+
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
1010
const options = {};
1111
if (body.distinct) {
1212
options.distinct = String(body.distinct);

src/Routers/AudiencesRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export class AudiencesRouter extends ClassesRouter {
88
}
99

1010
handleFind(req) {
11-
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
11+
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
1212
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
1313

1414
return rest

src/Routers/ClassesRouter.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ClassesRouter extends PromiseRouter {
1919
}
2020

2121
handleFind(req) {
22-
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
22+
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
2323
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
2424
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
2525
// Silently replace the limit on the query with the max configured
@@ -48,7 +48,7 @@ export class ClassesRouter extends PromiseRouter {
4848

4949
// Returns a promise for a {response} object.
5050
handleGet(req) {
51-
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
51+
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
5252
const options = {};
5353

5454
for (const key of Object.keys(body)) {
@@ -117,7 +117,7 @@ export class ClassesRouter extends PromiseRouter {
117117
req.config,
118118
req.auth,
119119
this.className(req),
120-
req.body,
120+
req.body || {},
121121
req.info.clientSDK,
122122
req.info.context
123123
);
@@ -130,7 +130,7 @@ export class ClassesRouter extends PromiseRouter {
130130
req.auth,
131131
this.className(req),
132132
where,
133-
req.body,
133+
req.body || {},
134134
req.info.clientSDK,
135135
req.info.context
136136
);

src/Routers/CloudCodeRouter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class CloudCodeRouter extends PromiseRouter {
7777
}
7878

7979
static createJob(req) {
80-
const { job_schedule } = req.body;
80+
const { job_schedule } = req.body || {};
8181
validateJobSchedule(req.config, job_schedule);
8282
return rest.create(
8383
req.config,
@@ -91,7 +91,7 @@ export class CloudCodeRouter extends PromiseRouter {
9191

9292
static editJob(req) {
9393
const { objectId } = req.params;
94-
const { job_schedule } = req.body;
94+
const { job_schedule } = req.body || {};
9595
validateJobSchedule(req.config, job_schedule);
9696
return rest
9797
.update(

src/Routers/FilesRouter.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import express from 'express';
2-
import BodyParser from 'body-parser';
32
import * as Middlewares from '../middlewares';
43
import Parse from 'parse/node';
54
import Config from '../Config';
@@ -45,7 +44,7 @@ export class FilesRouter {
4544

4645
router.post(
4746
'/files/:filename',
48-
BodyParser.raw({
47+
express.raw({
4948
type: () => {
5049
return true;
5150
},

src/Routers/FunctionsRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class FunctionsRouter extends PromiseRouter {
5858
}
5959

6060
static handleCloudJob(req) {
61-
const jobName = req.params.jobName || req.body.jobName;
61+
const jobName = req.params.jobName || req.body?.jobName;
6262
const applicationId = req.config.applicationId;
6363
const jobHandler = jobStatusHandler(req.config);
6464
const jobFunction = triggers.getJob(jobName, applicationId);

src/Routers/GlobalConfigRouter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export class GlobalConfigRouter extends PromiseRouter {
4646
"read-only masterKey isn't allowed to update the config."
4747
);
4848
}
49-
const params = req.body.params;
50-
const masterKeyOnly = req.body.masterKeyOnly || {};
49+
const params = req.body.params || {};
50+
const masterKeyOnly = req.body?.masterKeyOnly || {};
5151
// Transform in dot notation to make sure it works
5252
const update = Object.keys(params).reduce((acc, key) => {
5353
acc[`params.${key}`] = params[key];

src/Routers/GraphQLRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class GraphQLRouter extends PromiseRouter {
1919
"read-only masterKey isn't allowed to update the GraphQL config."
2020
);
2121
}
22-
const data = await req.config.parseGraphQLController.updateGraphQLConfig(req.body.params);
22+
const data = await req.config.parseGraphQLController.updateGraphQLConfig(req.body?.params || {});
2323
return {
2424
response: data,
2525
};

src/Routers/HooksRouter.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class HooksRouter extends PromiseRouter {
1212
}
1313

1414
handlePost(req) {
15-
return this.createHook(req.body, req.config);
15+
return this.createHook(req.body || {}, req.config);
1616
}
1717

1818
handleGetFunctions(req) {
@@ -66,11 +66,11 @@ export class HooksRouter extends PromiseRouter {
6666

6767
handleUpdate(req) {
6868
var hook;
69-
if (req.params.functionName && req.body.url) {
69+
if (req.params.functionName && req.body?.url) {
7070
hook = {};
7171
hook.functionName = req.params.functionName;
7272
hook.url = req.body.url;
73-
} else if (req.params.className && req.params.triggerName && req.body.url) {
73+
} else if (req.params.className && req.params.triggerName && req.body?.url) {
7474
hook = {};
7575
hook.className = req.params.className;
7676
hook.triggerName = req.params.triggerName;
@@ -82,7 +82,7 @@ export class HooksRouter extends PromiseRouter {
8282
}
8383

8484
handlePut(req) {
85-
var body = req.body;
85+
var body = req.body || {};
8686
if (body.__op == 'Delete') {
8787
return this.handleDelete(req);
8888
} else {

src/Routers/IAPValidationRouter.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ function getFileForProductIdentifier(productIdentifier, req) {
6868

6969
export class IAPValidationRouter extends PromiseRouter {
7070
handleRequest(req) {
71-
let receipt = req.body.receipt;
72-
const productIdentifier = req.body.productIdentifier;
71+
let receipt = req.body?.receipt;
72+
const productIdentifier = req.body?.productIdentifier;
7373

7474
if (!receipt || !productIdentifier) {
7575
// TODO: Error, malformed request
@@ -84,7 +84,7 @@ export class IAPValidationRouter extends PromiseRouter {
8484
}
8585
}
8686

87-
if (process.env.TESTING == '1' && req.body.bypassAppStoreValidation) {
87+
if (process.env.TESTING == '1' && req.body?.bypassAppStoreValidation) {
8888
return getFileForProductIdentifier(productIdentifier, req);
8989
}
9090

src/Routers/InstallationsRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class InstallationsRouter extends ClassesRouter {
1010
}
1111

1212
handleFind(req) {
13-
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
13+
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
1414
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
1515
return rest
1616
.find(

src/Routers/PagesRouter.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ export class PagesRouter extends PromiseRouter {
107107

108108
resendVerificationEmail(req) {
109109
const config = req.config;
110-
const username = req.body.username;
111-
const token = req.body.token;
110+
const username = req.body?.username;
111+
const token = req.body?.token;
112112

113113
if (!config) {
114114
this.invalidRequest();
@@ -178,7 +178,7 @@ export class PagesRouter extends PromiseRouter {
178178
this.invalidRequest();
179179
}
180180

181-
const { new_password, token: rawToken } = req.body;
181+
const { new_password, token: rawToken } = req.body || {};
182182
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
183183

184184
if ((!token || !new_password) && req.xhr === false) {
@@ -320,7 +320,7 @@ export class PagesRouter extends PromiseRouter {
320320
*/
321321
staticRoute(req) {
322322
// Get requested path
323-
const relativePath = req.params[0];
323+
const relativePath = req.params['resource'][0];
324324

325325
// Resolve requested path to absolute path
326326
const absolutePath = path.resolve(this.pagesPath, relativePath);
@@ -716,7 +716,7 @@ export class PagesRouter extends PromiseRouter {
716716
mountStaticRoute() {
717717
this.route(
718718
'GET',
719-
`/${this.pagesEndpoint}/(*)?`,
719+
`/${this.pagesEndpoint}/*resource`,
720720
req => {
721721
this.setConfig(req, true);
722722
},

src/Routers/PublicAPIRouter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class PublicAPIRouter extends PromiseRouter {
5252
}
5353

5454
resendVerificationEmail(req) {
55-
const username = req.body.username;
55+
const username = req.body?.username;
5656
const appId = req.params.appId;
5757
const config = Config.get(appId);
5858

@@ -162,7 +162,7 @@ export class PublicAPIRouter extends PromiseRouter {
162162
return this.missingPublicServerURL();
163163
}
164164

165-
const { new_password, token: rawToken } = req.body;
165+
const { new_password, token: rawToken } = req.body || {};
166166
const token = rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken;
167167

168168
if ((!token || !new_password) && req.xhr === false) {

src/Routers/PushRouter.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class PushRouter extends PromiseRouter {
2626
});
2727
let pushStatusId;
2828
pushController
29-
.sendPush(req.body, where, req.config, req.auth, objectId => {
29+
.sendPush(req.body || {}, where, req.config, req.auth, objectId => {
3030
pushStatusId = objectId;
3131
resolve({
3232
headers: {

0 commit comments

Comments
 (0)