From 0ea5e5b7df894536b24e5c272bfe25fcc95fa886 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Feb 2025 22:55:21 +1100 Subject: [PATCH 1/7] feat: dynamic masterKey --- spec/index.spec.js | 57 ++++++++++++++++++++++++++++++++++++++ src/Config.js | 22 +++++++++++++++ src/Options/Definitions.js | 6 ++++ src/Options/docs.js | 3 +- src/Options/index.js | 4 ++- src/ParseServer.js | 2 +- src/middlewares.js | 5 ++-- 7 files changed, 94 insertions(+), 5 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 66654aaec4..19253b502c 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -601,6 +601,63 @@ describe('server', () => { await new Promise(resolve => server.close(resolve)); }); + it('should load masterKey', async () => { + await reconfigureServer({ + masterKey: () => 'testMasterKey', + masterKeyTtl: 1000, // TTL is set + }); + + await new Parse.Object('TestObject').save(); + + const config = Config.get(Parse.applicationId); + expect(config.masterKeyCache.masterKey).toEqual('testMasterKey'); + expect(config.masterKeyCache.expiresAt.getTime()).toBeGreaterThan(Date.now()); + }); + + it('should not reload if ttl is not set', async () => { + const masterKeySpy = jasmine.createSpy().and.returnValue(Promise.resolve('initialMasterKey')); + + await reconfigureServer({ + masterKey: masterKeySpy, + masterKeyTtl: null, // No TTL set + }); + + await new Parse.Object('TestObject').save(); + + const config = Config.get(Parse.applicationId); + const firstMasterKey = config.masterKeyCache.masterKey; + + // Simulate calling the method again + await config.loadMasterKey(); + const secondMasterKey = config.masterKeyCache.masterKey; + + expect(firstMasterKey).toEqual('initialMasterKey'); + expect(secondMasterKey).toEqual('initialMasterKey'); + expect(masterKeySpy).toHaveBeenCalledTimes(1); // Should only be called once + expect(config.masterKeyCache.expiresAt).toBeNull(); // TTL is not set, so expiresAt should remain null + }); + + fit('should reload masterKey if ttl is set and expired', async () => { + const masterKeySpy = jasmine.createSpy() + .and.returnValues(Promise.resolve('firstMasterKey'), Promise.resolve('secondMasterKey')); + + await reconfigureServer({ + masterKey: masterKeySpy, + masterKeyTtl: 1/1000, // TTL is set to 1ms + }); + + await new Parse.Object('TestObject').save(); + + await new Promise(resolve => setTimeout(resolve, 10)); + + await new Parse.Object('TestObject').save(); + + const config = Config.get(Parse.applicationId); + expect(masterKeySpy).toHaveBeenCalledTimes(2); + expect(config.masterKeyCache.masterKey).toEqual('secondMasterKey'); + }); + + it('should not fail when Google signin is introduced without the optional clientId', done => { const jwt = require('jsonwebtoken'); const authUtils = require('../lib/Adapters/Auth/utils'); diff --git a/src/Config.js b/src/Config.js index c4884434ca..6292caee71 100644 --- a/src/Config.js +++ b/src/Config.js @@ -724,6 +724,28 @@ export class Config { return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/verify_email`; } + async loadMasterKey() { + if (typeof this.masterKey === 'function') { + const ttlIsEmpty = !this.masterKeyTtl; + const isExpired = this.masterKeyCache?.expiresAt && this.masterKeyCache.expiresAt < new Date(); + + if ((!isExpired || ttlIsEmpty) && this.masterKeyCache?.masterKey) { + return this.masterKeyCache.masterKey; + } + + const masterKey = await this.masterKey(); + + const expiresAt = this.masterKeyTtl ? new Date(Date.now() + 1000 * this.masterKeyTtl) : null + this.masterKeyCache = { masterKey, expiresAt }; + Config.put(this); + + return this.masterKeyCache.masterKey; + } + + return this.masterKey; + } + + // TODO: Remove this function once PagesRouter replaces the PublicAPIRouter; // the (default) endpoint has to be defined in PagesRouter only. get pagesEndpoint() { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index a2696f44de..e96c821975 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -369,6 +369,12 @@ module.exports.ParseServerOptions = { action: parsers.arrayParser, default: ['127.0.0.1', '::1'], }, + masterKeyTtl: { + env: 'PARSE_SERVER_MASTER_KEY_TTL', + help: + 'MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function.', + action: parsers.numberParser('masterKeyTtl'), + }, maxLimit: { env: 'PARSE_SERVER_MAX_LIMIT', help: 'Max value for limit option on queries, defaults to unlimited', diff --git a/src/Options/docs.js b/src/Options/docs.js index 75b22daf80..c70c0f51c0 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -65,8 +65,9 @@ * @property {String} logsFolder Folder for the logs (defaults to './logs'); set to null to disable file based logging * @property {String} maintenanceKey (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.

⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. * @property {String[]} maintenanceKeyIps (Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.

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 scenarios:
- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.
- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.

Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key. - * @property {String} masterKey Your Parse Master Key + * @property {Union} masterKey Your Parse Master Key * @property {String[]} masterKeyIps (Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.

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 scenarios:
- Setting an empty array `[]` means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.
- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.

Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the master key. + * @property {Number} masterKeyTtl MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function. * @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 4cc5a551c3..53f55ddc03 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -46,7 +46,9 @@ export interface ParseServerOptions { :ENV: PARSE_SERVER_APPLICATION_ID */ appId: string; /* Your Parse Master Key */ - masterKey: string; + masterKey: (() => void) | string; + /* MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function. */ + masterKeyTtl: ?number; /* (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.

⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */ maintenanceKey: string; /* URL to your parse server with http:// or https://. diff --git a/src/ParseServer.js b/src/ParseServer.js index db30637db8..bec755d5bf 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -162,7 +162,7 @@ class ParseServer { } const pushController = await controllers.getPushController(this.config); await hooksController.load(); - const startupPromises = []; + const startupPromises = [this.config.loadMasterKey?.()]; if (schema) { startupPromises.push(new DefinedSchemas(schema, this.config).execute()); } diff --git a/src/middlewares.js b/src/middlewares.js index 178692b447..57838b1a69 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -69,7 +69,7 @@ export const checkIp = (ip, ipRangeList, store) => { // Adds info to the request: // req.config - the Config for this app // req.auth - the Auth for this request -export function handleParseHeaders(req, res, next) { +export async function handleParseHeaders(req, res, next) { var mount = getMountForRequest(req); let context = {}; @@ -238,7 +238,8 @@ export function handleParseHeaders(req, res, next) { ); } - let isMaster = info.masterKey === req.config.masterKey; + const masterKey = await req.config.loadMasterKey(); + let isMaster = info.masterKey === masterKey; if (isMaster && !checkIp(clientIp, req.config.masterKeyIps || [], req.config.masterKeyIpsStore)) { const log = req.config?.loggerController || defaultLogger; From 1ebc5d5a5ba08dbc021202ebf83229a8216c7eec Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Feb 2025 22:55:55 +1100 Subject: [PATCH 2/7] run lint --- spec/index.spec.js | 28 ++++++++++++++-------------- src/Config.js | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index 19253b502c..ac25050a4c 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -606,48 +606,48 @@ describe('server', () => { masterKey: () => 'testMasterKey', masterKeyTtl: 1000, // TTL is set }); - + await new Parse.Object('TestObject').save(); - + const config = Config.get(Parse.applicationId); expect(config.masterKeyCache.masterKey).toEqual('testMasterKey'); expect(config.masterKeyCache.expiresAt.getTime()).toBeGreaterThan(Date.now()); }); - + it('should not reload if ttl is not set', async () => { const masterKeySpy = jasmine.createSpy().and.returnValue(Promise.resolve('initialMasterKey')); - + await reconfigureServer({ masterKey: masterKeySpy, masterKeyTtl: null, // No TTL set }); - + await new Parse.Object('TestObject').save(); - + const config = Config.get(Parse.applicationId); const firstMasterKey = config.masterKeyCache.masterKey; - + // Simulate calling the method again await config.loadMasterKey(); const secondMasterKey = config.masterKeyCache.masterKey; - + expect(firstMasterKey).toEqual('initialMasterKey'); expect(secondMasterKey).toEqual('initialMasterKey'); expect(masterKeySpy).toHaveBeenCalledTimes(1); // Should only be called once expect(config.masterKeyCache.expiresAt).toBeNull(); // TTL is not set, so expiresAt should remain null }); - + fit('should reload masterKey if ttl is set and expired', async () => { const masterKeySpy = jasmine.createSpy() .and.returnValues(Promise.resolve('firstMasterKey'), Promise.resolve('secondMasterKey')); - + await reconfigureServer({ masterKey: masterKeySpy, - masterKeyTtl: 1/1000, // TTL is set to 1ms + masterKeyTtl: 1 / 1000, // TTL is set to 1ms }); - + await new Parse.Object('TestObject').save(); - + await new Promise(resolve => setTimeout(resolve, 10)); await new Parse.Object('TestObject').save(); @@ -656,7 +656,7 @@ describe('server', () => { expect(masterKeySpy).toHaveBeenCalledTimes(2); expect(config.masterKeyCache.masterKey).toEqual('secondMasterKey'); }); - + it('should not fail when Google signin is introduced without the optional clientId', done => { const jwt = require('jsonwebtoken'); diff --git a/src/Config.js b/src/Config.js index 6292caee71..73bae0dc0d 100644 --- a/src/Config.js +++ b/src/Config.js @@ -729,7 +729,7 @@ export class Config { const ttlIsEmpty = !this.masterKeyTtl; const isExpired = this.masterKeyCache?.expiresAt && this.masterKeyCache.expiresAt < new Date(); - if ((!isExpired || ttlIsEmpty) && this.masterKeyCache?.masterKey) { + if ((!isExpired || ttlIsEmpty) && this.masterKeyCache?.masterKey) { return this.masterKeyCache.masterKey; } @@ -744,7 +744,7 @@ export class Config { return this.masterKey; } - + // TODO: Remove this function once PagesRouter replaces the PublicAPIRouter; // the (default) endpoint has to be defined in PagesRouter only. From 92facac1d1a388463e6d96f234d354e98dce3a21 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Feb 2025 22:59:04 +1100 Subject: [PATCH 3/7] Update index.spec.js --- spec/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.spec.js b/spec/index.spec.js index ac25050a4c..1a2ea889a9 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -637,7 +637,7 @@ describe('server', () => { expect(config.masterKeyCache.expiresAt).toBeNull(); // TTL is not set, so expiresAt should remain null }); - fit('should reload masterKey if ttl is set and expired', async () => { + it('should reload masterKey if ttl is set and expired', async () => { const masterKeySpy = jasmine.createSpy() .and.returnValues(Promise.resolve('firstMasterKey'), Promise.resolve('secondMasterKey')); From 0e0a03ff0eb706d35c76b869f6bfb070b73ec1ac Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 1 Feb 2025 23:14:36 +1100 Subject: [PATCH 4/7] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index c0fcb659e5..fbec4d8d37 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -46,32 +46,32 @@ describe('middlewares', () => { }); }); - it('should give invalid response when keys are configured but no key supplied', () => { + it('should give invalid response when keys are configured but no key supplied', async () => { AppCachePut(fakeReq.body._ApplicationId, { masterKey: 'masterKey', restAPIKey: 'restAPIKey', }); - middlewares.handleParseHeaders(fakeReq, fakeRes); + await middlewares.handleParseHeaders(fakeReq, fakeRes); expect(fakeRes.status).toHaveBeenCalledWith(403); }); - it('should give invalid response when keys are configured but supplied key is incorrect', () => { + it('should give invalid response when keys are configured but supplied key is incorrect', async () => { AppCachePut(fakeReq.body._ApplicationId, { masterKey: 'masterKey', restAPIKey: 'restAPIKey', }); fakeReq.headers['x-parse-rest-api-key'] = 'wrongKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); + await middlewares.handleParseHeaders(fakeReq, fakeRes); expect(fakeRes.status).toHaveBeenCalledWith(403); }); - it('should give invalid response when keys are configured but different key is supplied', () => { + it('should give invalid response when keys are configured but different key is supplied', async () => { AppCachePut(fakeReq.body._ApplicationId, { masterKey: 'masterKey', restAPIKey: 'restAPIKey', }); fakeReq.headers['x-parse-client-key'] = 'clientKey'; - middlewares.handleParseHeaders(fakeReq, fakeRes); + await middlewares.handleParseHeaders(fakeReq, fakeRes); expect(fakeRes.status).toHaveBeenCalledWith(403); }); @@ -147,7 +147,7 @@ describe('middlewares', () => { }); }); - it_id('4a0bce41-c536-4482-a873-12ed023380e2')(it)('should not succeed and log if the ip does not belong to masterKeyIps list', async () => { + it('should not succeed and log if the ip does not belong to masterKeyIps list', async () => { const logger = require('../lib/logger').logger; spyOn(logger, 'error').and.callFake(() => {}); AppCachePut(fakeReq.body._ApplicationId, { @@ -157,13 +157,7 @@ describe('middlewares', () => { fakeReq.ip = '127.0.0.1'; fakeReq.headers['x-parse-master-key'] = 'masterKey'; - let error; - - try { - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - } catch (err) { - error = err; - } + const error = await middlewares.handleParseHeaders(fakeReq, fakeRes, () => {}).catch(e => e); expect(error).toBeDefined(); expect(error.message).toEqual(`unauthorized`); @@ -182,13 +176,7 @@ describe('middlewares', () => { fakeReq.ip = '10.0.0.2'; fakeReq.headers['x-parse-maintenance-key'] = 'masterKey'; - let error; - - try { - await new Promise(resolve => middlewares.handleParseHeaders(fakeReq, fakeRes, resolve)); - } catch (err) { - error = err; - } + const error = await middlewares.handleParseHeaders(fakeReq, fakeRes, () => {}).catch(e => e); expect(error).toBeDefined(); expect(error.message).toEqual(`unauthorized`); From 82274ba2520a4a64f4793bc1379f206a24714070 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 2 Feb 2025 20:00:36 +1100 Subject: [PATCH 5/7] Update Middlewares.spec.js --- spec/Middlewares.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Middlewares.spec.js b/spec/Middlewares.spec.js index fbec4d8d37..57dca22b0e 100644 --- a/spec/Middlewares.spec.js +++ b/spec/Middlewares.spec.js @@ -147,7 +147,7 @@ describe('middlewares', () => { }); }); - it('should not succeed and log if the ip does not belong to masterKeyIps list', async () => { + it_id('4a0bce41-c536-4482-a873-12ed023380e2')(it)('should not succeed and log if the ip does not belong to masterKeyIps list', async () => { const logger = require('../lib/logger').logger; spyOn(logger, 'error').and.callFake(() => {}); AppCachePut(fakeReq.body._ApplicationId, { From 81635e89a99f54546459b66e7d4b601c2b36f3b5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 9 Feb 2025 16:18:23 +1100 Subject: [PATCH 6/7] Update src/Options/index.js Co-authored-by: Manuel <5673677+mtrezza@users.noreply.github.com> Signed-off-by: Daniel --- src/Options/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/index.js b/src/Options/index.js index 53f55ddc03..70dd2a917c 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -47,7 +47,7 @@ export interface ParseServerOptions { appId: string; /* Your Parse Master Key */ masterKey: (() => void) | string; - /* MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function. */ + /* (Optional) The duration in seconds for which the current `masterKey` is being used before it is requested again if `masterKey` is set to a function. If `masterKey` is not set to a function, this option has no effect. Default is `0`, which means the master key is requested by invoking the `masterKey` function every time the master key is used internally by Parse Server. */ masterKeyTtl: ?number; /* (Optional) The maintenance key is used for modifying internal and read-only fields of Parse Server.

⚠️ This key is not intended to be used as part of a regular operation of Parse Server. This key is intended to conduct out-of-band changes such as one-time migrations or data correction tasks. Internal fields are not officially documented and may change at any time without publication in release changelogs. We strongly advice not to rely on internal fields as part of your regular operation and to investigate the implications of any planned changes *directly in the source code* of your current version of Parse Server. */ maintenanceKey: string; From 6ee76370a3f3a4e2efaabc08c3eaa0812183e16a Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 12 Feb 2025 21:35:22 +1100 Subject: [PATCH 7/7] run definitions --- src/Options/Definitions.js | 2 +- src/Options/docs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index e96c821975..da0f040014 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -372,7 +372,7 @@ module.exports.ParseServerOptions = { masterKeyTtl: { env: 'PARSE_SERVER_MASTER_KEY_TTL', help: - 'MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function.', + '(Optional) The duration in seconds for which the current `masterKey` is being used before it is requested again if `masterKey` is set to a function. If `masterKey` is not set to a function, this option has no effect. Default is `0`, which means the master key is requested by invoking the `masterKey` function every time the master key is used internally by Parse Server.', action: parsers.numberParser('masterKeyTtl'), }, maxLimit: { diff --git a/src/Options/docs.js b/src/Options/docs.js index c70c0f51c0..e1f89215f3 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -67,7 +67,7 @@ * @property {String[]} maintenanceKeyIps (Optional) Restricts the use of maintenance key permissions to a list of IP addresses or ranges.

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 scenarios:
- Setting an empty array `[]` means that the maintenance key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the maintenance key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.
- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.

Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the maintenance key. * @property {Union} masterKey Your Parse Master Key * @property {String[]} masterKeyIps (Optional) Restricts the use of master key permissions to a list of IP addresses or ranges.

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 scenarios:
- Setting an empty array `[]` means that the master key cannot be used even in Parse Server Cloud Code. This value cannot be set via an environment variable as there is no way to pass an empty array to Parse Server via an environment variable.
- Setting `['0.0.0.0/0', '::0']` means to allow any IPv4 and IPv6 address to use the master key and effectively disables the IP filter.

Considerations:
- IPv4 and IPv6 addresses are not compared against each other. Each IP version (IPv4 and IPv6) needs to be considered separately. For example, `['0.0.0.0/0']` allows any IPv4 address and blocks every IPv6 address. Conversely, `['::0']` allows any IPv6 address and blocks every IPv4 address.
- Keep in mind that the IP version in use depends on the network stack of the environment in which Parse Server runs. A local environment may use a different IP version than a remote environment. For example, it's possible that locally the value `['0.0.0.0/0']` allows the request IP because the environment is using IPv4, but when Parse Server is deployed remotely the request IP is blocked because the remote environment is using IPv6.
- When setting the option via an environment variable the notation is a comma-separated string, for example `"0.0.0.0/0,::0"`.
- IPv6 zone indices (`%` suffix) are not supported, for example `fe80::1%eth0`, `fe80::1%1` or `::1%lo`.

Defaults to `['127.0.0.1', '::1']` which means that only `localhost`, the server instance on which Parse Server runs, is allowed to use the master key. - * @property {Number} masterKeyTtl MasterKeyTtl is the duration in seconds for which the master key is refreshed. Only valid if the masterKey is a function. + * @property {Number} masterKeyTtl (Optional) The duration in seconds for which the current `masterKey` is being used before it is requested again if `masterKey` is set to a function. If `masterKey` is not set to a function, this option has no effect. Default is `0`, which means the master key is requested by invoking the `masterKey` function every time the master key is used internally by Parse Server. * @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