Skip to content

Commit dde1a23

Browse files
committed
include health endpoint
1 parent 16bf989 commit dde1a23

File tree

3 files changed

+96
-73
lines changed

3 files changed

+96
-73
lines changed

spec/RouteAllowList.spec.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,6 @@ describe('routeAllowList', () => {
249249
const request = require('../lib/request');
250250
try {
251251
await request({
252-
headers: {
253-
'X-Parse-Application-Id': 'test',
254-
'X-Parse-REST-API-Key': 'rest',
255-
},
256252
method: 'GET',
257253
url: 'http://localhost:8378/1/health',
258254
});

src/ParseServer.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class ParseServer {
311311
//api.use("/apps", express.static(__dirname + "/public"));
312312
api.use(middlewares.allowCrossDomain(appId));
313313
api.use(middlewares.allowDoubleForwardSlash);
314+
api.use(middlewares.handleParseAuth(appId));
314315
// File handling needs to be before the default JSON body parser because file
315316
// uploads send binary data that should not be parsed as JSON.
316317
api.use(
@@ -320,6 +321,8 @@ class ParseServer {
320321
})
321322
);
322323

324+
api.use('/health', middlewares.enforceRouteAllowList, middlewares.handleParseHealth(options));
325+
323326
api.use(
324327
'/',
325328
express.urlencoded({ extended: false }),
@@ -331,16 +334,6 @@ class ParseServer {
331334
api.use(middlewares.handleParseHeaders);
332335
api.use(middlewares.enforceRouteAllowList);
333336
api.set('query parser', 'extended');
334-
335-
api.use('/health', function (req, res) {
336-
res.status(options.state === 'ok' ? 200 : 503);
337-
if (options.state === 'starting') {
338-
res.set('Retry-After', 1);
339-
}
340-
res.json({
341-
status: options.state,
342-
});
343-
});
344337
const routes = Array.isArray(rateLimit) ? rateLimit : [rateLimit];
345338
for (const route of routes) {
346339
middlewares.addRateLimit(route, options);

src/middlewares.js

Lines changed: 93 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -241,70 +241,22 @@ export async function handleParseHeaders(req, res, next) {
241241
req.config.ip = clientIp;
242242
req.info = info;
243243

244-
const isMaintenance =
245-
req.config.maintenanceKey && info.maintenanceKey === req.config.maintenanceKey;
246-
if (isMaintenance) {
247-
if (checkIp(clientIp, req.config.maintenanceKeyIps || [], req.config.maintenanceKeyIpsStore)) {
248-
req.auth = new auth.Auth({
249-
config: req.config,
250-
installationId: info.installationId,
251-
isMaintenance: true,
252-
});
253-
next();
254-
return;
255-
}
256-
const log = req.config?.loggerController || defaultLogger;
257-
log.error(
258-
`Request using maintenance key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'maintenanceKeyIps'.`
259-
);
260-
}
261-
262-
const masterKey = await req.config.loadMasterKey();
263-
let isMaster = info.masterKey === masterKey;
264-
265-
if (isMaster && !checkIp(clientIp, req.config.masterKeyIps || [], req.config.masterKeyIpsStore)) {
266-
const log = req.config?.loggerController || defaultLogger;
267-
log.error(
268-
`Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.`
269-
);
270-
isMaster = false;
271-
const error = new Error();
272-
error.status = 403;
273-
error.message = `unauthorized`;
274-
throw error;
275-
}
276-
277-
if (isMaster) {
278-
req.auth = new auth.Auth({
244+
// Skip key detection if already resolved by handleParseAuth (header-based).
245+
// Only resolve here for body-based _MasterKey (info.masterKey may come from body).
246+
if (!req.auth || (!req.auth.isMaster && !req.auth.isMaintenance)) {
247+
const resolved = await resolveKeyAuth({
279248
config: req.config,
249+
keyValue: info.masterKey,
250+
maintenanceKeyValue: info.maintenanceKey,
280251
installationId: info.installationId,
281-
isMaster: true,
252+
clientIp,
282253
});
283-
return handleRateLimit(req, res, next);
254+
if (resolved) {
255+
req.auth = resolved;
256+
}
284257
}
285258

286-
var isReadOnlyMaster = info.masterKey === req.config.readOnlyMasterKey;
287-
if (
288-
typeof req.config.readOnlyMasterKey != 'undefined' &&
289-
req.config.readOnlyMasterKey &&
290-
isReadOnlyMaster
291-
) {
292-
if (!checkIp(clientIp, req.config.readOnlyMasterKeyIps || [], req.config.readOnlyMasterKeyIpsStore)) {
293-
const log = req.config?.loggerController || defaultLogger;
294-
log.error(
295-
`Request using read-only master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'readOnlyMasterKeyIps'.`
296-
);
297-
const error = new Error();
298-
error.status = 403;
299-
error.message = 'unauthorized';
300-
throw error;
301-
}
302-
req.auth = new auth.Auth({
303-
config: req.config,
304-
installationId: info.installationId,
305-
isMaster: true,
306-
isReadOnly: true,
307-
});
259+
if (req.auth && (req.auth.isMaster || req.auth.isMaintenance)) {
308260
return handleRateLimit(req, res, next);
309261
}
310262

@@ -491,6 +443,88 @@ export function allowMethodOverride(req, res, next) {
491443
next();
492444
}
493445

446+
async function resolveKeyAuth({ config, keyValue, maintenanceKeyValue, installationId, clientIp }) {
447+
if (maintenanceKeyValue && maintenanceKeyValue === config.maintenanceKey) {
448+
if (checkIp(clientIp, config.maintenanceKeyIps || [], config.maintenanceKeyIpsStore)) {
449+
return new auth.Auth({ config, installationId, isMaintenance: true });
450+
}
451+
const log = config.loggerController || defaultLogger;
452+
log.error(
453+
`Request using maintenance key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'maintenanceKeyIps'.`
454+
);
455+
}
456+
const masterKey = await config.loadMasterKey();
457+
if (keyValue === masterKey) {
458+
if (checkIp(clientIp, config.masterKeyIps || [], config.masterKeyIpsStore)) {
459+
return new auth.Auth({ config, installationId, isMaster: true });
460+
}
461+
const log = config.loggerController || defaultLogger;
462+
log.error(
463+
`Request using master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'masterKeyIps'.`
464+
);
465+
const error = new Error();
466+
error.status = 403;
467+
error.message = 'unauthorized';
468+
throw error;
469+
}
470+
if (
471+
keyValue &&
472+
typeof config.readOnlyMasterKey !== 'undefined' &&
473+
config.readOnlyMasterKey &&
474+
keyValue === config.readOnlyMasterKey
475+
) {
476+
if (checkIp(clientIp, config.readOnlyMasterKeyIps || [], config.readOnlyMasterKeyIpsStore)) {
477+
return new auth.Auth({ config, installationId, isMaster: true, isReadOnly: true });
478+
}
479+
const log = config.loggerController || defaultLogger;
480+
log.error(
481+
`Request using read-only master key rejected as the request IP address '${clientIp}' is not set in Parse Server option 'readOnlyMasterKeyIps'.`
482+
);
483+
const error = new Error();
484+
error.status = 403;
485+
error.message = 'unauthorized';
486+
throw error;
487+
}
488+
return null;
489+
}
490+
491+
export function handleParseAuth(appId) {
492+
return async (req, res, next) => {
493+
const mount = getMountForRequest(req);
494+
const config = Config.get(appId, mount);
495+
if (!config) {
496+
return next();
497+
}
498+
req.config = config;
499+
const clientIp = getClientIp(req);
500+
req.config.ip = clientIp;
501+
await config.loadKeys();
502+
const resolved = await resolveKeyAuth({
503+
config,
504+
keyValue: req.get('X-Parse-Master-Key') || null,
505+
maintenanceKeyValue: req.get('X-Parse-Maintenance-Key') || null,
506+
installationId: req.get('X-Parse-Installation-Id') || 'cloud',
507+
clientIp,
508+
});
509+
if (resolved) {
510+
req.auth = resolved;
511+
}
512+
return next();
513+
};
514+
}
515+
516+
export function handleParseHealth(options) {
517+
return (req, res) => {
518+
res.status(options.state === 'ok' ? 200 : 503);
519+
if (options.state === 'starting') {
520+
res.set('Retry-After', 1);
521+
}
522+
res.json({
523+
status: options.state,
524+
});
525+
};
526+
}
527+
494528
export function enforceRouteAllowList(req, res, next) {
495529
const config = req.config;
496530
if (!config || config.routeAllowList === undefined || config.routeAllowList === null) {

0 commit comments

Comments
 (0)