Compare commits

...
5 Commits
Author SHA1 Message Date
semantic-release-bot 30576f1091 chore(release): 5.4.1 [skip ci]
## [5.4.1](https://github.com/parse-community/parse-server/compare/5.4.0...5.4.1) (2023-01-31)

### Bug Fixes

* The client IP address may be determined incorrectly in some cases; it is now required to set the Parse Server option `trustProxy` accordingly if Parse Server runs behind a proxy server, see the express framework's [trust proxy](https://expressjs.com/en/guide/behind-proxies.html) setting; this fixes a security vulnerability in which the Parse Server option `masterKeyIps` may be circumvented, see [GHSA-vm5r-c87r-pf6x](https://github.com/parse-community/parse-server/security/advisories/GHSA-vm5r-c87r-pf6x) ([#8369](https://github.com/parse-community/parse-server/issues/8369)) ([e016d81](https://github.com/parse-community/parse-server/commit/e016d813e083ce6828f9abce245d15b681a224d8))
2023-01-31 03:57:58 +00:00
Manuel e016d813e0 fix: The client IP address may be determined incorrectly in some cases; it is now required to set the Parse Server option trustProxy accordingly if Parse Server runs behind a proxy server, see the express framework's [trust proxy](https://expressjs.com/en/guide/behind-proxies.html) setting; this fixes a security vulnerability in which the Parse Server option masterKeyIps may be circumvented, see [GHSA-vm5r-c87r-pf6x](https://github.com/parse-community/parse-server/security/advisories/GHSA-vm5r-c87r-pf6x) (#8369) 2023-01-05 14:20:40 +01:00
Manuel c8bc200729 ci: Add LTS branches to CI workflow 2023-01-05 13:20:43 +01:00
Manuel Trezza 09d04b0aad ci: update auto-release workflow 2022-11-19 12:35:00 +01:00
Manuel Trezza 38f64be596 ci: update auto-release for LTS 2022-11-19 12:31:43 +01:00
12 changed files with 46 additions and 1908 deletions
+2 -2
View File
@@ -1,9 +1,9 @@
name: ci
on:
push:
branches: [ release, alpha, beta ]
branches: [ release*, alpha, beta ]
pull_request:
branches: [ release, alpha, beta ]
branches: [ release*, alpha, beta ]
env:
NODE_VERSION: 18.1.0
PARSE_SERVER_TEST_TIMEOUT: 20000
+1 -1
View File
@@ -1,7 +1,7 @@
name: release-automated
on:
push:
branches: [ release, alpha, beta, next-major ]
branches: [ release, alpha, beta, next-major, release-4.x.x, release-5.x.x ]
jobs:
release:
runs-on: ubuntu-latest
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "5.4.0",
"version": "5.4.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "5.4.0",
"version": "5.4.1",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
+2 -15
View File
@@ -28,7 +28,7 @@ async function config() {
console.log(`Running on branch: ${branch}`);
// Set changelog file
const changelogFile = `./changelogs/CHANGELOG_${branch}.md`;
const changelogFile = `./changelogs/CHANGELOG_release.md`;
console.log(`Changelog file output to: ${changelogFile}`);
// Load template file contents
@@ -41,10 +41,7 @@ async function config() {
{ name: 'beta', prerelease: true },
'next-major',
// Long-Term-Support branches
// { name: 'release-1', range: '1.x.x', channel: '1.x' },
// { name: 'release-2', range: '2.x.x', channel: '2.x' },
// { name: 'release-3', range: '3.x.x', channel: '3.x' },
// { name: 'release-4', range: '4.x.x', channel: '4.x' },
{ name: 'release-5.x.x', range: '5.x.x', channel: '5.x.x' },
],
dryRun: false,
debug: true,
@@ -88,16 +85,6 @@ async function config() {
labels: ['type:ci'],
releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
}],
// Back-merge module runs last because if it fails it should not impede the release process
[
"@saithodev/semantic-release-backmerge",
{
"branches": [
{ from: "beta", to: "alpha" },
{ from: "release", to: "beta" },
]
}
],
],
};
+3 -118
View File
@@ -158,78 +158,6 @@ describe('middlewares', () => {
});
});
it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { remoteAddress: 'ip3' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { remoteAddress: 'ip1' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.socket = { remoteAddress: 'ip3' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.socket = { remoteAddress: 'ip1' };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { socket: { remoteAddress: 'ip3' } };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1', 'ip2'],
});
fakeReq.connection = { socket: { remoteAddress: 'ip1' } };
fakeReq.headers['x-parse-master-key'] = 'masterKey';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should allow any ip to use masterKey if masterKeyIps is empty', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
@@ -243,52 +171,9 @@ describe('middlewares', () => {
});
});
it('should succeed if xff header does belong to masterKeyIps', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should succeed if xff header with one ip does belong to masterKeyIps', done => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1';
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
expect(fakeRes.status).not.toHaveBeenCalled();
done();
});
});
it('should not succeed if xff header does not belong to masterKeyIps', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip4'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = 'ip1, ip2, ip3';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
});
it('should not succeed if xff header is empty and masterKeyIps is set', () => {
AppCache.put(fakeReq.body._ApplicationId, {
masterKey: 'masterKey',
masterKeyIps: ['ip1'],
});
fakeReq.headers['x-parse-master-key'] = 'masterKey';
fakeReq.headers['x-forwarded-for'] = '';
middlewares.handleParseHeaders(fakeReq, fakeRes);
expect(fakeRes.status).toHaveBeenCalledWith(403);
it('can set trust proxy', async () => {
const server = await reconfigureServer({ trustProxy: 1 });
expect(server.app.parent.settings['trust proxy']).toBe(1);
});
it('should properly expose the headers', () => {
+7
View File
@@ -477,6 +477,13 @@ module.exports.ParseServerOptions = {
help: 'Starts the liveQuery server',
action: parsers.booleanParser,
},
trustProxy: {
env: 'PARSE_SERVER_TRUST_PROXY',
help:
'The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.',
action: parsers.objectParser,
default: [],
},
userSensitiveFields: {
env: 'PARSE_SERVER_USER_SENSITIVE_FIELDS',
help:
+1
View File
@@ -88,6 +88,7 @@
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
* @property {Boolean} silent Disables console output
* @property {Boolean} startLiveQueryServer Starts the liveQuery server
* @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose
* @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process.<br><br>Default is `false`.
+3
View File
@@ -238,6 +238,9 @@ export interface ParseServerOptions {
cluster: ?NumberOrBoolean;
/* middleware for express server, can be string or function */
middleware: ?((() => void) | string);
/* The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
:DEFAULT: false */
trustProxy: ?any;
/* Starts the liveQuery server */
startLiveQueryServer: ?boolean;
/* Live query server configuration options (will start the liveQuery server) */
+3
View File
@@ -304,6 +304,9 @@ class ParseServer {
options
);
}
if (options.trustProxy) {
app.set('trust proxy', options.trustProxy);
}
/* istanbul ignore next */
if (!process.env.TESTING) {
configureListeners(this);
+1 -16
View File
@@ -280,22 +280,7 @@ export function handleParseHeaders(req, res, next) {
}
function getClientIp(req) {
if (req.headers['x-forwarded-for']) {
// try to get from x-forwared-for if it set (behind reverse proxy)
return req.headers['x-forwarded-for'].split(',')[0];
} else if (req.connection && req.connection.remoteAddress) {
// no proxy, try getting from connection.remoteAddress
return req.connection.remoteAddress;
} else if (req.socket) {
// try to get it from req.socket
return req.socket.remoteAddress;
} else if (req.connection && req.connection.socket) {
// try to get it form the connection.socket
return req.connection.socket.remoteAddress;
} else {
// if non above, fallback.
return req.ip;
}
return req.ip;
}
function httpAuth(req) {