diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 8a54cbdc..9f5cc88d 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,10 @@ +# [7.1.0-alpha.5](https://github.com/parse-community/parse-server/compare/7.1.0-alpha.4...7.1.0-alpha.5) (2024-04-07) + + +### Features + +* Prevent Parse Server start in case of unknown option in server configuration ([#8987](https://github.com/parse-community/parse-server/issues/8987)) ([8758e6a](https://github.com/parse-community/parse-server/commit/8758e6abb9dbb68757bddcbd332ad25100c24a0e)) + # [7.1.0-alpha.4](https://github.com/parse-community/parse-server/compare/7.1.0-alpha.3...7.1.0-alpha.4) (2024-03-31) diff --git a/jsdoc-conf.json b/jsdoc-conf.json index efbaa0a3..52bb51f4 100644 --- a/jsdoc-conf.json +++ b/jsdoc-conf.json @@ -29,7 +29,7 @@ "template": "./node_modules/clean-jsdoc-theme", "theme_opts": { "default_theme": "dark", - "title": "", + "title": "", "create_style": "header, .sidebar-section-title, .sidebar-title { color: #139cee !important } .logo { margin-left : 40px; margin-right: 40px }" } }, diff --git a/package-lock.json b/package-lock.json index e10591eb..4d78e3e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parse-server", - "version": "7.1.0-alpha.4", + "version": "7.1.0-alpha.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "parse-server", - "version": "7.1.0-alpha.4", + "version": "7.1.0-alpha.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -15,7 +15,7 @@ "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", - "@parse/fs-files-adapter": "2.0.1", + "@parse/fs-files-adapter": "3.0.0", "@parse/push-adapter": "6.0.0", "bcryptjs": "2.4.3", "body-parser": "1.20.2", @@ -27,7 +27,7 @@ "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", - "graphql-relay": "0.10.0", + "graphql-relay": "0.10.1", "graphql-tag": "2.12.6", "graphql-upload": "15.0.2", "intersect": "1.0.1", @@ -60,7 +60,7 @@ }, "devDependencies": { "@actions/core": "1.10.1", - "@apollo/client": "3.9.5", + "@apollo/client": "3.9.11", "@babel/cli": "7.23.9", "@babel/core": "7.24.3", "@babel/plugin-proposal-object-rest-spread": "7.10.0", @@ -161,9 +161,9 @@ } }, "node_modules/@apollo/client": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.5.tgz", - "integrity": "sha512-7y+c8MTPU+hhTwvcGVtMMGIgWduzrvG1mz5yJMRyqYbheBkkky3Lki6ADWVSBXG1lZoOtPYvB2zDgVfKb2HSsw==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.11.tgz", + "integrity": "sha512-H7e9m7cRcFO93tokwzqrsbnfKorkpV24xU30hFH5u2g6B+c1DMo/ouyF/YrBPdrTzqxQCjTUmds/FLmJ7626GA==", "dev": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", @@ -174,7 +174,7 @@ "hoist-non-react-statics": "^3.3.2", "optimism": "^0.18.0", "prop-types": "^15.7.2", - "rehackt": "0.0.5", + "rehackt": "0.0.6", "response-iterator": "^0.2.6", "symbol-observable": "^4.0.0", "ts-invariant": "^0.10.3", @@ -3713,9 +3713,9 @@ } }, "node_modules/@parse/fs-files-adapter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-2.0.1.tgz", - "integrity": "sha512-9DY0T9lK73Ysw+wxxsBt9rpxWxJpMlHl/fTW175XSajusW0ZP5jERI3BTKeclV28eVmSU690EO2vnwCURsPZ7g==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-3.0.0.tgz", + "integrity": "sha512-Bb+qLtXQ/1SA2Ck6JLVhfD9JQf6cCwgeDZZJjcIdHzUtdPTFu1hj51xdD7tUCL47Ed2i3aAx6K/M6AjLWYVs3A==" }, "node_modules/@parse/node-apn": { "version": "6.0.1", @@ -8824,9 +8824,9 @@ "integrity": "sha512-q3prnhAL/dBsD+vaGr83B8DzkBijg+Yh+lbt7qp2dW1fpuO+q/upzDXvFJstVsSAA8m11MHGkSxxyxXeLou4MA==" }, "node_modules/graphql-relay": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.0.tgz", - "integrity": "sha512-44yBuw2/DLNEiMypbNZBt1yMDbBmyVPVesPywnteGGALiBmdyy1JP8jSg8ClLePg8ZZxk0O4BLhd1a6U/1jDOQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.1.tgz", + "integrity": "sha512-8AtwSe6B0/b4+YzynHr38PP7S+zX5Vs5LEo0BEzGCPq/THAiHa5H5ZLf3bRbKbok15ADxDQSsGJmlqXeJDDPIw==", "engines": { "node": "^12.20.0 || ^14.15.0 || >= 15.9.0" }, @@ -16909,9 +16909,9 @@ } }, "node_modules/rehackt": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.5.tgz", - "integrity": "sha512-BI1rV+miEkaHj8zd2n+gaMgzu/fKz7BGlb4zZ6HAiY9adDmJMkaDcmuXlJFv0eyKUob+oszs3/2gdnXUrzx2Tg==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.6.tgz", + "integrity": "sha512-l3WEzkt4ntlEc/IB3/mF6SRgNHA6zfQR7BlGOgBTOmx7IJJXojDASav+NsgXHFjHn+6RmwqsGPFgZpabWpeOdw==", "dev": true, "peerDependencies": { "@types/react": "*", @@ -19688,9 +19688,9 @@ "requires": {} }, "@apollo/client": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.5.tgz", - "integrity": "sha512-7y+c8MTPU+hhTwvcGVtMMGIgWduzrvG1mz5yJMRyqYbheBkkky3Lki6ADWVSBXG1lZoOtPYvB2zDgVfKb2HSsw==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.11.tgz", + "integrity": "sha512-H7e9m7cRcFO93tokwzqrsbnfKorkpV24xU30hFH5u2g6B+c1DMo/ouyF/YrBPdrTzqxQCjTUmds/FLmJ7626GA==", "dev": true, "requires": { "@graphql-typed-document-node/core": "^3.1.1", @@ -19701,7 +19701,7 @@ "hoist-non-react-statics": "^3.3.2", "optimism": "^0.18.0", "prop-types": "^15.7.2", - "rehackt": "0.0.5", + "rehackt": "0.0.6", "response-iterator": "^0.2.6", "symbol-observable": "^4.0.0", "ts-invariant": "^0.10.3", @@ -22250,9 +22250,9 @@ } }, "@parse/fs-files-adapter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-2.0.1.tgz", - "integrity": "sha512-9DY0T9lK73Ysw+wxxsBt9rpxWxJpMlHl/fTW175XSajusW0ZP5jERI3BTKeclV28eVmSU690EO2vnwCURsPZ7g==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@parse/fs-files-adapter/-/fs-files-adapter-3.0.0.tgz", + "integrity": "sha512-Bb+qLtXQ/1SA2Ck6JLVhfD9JQf6cCwgeDZZJjcIdHzUtdPTFu1hj51xdD7tUCL47Ed2i3aAx6K/M6AjLWYVs3A==" }, "@parse/node-apn": { "version": "6.0.1", @@ -26154,9 +26154,9 @@ "integrity": "sha512-q3prnhAL/dBsD+vaGr83B8DzkBijg+Yh+lbt7qp2dW1fpuO+q/upzDXvFJstVsSAA8m11MHGkSxxyxXeLou4MA==" }, "graphql-relay": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.0.tgz", - "integrity": "sha512-44yBuw2/DLNEiMypbNZBt1yMDbBmyVPVesPywnteGGALiBmdyy1JP8jSg8ClLePg8ZZxk0O4BLhd1a6U/1jDOQ==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/graphql-relay/-/graphql-relay-0.10.1.tgz", + "integrity": "sha512-8AtwSe6B0/b4+YzynHr38PP7S+zX5Vs5LEo0BEzGCPq/THAiHa5H5ZLf3bRbKbok15ADxDQSsGJmlqXeJDDPIw==", "requires": {} }, "graphql-tag": { @@ -32114,9 +32114,9 @@ } }, "rehackt": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.5.tgz", - "integrity": "sha512-BI1rV+miEkaHj8zd2n+gaMgzu/fKz7BGlb4zZ6HAiY9adDmJMkaDcmuXlJFv0eyKUob+oszs3/2gdnXUrzx2Tg==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.6.tgz", + "integrity": "sha512-l3WEzkt4ntlEc/IB3/mF6SRgNHA6zfQR7BlGOgBTOmx7IJJXojDASav+NsgXHFjHn+6RmwqsGPFgZpabWpeOdw==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index b3e8f3c7..e6f8fa72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "7.1.0-alpha.4", + "version": "7.1.0-alpha.5", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -24,7 +24,7 @@ "@graphql-tools/merge": "9.0.3", "@graphql-tools/schema": "10.0.3", "@graphql-tools/utils": "8.12.0", - "@parse/fs-files-adapter": "2.0.1", + "@parse/fs-files-adapter": "3.0.0", "@parse/push-adapter": "6.0.0", "bcryptjs": "2.4.3", "body-parser": "1.20.2", @@ -36,7 +36,7 @@ "follow-redirects": "1.15.6", "graphql": "16.8.1", "graphql-list-fields": "2.0.4", - "graphql-relay": "0.10.0", + "graphql-relay": "0.10.1", "graphql-tag": "2.12.6", "graphql-upload": "15.0.2", "intersect": "1.0.1", @@ -66,7 +66,7 @@ }, "devDependencies": { "@actions/core": "1.10.1", - "@apollo/client": "3.9.5", + "@apollo/client": "3.9.11", "@babel/cli": "7.23.9", "@babel/core": "7.24.3", "@babel/plugin-proposal-object-rest-spread": "7.10.0", diff --git a/release_docs.sh b/release_docs.sh index a9cc5bf3..0c7cc2b3 100755 --- a/release_docs.sh +++ b/release_docs.sh @@ -1,10 +1,17 @@ #!/bin/sh -e set -x +# GITHUB_ACTIONS=true SOURCE_TAG=test ./release_docs.sh + if [ "${GITHUB_ACTIONS}" = "" ]; then echo "Cannot release docs without GITHUB_ACTIONS set" exit 0; fi +if [ "${SOURCE_TAG}" = "" ]; +then + echo "Cannot release docs without SOURCE_TAG set" + exit 0; +fi REPO="https://github.com/parse-community/parse-server" rm -rf docs @@ -13,20 +20,20 @@ cd docs git pull origin gh-pages cd .. -DEST="master" +RELEASE="release" +VERSION="${SOURCE_TAG}" -if [ "${SOURCE_TAG}" != "" ]; -then - DEST="${SOURCE_TAG}" - # change the default page to the latest - echo "" > "docs/api/index.html" -fi +# change the default page to the latest +echo "" > "docs/api/index.html" npm run definitions npm run docs -mkdir -p "docs/api/${DEST}" -cp -R out/* "docs/api/${DEST}" +mkdir -p "docs/api/${RELEASE}" +cp -R out/* "docs/api/${RELEASE}" + +mkdir -p "docs/api/${VERSION}" +cp -R out/* "docs/api/${VERSION}" # Copy other resources RESOURCE_DIR=".github" diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index 0be6e008..72a37cb9 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -254,6 +254,23 @@ function inject(t, list) { if (action) { props.push(t.objectProperty(t.stringLiteral('action'), action)); } + + if (t.isGenericTypeAnnotation(elt)) { + if (elt.typeAnnotation.id.name in nestedOptionEnvPrefix) { + props.push( + t.objectProperty(t.stringLiteral('type'), t.stringLiteral(elt.typeAnnotation.id.name)) + ); + } + } else if (t.isArrayTypeAnnotation(elt)) { + const elementType = elt.typeAnnotation.elementType; + if (t.isGenericTypeAnnotation(elementType)) { + if (elementType.id.name in nestedOptionEnvPrefix) { + props.push( + t.objectProperty(t.stringLiteral('type'), t.stringLiteral(elementType.id.name + '[]')) + ); + } + } + } if (elt.defaultValue) { let parsedValue = parseDefaultValue(elt, elt.defaultValue, t); if (!parsedValue) { diff --git a/spec/ParseConfigKey.spec.js b/spec/ParseConfigKey.spec.js new file mode 100644 index 00000000..84b2fc6e --- /dev/null +++ b/spec/ParseConfigKey.spec.js @@ -0,0 +1,52 @@ +const Config = require('../lib/Config'); +const ParseServer = require('../lib/index').ParseServer; + +describe('Config Keys', () => { + const tests = [ + { + name: 'Invalid Root Keys', + options: { unknow: 'val', masterKeyIPs: '' }, + error: 'unknow, masterKeyIPs', + }, + { name: 'Invalid Schema Keys', options: { schema: { Strict: 'val' } }, error: 'schema.Strict' }, + { + name: 'Invalid Pages Keys', + options: { pages: { customUrls: { EmailVerificationSendFail: 'val' } } }, + error: 'pages.customUrls.EmailVerificationSendFail', + }, + { + name: 'Invalid LiveQueryServerOptions Keys', + options: { liveQueryServerOptions: { MasterKey: 'value' } }, + error: 'liveQueryServerOptions.MasterKey', + }, + { + name: 'Invalid RateLimit Keys - Array Item', + options: { rateLimit: [{ RequestPath: '' }, { RequestTimeWindow: '' }] }, + error: 'rateLimit[0].RequestPath, rateLimit[1].RequestTimeWindow', + }, + ]; + + tests.forEach(test => { + it(test.name, async () => { + const logger = require('../lib/logger').logger; + spyOn(logger, 'error').and.callThrough(); + spyOn(Config, 'validateOptions').and.callFake(() => {}); + + new ParseServer({ + ...defaultConfiguration, + ...test.options, + }); + expect(logger.error).toHaveBeenCalledWith(`Invalid Option Keys Found: ${test.error}`); + }); + }); + + it('should run fine', async () => { + try { + await reconfigureServer({ + ...defaultConfiguration, + }); + } catch (err) { + fail('Should run without error'); + } + }); +}); diff --git a/src/Config.js b/src/Config.js index 933cf398..5dfd3f95 100644 --- a/src/Config.js +++ b/src/Config.js @@ -64,6 +64,7 @@ export class Config { } static validateOptions({ + customPages, publicServerURL, revokeSessionOnPasswordReset, expireInactiveSessions, @@ -133,9 +134,18 @@ export class Config { this.validateRateLimit(rateLimit); this.validateLogLevels(logLevels); this.validateDatabaseOptions(databaseOptions); + this.validateCustomPages(customPages); this.validateAllowClientClassCreation(allowClientClassCreation); } + static validateCustomPages(customPages) { + if (!customPages) return; + + if (Object.prototype.toString.call(customPages) !== '[object Object]') { + throw Error('Parse Server option customPages must be an object.'); + } + } + static validateControllers({ verifyUserEmails, userController, @@ -569,6 +579,7 @@ export class Config { if (Object.prototype.toString.call(databaseOptions) !== '[object Object]') { throw `databaseOptions must be an object`; } + if (databaseOptions.enableSchemaHooks === undefined) { databaseOptions.enableSchemaHooks = DatabaseOptions.enableSchemaHooks.default; } else if (typeof databaseOptions.enableSchemaHooks !== 'boolean') { diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 6fbd358f..38e1d52d 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -15,6 +15,4 @@ * * If there are no deprecations, this must return an empty array. */ -module.exports = [ - { optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' }, -]; +module.exports = [{ optionKey: 'encodeParseObjectInCloudFunction', changeNewDefault: 'true' }]; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index ee1137b7..27baac22 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -54,6 +54,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_ACCOUNT_LOCKOUT', help: 'The account lockout policy for failed login attempts.', action: parsers.objectParser, + type: 'AccountLockoutOptions', }, allowClientClassCreation: { env: 'PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION', @@ -157,6 +158,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_CUSTOM_PAGES', help: 'custom pages for password validation and reset', action: parsers.objectParser, + type: 'CustomPagesOptions', default: {}, }, databaseAdapter: { @@ -169,6 +171,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_DATABASE_OPTIONS', help: 'Options to pass to the database client', action: parsers.objectParser, + type: 'DatabaseOptions', }, databaseURI: { env: 'PARSE_SERVER_DATABASE_URI', @@ -273,6 +276,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_FILE_UPLOAD_OPTIONS', help: 'Options for file uploads', action: parsers.objectParser, + type: 'FileUploadOptions', default: {}, }, graphQLPath: { @@ -294,6 +298,7 @@ module.exports.ParseServerOptions = { help: 'Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.', action: parsers.objectParser, + type: 'IdempotencyOptions', default: {}, }, javascriptKey: { @@ -309,11 +314,13 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_LIVE_QUERY', help: "parse-server's LiveQuery configuration object", action: parsers.objectParser, + type: 'LiveQueryOptions', }, liveQueryServerOptions: { env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS', help: 'Live query server configuration options (will start the liveQuery server)', action: parsers.objectParser, + type: 'LiveQueryServerOptions', }, loggerAdapter: { env: 'PARSE_SERVER_LOGGER_ADAPTER', @@ -328,6 +335,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_LOG_LEVELS', help: '(Optional) Overrides the log levels used internally by Parse Server to log events.', action: parsers.objectParser, + type: 'LogLevels', default: {}, }, logsFolder: { @@ -408,12 +416,14 @@ module.exports.ParseServerOptions = { help: 'The options for pages such as password reset and email verification. Caution, this is an experimental feature that may not be appropriate for production.', action: parsers.objectParser, + type: 'PagesOptions', default: {}, }, passwordPolicy: { env: 'PARSE_SERVER_PASSWORD_POLICY', help: 'The password policy for enforcing password related rules.', action: parsers.objectParser, + type: 'PasswordPolicyOptions', }, playgroundPath: { env: 'PARSE_SERVER_PLAYGROUND_PATH', @@ -471,6 +481,7 @@ module.exports.ParseServerOptions = { help: "Options to limit repeated requests to Parse Server APIs. This can be used to protect sensitive endpoints such as `/requestPasswordReset` from brute-force attacks or Parse Server as a whole from denial-of-service (DoS) attacks.

