From c2c1979e091325f3b436357c9b9d39fa2cef12d7 Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 3 Nov 2022 12:55:19 +1100 Subject: [PATCH 1/7] feat: limit masterKey to localhost --- package-lock.json | 8 +++ package.json | 9 +-- spec/Middlewares.spec.js | 137 +++++++++++++++++-------------------- spec/helper.js | 1 + src/Config.js | 5 +- src/Options/Definitions.js | 2 +- src/Options/index.js | 2 +- src/middlewares.js | 13 ++-- 8 files changed, 87 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00124696bf..315b81ae56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7670,6 +7670,14 @@ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" }, + "ip-range-check": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ip-range-check/-/ip-range-check-0.2.0.tgz", + "integrity": "sha512-oaM3l/3gHbLlt/tCWLvt0mj1qUaI+STuRFnUvARGCujK9vvU61+2JsDpmkMzR4VsJhuFXWWgeKKVnwwoFfzCqw==", + "requires": { + "ipaddr.js": "^1.0.1" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/package.json b/package.json index 367e370b0f..2f722dd6e9 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ ], "license": "BSD-3-Clause", "dependencies": { - "@graphql-yoga/node": "2.6.0", - "@graphql-tools/utils": "8.12.0", "@graphql-tools/merge": "8.3.6", "@graphql-tools/schema": "9.0.4", + "@graphql-tools/utils": "8.12.0", + "@graphql-yoga/node": "2.6.0", "@parse/fs-files-adapter": "1.2.2", "@parse/push-adapter": "4.1.2", "bcryptjs": "2.4.3", @@ -34,9 +34,10 @@ "follow-redirects": "1.15.2", "graphql": "16.6.0", "graphql-list-fields": "2.0.2", - "graphql-tag": "2.12.6", "graphql-relay": "0.10.0", + "graphql-tag": "2.12.6", "intersect": "1.0.1", + "ip-range-check": "0.2.0", "jsonwebtoken": "8.5.1", "jwks-rsa": "2.1.4", "ldapjs": "2.3.3", @@ -59,7 +60,6 @@ "ws": "8.9.0" }, "devDependencies": { - "graphql-tag": "2.12.6", "@actions/core": "1.9.1", "@apollo/client": "3.6.1", "@babel/cli": "7.10.0", @@ -86,6 +86,7 @@ "eslint-plugin-flowtype": "5.1.3", "flow-bin": "0.119.1", "form-data": "3.0.0", + "graphql-tag": "2.12.6", "husky": "4.3.8", "jasmine": "3.5.0", "jasmine-spec-reporter": "7.0.0", diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index 2c380152ba..eaee3f69cf 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -3,7 +3,6 @@ const AppCache = require('../lib/cache').AppCache; describe('middlewares', () => { let fakeReq, fakeRes; - beforeEach(() => { fakeReq = { originalUrl: 'http://example.com/parse/', @@ -117,10 +116,12 @@ describe('middlewares', () => { const otherKeys = BodyKeys.filter( otherKey => otherKey !== infoKey && otherKey !== 'javascriptKey' ); - it(`it should pull ${bodyKey} into req.info`, done => { + AppCache.put(fakeReq.body._ApplicationId, { + masterKeyIps: ['0.0.0.0/0'], + }); + fakeReq.ip = '127.0.0.1'; fakeReq.body[bodyKey] = keyValue; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { expect(fakeReq.body[bodyKey]).toEqual(undefined); expect(fakeReq.info[infoKey]).toEqual(keyValue); @@ -134,23 +135,23 @@ describe('middlewares', () => { }); }); - it('should not succeed if the ip does not belong to masterKeyIps list', () => { + it('should not succeed if the ip does not belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1'], }); - fakeReq.ip = 'ip3'; + fakeReq.ip = '127.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); - expect(fakeRes.status).toHaveBeenCalledWith(403); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); it('should succeed if the ip does belong to masterKeyIps list', done => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1'], }); - fakeReq.ip = 'ip1'; + fakeReq.ip = '10.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; middlewares.handleParseHeaders(fakeReq, fakeRes, () => { expect(fakeRes.status).not.toHaveBeenCalled(); @@ -158,137 +159,125 @@ describe('middlewares', () => { }); }); - it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', () => { + it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); - fakeReq.connection = { remoteAddress: 'ip3' }; + fakeReq.connection = { remoteAddress: '127.0.0.1' }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); - expect(fakeRes.status).toHaveBeenCalledWith(403); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); - it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', done => { + it('should succeed if the connection.remoteAddress does belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); - fakeReq.connection = { remoteAddress: 'ip1' }; + fakeReq.connection = { remoteAddress: '10.0.0.1' }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', () => { + it('should not succeed if the socket.remoteAddress does not belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); - fakeReq.socket = { remoteAddress: 'ip3' }; + fakeReq.socket = { remoteAddress: '127.0.0.1' }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); - expect(fakeRes.status).toHaveBeenCalledWith(403); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); - it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', done => { + it('should succeed if the socket.remoteAddress does belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); - fakeReq.socket = { remoteAddress: 'ip1' }; + fakeReq.socket = { remoteAddress: '10.0.0.1' }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', () => { + it('should not succeed if the connection.socket.remoteAddress does not belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); fakeReq.connection = { socket: { remoteAddress: 'ip3' } }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); - expect(fakeRes.status).toHaveBeenCalledWith(403); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); - it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', done => { + it('should succeed if the connection.socket.remoteAddress does belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1', 'ip2'], + masterKeyIps: ['10.0.0.1', '10.0.0.2'], }); - fakeReq.connection = { socket: { remoteAddress: 'ip1' } }; + fakeReq.connection = { socket: { remoteAddress: '10.0.0.1' } }; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should allow any ip to use masterKey if masterKeyIps is empty', done => { + it('should allow any ip to use masterKey if masterKeyIps is empty', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: [], + masterKeyIps: ['0.0.0.0/0'], }); - fakeReq.ip = 'ip1'; + fakeReq.ip = '10.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should succeed if xff header does belong to masterKeyIps', done => { + it('should succeed if xff header does belong to masterKeyIps', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1'], + masterKeyIps: ['10.0.0.1'], }); 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(); - }); + fakeReq.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2, ip3'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should succeed if xff header with one ip does belong to masterKeyIps', done => { + it('should succeed if xff header with one ip does belong to masterKeyIps', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1'], + masterKeyIps: ['10.0.0.1'], }); fakeReq.headers['x-parse-master-key'] = 'masterKey'; - fakeReq.headers['x-forwarded-for'] = 'ip1'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + fakeReq.headers['x-forwarded-for'] = '10.0.0.1'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); - it('should not succeed if xff header does not belong to masterKeyIps', () => { + it('should not succeed if xff header does not belong to masterKeyIps', async () => { 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); + fakeReq.headers['x-forwarded-for'] = '10.0.0.1, 10.0.0.2, ip3'; + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); - it('should not succeed if xff header is empty and masterKeyIps is set', () => { + it('should not succeed if xff header is empty and masterKeyIps is set', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', - masterKeyIps: ['ip1'], + masterKeyIps: ['10.0.0.1'], }); fakeReq.headers['x-parse-master-key'] = 'masterKey'; fakeReq.headers['x-forwarded-for'] = ''; - middlewares.handleParseHeaders(fakeReq, fakeRes); - expect(fakeRes.status).toHaveBeenCalledWith(403); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(false); }); it('should properly expose the headers', () => { diff --git a/spec/helper.js b/spec/helper.js index f769c0f521..7030e71de9 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -110,6 +110,7 @@ const defaultConfiguration = { enableForAnonymousUser: true, enableForAuthenticatedUser: true, }, + masterKeyIps: ['127.0.0.1'], push: { android: { senderId: 'yolo', diff --git a/src/Config.js b/src/Config.js index 9c5fb3bc83..238e44a65c 100644 --- a/src/Config.js +++ b/src/Config.js @@ -427,7 +427,10 @@ export class Config { } static validateMasterKeyIps(masterKeyIps) { - for (const ip of masterKeyIps) { + for (let ip of masterKeyIps) { + if (ip.includes('/')) { + ip = ip.split('/')[0]; + } if (!net.isIP(ip)) { throw `Invalid ip in masterKeyIps: ${ip}`; } diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index d8e3f841dd..5c1fc915fb 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -297,7 +297,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_MASTER_KEY_IPS', help: 'Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)', action: parsers.arrayParser, - default: [], + default: ['127.0.0.1'], }, maxLimit: { env: 'PARSE_SERVER_MAX_LIMIT', diff --git a/src/Options/index.js b/src/Options/index.js index 2592e1e441..5f93182770 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -50,7 +50,7 @@ export interface ParseServerOptions { :ENV: PARSE_SERVER_URL */ serverURL: string; /* Restrict masterKey to be used by only these ips, defaults to [] (allow all ips) - :DEFAULT: [] */ + :DEFAULT: ["127.0.0.1"] */ masterKeyIps: ?(string[]); /* Sets the app name */ appName: ?string; diff --git a/src/middlewares.js b/src/middlewares.js index 37acf46821..42aad1cbbe 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -7,6 +7,7 @@ import defaultLogger from './logger'; import rest from './rest'; import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter'; import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageAdapter'; +import ipRangeCheck from 'ip-range-check'; export const DEFAULT_ALLOWED_HEADERS = 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control'; @@ -164,17 +165,11 @@ export function handleParseHeaders(req, res, next) { req.config.ip = clientIp; req.info = info; - if ( - info.masterKey && - req.config.masterKeyIps && - req.config.masterKeyIps.length !== 0 && - req.config.masterKeyIps.indexOf(clientIp) === -1 - ) { - return invalidRequest(req, res); + let isMaster = info.masterKey === req.config.masterKey; + if (isMaster && !ipRangeCheck(clientIp, req.config.masterKeyIps || [])) { + isMaster = false; } - var isMaster = info.masterKey === req.config.masterKey; - if (isMaster) { req.auth = new auth.Auth({ config: req.config, From 01bbe025f2b59aa900d9d6ad9e8d88acc7f7f30f Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 3 Nov 2022 13:02:36 +1100 Subject: [PATCH 2/7] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index eaee3f69cf..5e1ec24d89 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -146,17 +146,15 @@ describe('middlewares', () => { expect(fakeReq.auth.isMaster).toBe(false); }); - it('should succeed if the ip does belong to masterKeyIps list', done => { + it('should succeed if the ip does belong to masterKeyIps list', async () => { AppCache.put(fakeReq.body._ApplicationId, { masterKey: 'masterKey', masterKeyIps: ['10.0.0.1'], }); fakeReq.ip = '10.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes, () => { - expect(fakeRes.status).not.toHaveBeenCalled(); - done(); - }); + await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); + expect(fakeReq.auth.isMaster).toBe(true); }); it('should not succeed if the connection.remoteAddress does not belong to masterKeyIps list', async () => { From ff06e5ab68b3ccc8461f3c4d1c6347a4e67000c4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 11 Nov 2022 10:37:49 +1100 Subject: [PATCH 3/7] definitions --- src/Options/Definitions.js | 3 ++- src/Options/docs.js | 2 +- src/Options/index.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 2902969f53..928ebf87d5 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -302,7 +302,8 @@ module.exports.ParseServerOptions = { }, masterKeyIps: { env: 'PARSE_SERVER_MASTER_KEY_IPS', - help: 'Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)', + help: + 'Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"].', action: parsers.arrayParser, default: ['127.0.0.1'], }, diff --git a/src/Options/docs.js b/src/Options/docs.js index 9a379074b1..da2472d267 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -58,7 +58,7 @@ * @property {String} logLevel Sets the level for logs * @property {String} logsFolder Folder for the logs (defaults to './logs'); set to null to disable file based logging * @property {String} masterKey Your Parse Master Key - * @property {String[]} masterKeyIps Restrict masterKey to be used by only these ips, defaults to [] (allow all ips) + * @property {String[]} masterKeyIps Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"]. * @property {Number} maxLimit Max value for limit option on queries, defaults to unlimited * @property {Number|String} maxLogFiles Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null) * @property {String} maxUploadSize Max file size for uploads, defaults to 20mb diff --git a/src/Options/index.js b/src/Options/index.js index 7ef43fe803..a28fb97ace 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -49,7 +49,7 @@ export interface ParseServerOptions { /* URL to your parse server with http:// or https://. :ENV: PARSE_SERVER_URL */ serverURL: string; - /* Restrict masterKey to be used by only these ips, defaults to [] (allow all ips) + /* Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"]. :DEFAULT: ["127.0.0.1"] */ masterKeyIps: ?(string[]); /* Sets the app name */ From 551573fbbb33b2dbec43e6aed0b1d5af8349238b Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 11 Nov 2022 00:43:24 +0100 Subject: [PATCH 4/7] improve docs --- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- src/Options/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 928ebf87d5..e25c2e53bc 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -303,7 +303,7 @@ module.exports.ParseServerOptions = { masterKeyIps: { env: 'PARSE_SERVER_MASTER_KEY_IPS', help: - 'Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"].', + "(Optional) Restricts the use of master key permissions to a list of IP addresses.

This option accepts a list of single IP addresses, for example:
`['10.0.0.1', '10.0.0.2']`

You can also use CIDR notation to specify an IP address range, for example:
`['10.0.1.0/24']`

Special cases:
- Setting an empty array `[]` means that `masterKey`` cannot be used even in Parse Server Cloud Code.
- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.

To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.

Defaults to `['127.0.0.1']` which means that only `localhost`, the server itself, is allowed to use the master key.", action: parsers.arrayParser, default: ['127.0.0.1'], }, diff --git a/src/Options/docs.js b/src/Options/docs.js index da2472d267..6c22e91e2e 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -58,7 +58,7 @@ * @property {String} logLevel Sets the level for logs * @property {String} logsFolder Folder for the logs (defaults to './logs'); set to null to disable file based logging * @property {String} masterKey Your Parse Master Key - * @property {String[]} masterKeyIps Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"]. + * @property {String[]} masterKeyIps (Optional) Restricts the use of master key permissions to a list of IP addresses.

This option accepts a list of single IP addresses, for example:
`['10.0.0.1', '10.0.0.2']`

You can also use CIDR notation to specify an IP address range, for example:
`['10.0.1.0/24']`

Special cases:
- Setting an empty array `[]` means that `masterKey`` cannot be used even in Parse Server Cloud Code.
- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.

To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.

Defaults to `['127.0.0.1']` which means that only `localhost`, the server itself, is allowed to use the master key. * @property {Number} maxLimit Max value for limit option on queries, defaults to unlimited * @property {Number|String} maxLogFiles Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null) * @property {String} maxUploadSize Max file size for uploads, defaults to 20mb diff --git a/src/Options/index.js b/src/Options/index.js index a28fb97ace..8b6d4c019e 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -49,7 +49,7 @@ export interface ParseServerOptions { /* URL to your parse server with http:// or https://. :ENV: PARSE_SERVER_URL */ serverURL: string; - /* Restrict masterKey to be used by only these ips, defaults to ["127.0.0.1"] (localhost only). Option can be an array of IPs, or CIDR range. For allow from anywhere, use ["0.0.0.0/0"]. + /* (Optional) Restricts the use of master key permissions to a list of IP addresses.

This option accepts a list of single IP addresses, for example:
`['10.0.0.1', '10.0.0.2']`

You can also use CIDR notation to specify an IP address range, for example:
`['10.0.1.0/24']`

Special cases:
- Setting an empty array `[]` means that `masterKey`` cannot be used even in Parse Server Cloud Code.
- Setting `['0.0.0.0/0']` means disabling the filter and the master key can be used from any IP address.

To connect Parse Dashboard from a different server requires to add the IP address of the server that hosts Parse Dashboard because Parse Dashboard uses the master key.

Defaults to `['127.0.0.1']` which means that only `localhost`, the server itself, is allowed to use the master key. :DEFAULT: ["127.0.0.1"] */ masterKeyIps: ?(string[]); /* Sets the app name */ From d442d47f8366dc73c35accb6873ac682855f0fd0 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 11 Nov 2022 02:18:18 +0100 Subject: [PATCH 5/7] fix typo in error message --- src/Config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.js b/src/Config.js index 48c119b1c7..6667a4da03 100644 --- a/src/Config.js +++ b/src/Config.js @@ -440,7 +440,7 @@ export class Config { ip = ip.split('/')[0]; } if (!net.isIP(ip)) { - throw `Invalid ip in masterKeyIps: ${ip}`; + throw `Invalid IP address '${ip}' in option masterKeyIps.`; } } } From cbca436ea06611065ce5e3adc3d201460354d5aa Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 11 Nov 2022 02:29:48 +0100 Subject: [PATCH 6/7] reword error msg --- spec/index.spec.js | 4 +++- src/Config.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 837656d1f2..a17be39cf5 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -495,7 +495,9 @@ describe('server', () => { it('fails if you provides invalid ip in masterKeyIps', done => { reconfigureServer({ masterKeyIps: ['invalidIp', '1.2.3.4'] }).catch(error => { - expect(error).toEqual('Invalid ip in masterKeyIps: invalidIp'); + expect(error).toEqual( + 'The option "masterKeyIps" contains an invalid IP address "invalidIp".' + ); done(); }); }); diff --git a/src/Config.js b/src/Config.js index 6667a4da03..8045b735c9 100644 --- a/src/Config.js +++ b/src/Config.js @@ -440,7 +440,7 @@ export class Config { ip = ip.split('/')[0]; } if (!net.isIP(ip)) { - throw `Invalid IP address '${ip}' in option masterKeyIps.`; + throw `The option "masterKeyIps" contains an invalid IP address "${ip}".`; } } } From f2b6a8c857c71ab7eddf6e069b3d78aaba8aa8fc Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Fri, 11 Nov 2022 02:30:37 +0100 Subject: [PATCH 7/7] reword error msg --- spec/index.spec.js | 2 +- src/Config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index a17be39cf5..d28d532861 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -496,7 +496,7 @@ describe('server', () => { it('fails if you provides invalid ip in masterKeyIps', done => { reconfigureServer({ masterKeyIps: ['invalidIp', '1.2.3.4'] }).catch(error => { expect(error).toEqual( - 'The option "masterKeyIps" contains an invalid IP address "invalidIp".' + 'The Parse Server option "masterKeyIps" contains an invalid IP address "invalidIp".' ); done(); }); diff --git a/src/Config.js b/src/Config.js index 8045b735c9..d2cd3b94f8 100644 --- a/src/Config.js +++ b/src/Config.js @@ -440,7 +440,7 @@ export class Config { ip = ip.split('/')[0]; } if (!net.isIP(ip)) { - throw `The option "masterKeyIps" contains an invalid IP address "${ip}".`; + throw `The Parse Server option "masterKeyIps" contains an invalid IP address "${ip}".`; } } }