\u2139\uFE0F Mind the following limitations:
- rate limits applied per IP address; this limits protection against distributed denial-of-service (DDoS) attacks where many requests are coming from various IP addresses
- if multiple Parse Server instances are behind a load balancer or ran in a cluster, each instance will calculate it's own request rates, independent from other instances; this limits the applicability of this feature when using a load balancer and another rate limiting solution that takes requests across all instances into account may be more suitable
- this feature provides basic protection against denial-of-service attacks, but a more sophisticated solution works earlier in the request flow and prevents a malicious requests to even reach a server instance; it's therefore recommended to implement a solution according to architecture and user case.", action: parsers.arrayParser, + type: 'RateLimitOptions[]', default: [], }, readOnlyMasterKey: { @@ -516,11 +527,13 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_SCHEMA', help: 'Defined schema', action: parsers.objectParser, + type: 'SchemaOptions', }, security: { env: 'PARSE_SERVER_SECURITY', help: 'The security options to identify and report weak security settings.', action: parsers.objectParser, + type: 'SecurityOptions', default: {}, }, sendUserEmailVerification: { @@ -665,12 +678,14 @@ module.exports.PagesOptions = { env: 'PARSE_SERVER_PAGES_CUSTOM_ROUTES', help: 'The custom routes.', action: parsers.arrayParser, + type: 'PagesRoute[]', default: [], }, customUrls: { env: 'PARSE_SERVER_PAGES_CUSTOM_URLS', help: 'The URLs to the custom pages.', action: parsers.objectParser, + type: 'PagesCustomUrlsOptions', default: {}, }, enableLocalization: { diff --git a/src/ParseServer.js b/src/ParseServer.js index 85a8acc4..e8c7078a 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -45,6 +45,7 @@ import { SecurityRouter } from './Routers/SecurityRouter'; import CheckRunner from './Security/CheckRunner'; import Deprecator from './Deprecator/Deprecator'; import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas'; +import OptionsDefinitions from './Options/Definitions'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -59,6 +60,58 @@ class ParseServer { constructor(options: ParseServerOptions) { // Scan for deprecated Parse Server options Deprecator.scanParseServerOptions(options); + + const interfaces = JSON.parse(JSON.stringify(OptionsDefinitions)); + + function getValidObject(root) { + const result = {}; + for (const key in root) { + if (Object.prototype.hasOwnProperty.call(root[key], 'type')) { + if (root[key].type.endsWith('[]')) { + result[key] = [getValidObject(interfaces[root[key].type.slice(0, -2)])]; + } else { + result[key] = getValidObject(interfaces[root[key].type]); + } + } else { + result[key] = ''; + } + } + return result; + } + + const optionsBlueprint = getValidObject(interfaces['ParseServerOptions']); + + function validateKeyNames(original, ref, name = '') { + let result = []; + const prefix = name + (name !== '' ? '.' : ''); + for (const key in original) { + if (!Object.prototype.hasOwnProperty.call(ref, key)) { + result.push(prefix + key); + } else { + if (ref[key] === '') continue; + let res = []; + if (Array.isArray(original[key]) && Array.isArray(ref[key])) { + const type = ref[key][0]; + original[key].forEach((item, idx) => { + if (typeof item === 'object' && item !== null) { + res = res.concat(validateKeyNames(item, type, prefix + key + `[${idx}]`)); + } + }); + } else if (typeof original[key] === 'object' && typeof ref[key] === 'object') { + res = validateKeyNames(original[key], ref[key], prefix + key); + } + result = result.concat(res); + } + } + return result; + } + + const diff = validateKeyNames(options, optionsBlueprint); + if (diff.length > 0) { + const logger = logging.logger; + logger.error(`Invalid Option Keys Found: ${diff.join(', ')}`); + } + // Set option defaults injectDefaults(options); const { @@ -70,9 +123,9 @@ class ParseServer { // Initialize the node client SDK automatically Parse.initialize(appId, javascriptKey || 'unused', masterKey); Parse.serverURL = serverURL; - Config.validateOptions(options); const allControllers = controllers.getControllers(options); + options.state = 'initialized'; this.config = Config.put(Object.assign({}, options, allControllers)); this.config.masterKeyIpsStore = new Map();