diff --git a/package-lock.json b/package-lock.json index deed9e6054..a71afdc1d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "all-node-versions": "11.3.0", "apollo-upload-client": "17.0.0", "bcrypt-nodejs": "0.0.3", - "clean-jsdoc-theme": "^4.2.7", + "clean-jsdoc-theme": "4.2.7", "cross-env": "7.0.2", "deep-diff": "1.0.2", "eslint": "8.26.0", diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index e0d33daa4b..9a92238f3d 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -285,6 +285,9 @@ function inject(t, list) { type = 'String|String[]'; } comments += ` * @property {${type}} ${elt.name} ${elt.help}\n`; + if (nestedOptionTypes.includes(type) && type !== 'Object') { + props.push(t.objectProperty(t.stringLiteral('group'), t.stringLiteral(`${type}`))); + } const obj = t.objectExpression(props); return t.objectProperty(t.stringLiteral(elt.name), obj); }) diff --git a/spec/SecurityCheck.spec.js b/spec/SecurityCheck.spec.js index 647ed909c0..6e0c6650fb 100644 --- a/spec/SecurityCheck.spec.js +++ b/spec/SecurityCheck.spec.js @@ -80,7 +80,7 @@ describe('Security Check', () => { describe('server options', () => { it('uses default configuration when none is set', async () => { - await reconfigureServerWithSecurityConfig({}); + await reconfigureServer(); expect(Config.get(Parse.applicationId).security.enableCheck).toBe( Definitions.SecurityOptions.enableCheck.default ); diff --git a/spec/index.spec.js b/spec/index.spec.js index 66654aaec4..16444eb66b 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -446,6 +446,42 @@ describe('server', () => { }); }); + it('default interface keys are set', async () => { + delete process.env.PARSE_SERVER_PASSWORD_POLICY_RESET_TOKEN_REUSE_IF_VALID; + delete process.env.PARSE_SERVER_PASSWORD_POLICY_RESET_TOKEN_VALIDITY_DURATION; + await reconfigureServer(); + const config = Config.get('test'); + expect(config.passwordPolicy.resetTokenReuseIfValid).toBeDefined(); + expect(config.passwordPolicy.resetTokenReuseIfValid).toBeFalse(); + }); + + it('can set subkeys via environment variables', async () => { + process.env.PARSE_SERVER_PASSWORD_POLICY_RESET_TOKEN_REUSE_IF_VALID = true; + process.env.PARSE_SERVER_PASSWORD_POLICY_RESET_TOKEN_VALIDITY_DURATION = 3000; + await reconfigureServer(); + const config = Config.get('test'); + expect(config.passwordPolicy.resetTokenReuseIfValid).toBeDefined(); + expect(config.passwordPolicy.resetTokenReuseIfValid).toBeTrue(); + }); + + it('can set throw on invalid type', async () => { + await expectAsync( + reconfigureServer({ + revokeSessionOnPasswordReset: [], + }) + ).toBeRejectedWith('revokeSessionOnPasswordReset must be a boolean value.'); + }); + + it('can set throw on invalid subkeys', async () => { + await expectAsync( + reconfigureServer({ + fileUpload: { + enableForAnonymousUser: [], + }, + }) + ).toBeRejectedWith('fileUpload.enableForAnonymousUser must be a boolean value.'); + }); + it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => { reconfigureServer({ revokeSessionOnPasswordReset: 'non-bool' }).catch(done); }); diff --git a/src/Config.js b/src/Config.js index 747af78f82..b84230ea1a 100644 --- a/src/Config.js +++ b/src/Config.js @@ -2,22 +2,12 @@ // configured. // mount is the URL for the root of the API; includes http, domain, etc. -import { isBoolean, isString } from 'lodash'; import net from 'net'; import AppCache from './cache'; import DatabaseController from './Controllers/DatabaseController'; import { logLevels as validLogLevels } from './Controllers/LoggerController'; -import { - AccountLockoutOptions, - DatabaseOptions, - FileUploadOptions, - IdempotencyOptions, - LogLevels, - PagesOptions, - ParseServerOptions, - SchemaOptions, - SecurityOptions, -} from './Options/Definitions'; +import Definitions from './Options/Definitions'; +const { LogLevels, ParseServerOptions } = Definitions; function removeTrailingSlash(str) { if (!str) { @@ -53,6 +43,7 @@ export class Config { } static put(serverConfiguration) { + Config._validateTypes(serverConfiguration); Config.validateOptions(serverConfiguration); Config.validateControllers(serverConfiguration); AppCache.put(serverConfiguration.appId, serverConfiguration); @@ -62,7 +53,6 @@ export class Config { static validateOptions({ publicServerURL, - revokeSessionOnPasswordReset, expireInactiveSessions, sessionLength, defaultLimit, @@ -76,17 +66,7 @@ export class Config { readOnlyMasterKey, allowHeaders, idempotencyOptions, - fileUpload, - pages, - security, - enforcePrivateUsers, - schema, - requestKeywordDenylist, - allowExpiredAuthDataToken, logLevels, - rateLimit, - databaseOptions, - extendSessionOnUse, }) { if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); @@ -98,15 +78,6 @@ export class Config { this.validateAccountLockoutPolicy(accountLockout); this.validatePasswordPolicy(passwordPolicy); - this.validateFileUploadOptions(fileUpload); - - if (typeof revokeSessionOnPasswordReset !== 'boolean') { - throw 'revokeSessionOnPasswordReset must be a boolean value'; - } - - if (typeof extendSessionOnUse !== 'boolean') { - throw 'extendSessionOnUse must be a boolean value'; - } if (publicServerURL) { if (!publicServerURL.startsWith('http://') && !publicServerURL.startsWith('https://')) { @@ -120,15 +91,7 @@ export class Config { this.validateMaxLimit(maxLimit); this.validateAllowHeaders(allowHeaders); this.validateIdempotencyOptions(idempotencyOptions); - this.validatePagesOptions(pages); - this.validateSecurityOptions(security); - this.validateSchemaOptions(schema); - this.validateEnforcePrivateUsers(enforcePrivateUsers); - this.validateAllowExpiredAuthDataToken(allowExpiredAuthDataToken); - this.validateRequestKeywordDenylist(requestKeywordDenylist); - this.validateRateLimit(rateLimit); this.validateLogLevels(logLevels); - this.validateDatabaseOptions(databaseOptions); } static validateControllers({ @@ -151,140 +114,53 @@ export class Config { } } - static validateRequestKeywordDenylist(requestKeywordDenylist) { - if (requestKeywordDenylist === undefined) { - requestKeywordDenylist = requestKeywordDenylist.default; - } else if (!Array.isArray(requestKeywordDenylist)) { - throw 'Parse Server option requestKeywordDenylist must be an array.'; - } - } - - static validateEnforcePrivateUsers(enforcePrivateUsers) { - if (typeof enforcePrivateUsers !== 'boolean') { - throw 'Parse Server option enforcePrivateUsers must be a boolean.'; - } - } - - static validateAllowExpiredAuthDataToken(allowExpiredAuthDataToken) { - if (typeof allowExpiredAuthDataToken !== 'boolean') { - throw 'Parse Server option allowExpiredAuthDataToken must be a boolean.'; - } - } - - static validateSecurityOptions(security) { - if (Object.prototype.toString.call(security) !== '[object Object]') { - throw 'Parse Server option security must be an object.'; - } - if (security.enableCheck === undefined) { - security.enableCheck = SecurityOptions.enableCheck.default; - } else if (!isBoolean(security.enableCheck)) { - throw 'Parse Server option security.enableCheck must be a boolean.'; - } - if (security.enableCheckLog === undefined) { - security.enableCheckLog = SecurityOptions.enableCheckLog.default; - } else if (!isBoolean(security.enableCheckLog)) { - throw 'Parse Server option security.enableCheckLog must be a boolean.'; - } - } - - static validateSchemaOptions(schema: SchemaOptions) { - if (!schema) return; - if (Object.prototype.toString.call(schema) !== '[object Object]') { - throw 'Parse Server option schema must be an object.'; - } - if (schema.definitions === undefined) { - schema.definitions = SchemaOptions.definitions.default; - } else if (!Array.isArray(schema.definitions)) { - throw 'Parse Server option schema.definitions must be an array.'; - } - if (schema.strict === undefined) { - schema.strict = SchemaOptions.strict.default; - } else if (!isBoolean(schema.strict)) { - throw 'Parse Server option schema.strict must be a boolean.'; - } - if (schema.deleteExtraFields === undefined) { - schema.deleteExtraFields = SchemaOptions.deleteExtraFields.default; - } else if (!isBoolean(schema.deleteExtraFields)) { - throw 'Parse Server option schema.deleteExtraFields must be a boolean.'; - } - if (schema.recreateModifiedFields === undefined) { - schema.recreateModifiedFields = SchemaOptions.recreateModifiedFields.default; - } else if (!isBoolean(schema.recreateModifiedFields)) { - throw 'Parse Server option schema.recreateModifiedFields must be a boolean.'; - } - if (schema.lockSchemas === undefined) { - schema.lockSchemas = SchemaOptions.lockSchemas.default; - } else if (!isBoolean(schema.lockSchemas)) { - throw 'Parse Server option schema.lockSchemas must be a boolean.'; - } - if (schema.beforeMigration === undefined) { - schema.beforeMigration = null; - } else if (schema.beforeMigration !== null && typeof schema.beforeMigration !== 'function') { - throw 'Parse Server option schema.beforeMigration must be a function.'; - } - if (schema.afterMigration === undefined) { - schema.afterMigration = null; - } else if (schema.afterMigration !== null && typeof schema.afterMigration !== 'function') { - throw 'Parse Server option schema.afterMigration must be a function.'; - } - } + static _validateTypes(serverOpts) { + const getType = fn => { + if (Array.isArray(fn)) { + return 'array'; + } + if (fn === 'Any' || fn === 'function') { + return fn; + } + const type = typeof fn; + if (typeof fn === 'function') { + const match = fn && fn.toString().match(/^\s*function (\w+)/); + return (match ? match[1] : 'function').toLowerCase(); + } + return type; + }; - static validatePagesOptions(pages) { - if (Object.prototype.toString.call(pages) !== '[object Object]') { - throw 'Parse Server option pages must be an object.'; - } - if (pages.enableRouter === undefined) { - pages.enableRouter = PagesOptions.enableRouter.default; - } else if (!isBoolean(pages.enableRouter)) { - throw 'Parse Server option pages.enableRouter must be a boolean.'; - } - if (pages.enableLocalization === undefined) { - pages.enableLocalization = PagesOptions.enableLocalization.default; - } else if (!isBoolean(pages.enableLocalization)) { - throw 'Parse Server option pages.enableLocalization must be a boolean.'; - } - if (pages.localizationJsonPath === undefined) { - pages.localizationJsonPath = PagesOptions.localizationJsonPath.default; - } else if (!isString(pages.localizationJsonPath)) { - throw 'Parse Server option pages.localizationJsonPath must be a string.'; - } - if (pages.localizationFallbackLocale === undefined) { - pages.localizationFallbackLocale = PagesOptions.localizationFallbackLocale.default; - } else if (!isString(pages.localizationFallbackLocale)) { - throw 'Parse Server option pages.localizationFallbackLocale must be a string.'; - } - if (pages.placeholders === undefined) { - pages.placeholders = PagesOptions.placeholders.default; - } else if ( - Object.prototype.toString.call(pages.placeholders) !== '[object Object]' && - typeof pages.placeholders !== 'function' - ) { - throw 'Parse Server option pages.placeholders must be an object or a function.'; - } - if (pages.forceRedirect === undefined) { - pages.forceRedirect = PagesOptions.forceRedirect.default; - } else if (!isBoolean(pages.forceRedirect)) { - throw 'Parse Server option pages.forceRedirect must be a boolean.'; - } - if (pages.pagesPath === undefined) { - pages.pagesPath = PagesOptions.pagesPath.default; - } else if (!isString(pages.pagesPath)) { - throw 'Parse Server option pages.pagesPath must be a string.'; - } - if (pages.pagesEndpoint === undefined) { - pages.pagesEndpoint = PagesOptions.pagesEndpoint.default; - } else if (!isString(pages.pagesEndpoint)) { - throw 'Parse Server option pages.pagesEndpoint must be a string.'; - } - if (pages.customUrls === undefined) { - pages.customUrls = PagesOptions.customUrls.default; - } else if (Object.prototype.toString.call(pages.customUrls) !== '[object Object]') { - throw 'Parse Server option pages.customUrls must be an object.'; - } - if (pages.customRoutes === undefined) { - pages.customRoutes = PagesOptions.customRoutes.default; - } else if (!(pages.customRoutes instanceof Array)) { - throw 'Parse Server option pages.customRoutes must be an array.'; + const checkKey = (setValue, path, action, original) => { + if (setValue === undefined || !action) { + return; + } + const requiredType = getType(original == null ? action(setValue) : original); + const thisType = getType(setValue); + if (requiredType !== thisType) { + throw `${path} must be a${ + requiredType.charAt(0).match(/[aeiou]/i) ? 'n' : '' + } ${requiredType} value.`; + } + if (requiredType === 'number' && isNaN(setValue)) { + throw `${path} must be a valid number.`; + } + }; + for (const key in ParseServerOptions) { + const definition = ParseServerOptions[key]; + const setValue = serverOpts[key]; + checkKey(setValue, key, definition.action, definition.default); + if (setValue && definition.group) { + const group = Definitions[definition.group]; + for (const subkey in group) { + const subdefinition = group[subkey]; + checkKey( + setValue[subkey], + `${key}.${subkey}`, + subdefinition.action, + subdefinition.default + ); + } + } } } @@ -292,18 +168,11 @@ export class Config { if (!idempotencyOptions) { return; } - if (idempotencyOptions.ttl === undefined) { - idempotencyOptions.ttl = IdempotencyOptions.ttl.default; - } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { + if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { throw 'idempotency TTL value must be greater than 0 seconds'; } else if (isNaN(idempotencyOptions.ttl)) { throw 'idempotency TTL value must be a number'; } - if (!idempotencyOptions.paths) { - idempotencyOptions.paths = IdempotencyOptions.paths.default; - } else if (!(idempotencyOptions.paths instanceof Array)) { - throw 'idempotency paths must be of an array of strings'; - } } static validateAccountLockoutPolicy(accountLockout) { @@ -323,12 +192,6 @@ export class Config { ) { throw 'Account lockout threshold should be an integer greater than 0 and less than 1000'; } - - if (accountLockout.unlockOnPasswordReset === undefined) { - accountLockout.unlockOnPasswordReset = AccountLockoutOptions.unlockOnPasswordReset.default; - } else if (!isBoolean(accountLockout.unlockOnPasswordReset)) { - throw 'Parse Server option accountLockout.unlockOnPasswordReset must be a boolean.'; - } } } @@ -439,34 +302,6 @@ export class Config { } } - static validateFileUploadOptions(fileUpload) { - try { - if (fileUpload == null || typeof fileUpload !== 'object' || fileUpload instanceof Array) { - throw 'fileUpload must be an object value.'; - } - } catch (e) { - if (e instanceof ReferenceError) { - return; - } - throw e; - } - if (fileUpload.enableForAnonymousUser === undefined) { - fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default; - } else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') { - throw 'fileUpload.enableForAnonymousUser must be a boolean value.'; - } - if (fileUpload.enableForPublic === undefined) { - fileUpload.enableForPublic = FileUploadOptions.enableForPublic.default; - } else if (typeof fileUpload.enableForPublic !== 'boolean') { - throw 'fileUpload.enableForPublic must be a boolean value.'; - } - if (fileUpload.enableForAuthenticatedUser === undefined) { - fileUpload.enableForAuthenticatedUser = FileUploadOptions.enableForAuthenticatedUser.default; - } else if (typeof fileUpload.enableForAuthenticatedUser !== 'boolean') { - throw 'fileUpload.enableForAuthenticatedUser must be a boolean value.'; - } - } - static validateIps(field, masterKeyIps) { for (let ip of masterKeyIps) { if (ip.includes('/')) { @@ -501,12 +336,6 @@ export class Config { } static validateDefaultLimit(defaultLimit) { - if (defaultLimit == null) { - defaultLimit = ParseServerOptions.defaultLimit.default; - } - if (typeof defaultLimit !== 'number') { - throw 'Default limit must be a number.'; - } if (defaultLimit <= 0) { throw 'Default limit must be a value greater than 0.'; } @@ -536,73 +365,8 @@ export class Config { static validateLogLevels(logLevels) { for (const key of Object.keys(LogLevels)) { - if (logLevels[key]) { - if (validLogLevels.indexOf(logLevels[key]) === -1) { - throw `'${key}' must be one of ${JSON.stringify(validLogLevels)}`; - } - } else { - logLevels[key] = LogLevels[key].default; - } - } - } - - static validateDatabaseOptions(databaseOptions) { - if (databaseOptions == undefined) { - return; - } - 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') { - throw `databaseOptions.enableSchemaHooks must be a boolean`; - } - if (databaseOptions.schemaCacheTtl === undefined) { - databaseOptions.schemaCacheTtl = DatabaseOptions.schemaCacheTtl.default; - } else if (typeof databaseOptions.schemaCacheTtl !== 'number') { - throw `databaseOptions.schemaCacheTtl must be a number`; - } - } - - static validateRateLimit(rateLimit) { - if (!rateLimit) { - return; - } - if ( - Object.prototype.toString.call(rateLimit) !== '[object Object]' && - !Array.isArray(rateLimit) - ) { - throw `rateLimit must be an array or object`; - } - const options = Array.isArray(rateLimit) ? rateLimit : [rateLimit]; - for (const option of options) { - if (Object.prototype.toString.call(option) !== '[object Object]') { - throw `rateLimit must be an array of objects`; - } - if (option.requestPath == null) { - throw `rateLimit.requestPath must be defined`; - } - if (typeof option.requestPath !== 'string') { - throw `rateLimit.requestPath must be a string`; - } - if (option.requestTimeWindow == null) { - throw `rateLimit.requestTimeWindow must be defined`; - } - if (typeof option.requestTimeWindow !== 'number') { - throw `rateLimit.requestTimeWindow must be a number`; - } - if (option.includeInternalRequests && typeof option.includeInternalRequests !== 'boolean') { - throw `rateLimit.includeInternalRequests must be a boolean`; - } - if (option.requestCount == null) { - throw `rateLimit.requestCount must be defined`; - } - if (typeof option.requestCount !== 'number') { - throw `rateLimit.requestCount must be a number`; - } - if (option.errorResponseMessage && typeof option.errorResponseMessage !== 'string') { - throw `rateLimit.errorResponseMessage must be a string`; + if (validLogLevels.indexOf(logLevels[key]) === -1) { + throw `'${key}' must be one of ${JSON.stringify(validLogLevels)}`; } } } diff --git a/src/Controllers/index.js b/src/Controllers/index.js index 0a9b3db57d..9ff82352de 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -153,7 +153,8 @@ export function getDatabaseController(options: ParseServerOptions): DatabaseCont const { databaseURI, collectionPrefix, databaseOptions } = options; let { databaseAdapter } = options; if ( - (databaseOptions || + ((databaseOptions && + JSON.stringify(databaseOptions) !== JSON.stringify(defaults.databaseOptions)) || (databaseURI && databaseURI !== defaults.databaseURI) || collectionPrefix !== defaults.collectionPrefix) && databaseAdapter diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index a583c38c24..0b1567deaf 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -103,7 +103,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_AUTH_PROVIDERS', help: 'Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication', - action: parsers.arrayParser, + action: parsers.objectParser, }, cacheAdapter: { env: 'PARSE_SERVER_CACHE_ADAPTER', @@ -145,6 +145,7 @@ module.exports.ParseServerOptions = { help: 'custom pages for password validation and reset', action: parsers.objectParser, default: {}, + group: 'CustomPagesOptions', }, databaseAdapter: { env: 'PARSE_SERVER_DATABASE_ADAPTER', @@ -156,6 +157,7 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_DATABASE_OPTIONS', help: 'Options to pass to the database client', action: parsers.objectParser, + group: 'DatabaseOptions', }, databaseURI: { env: 'PARSE_SERVER_DATABASE_URI', @@ -247,6 +249,7 @@ module.exports.ParseServerOptions = { help: 'Options for file uploads', action: parsers.objectParser, default: {}, + group: 'FileUploadOptions', }, graphQLPath: { env: 'PARSE_SERVER_GRAPHQL_PATH', @@ -268,6 +271,7 @@ module.exports.ParseServerOptions = { '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, default: {}, + group: 'IdempotencyOptions', }, javascriptKey: { env: 'PARSE_SERVER_JAVASCRIPT_KEY', @@ -302,6 +306,7 @@ module.exports.ParseServerOptions = { help: '(Optional) Overrides the log levels used internally by Parse Server to log events.', action: parsers.objectParser, default: {}, + group: 'LogLevels', }, logsFolder: { env: 'PARSE_SERVER_LOGS_FOLDER', @@ -382,11 +387,13 @@ module.exports.ParseServerOptions = { '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, default: {}, + group: 'PagesOptions', }, passwordPolicy: { env: 'PARSE_SERVER_PASSWORD_POLICY', help: 'The password policy for enforcing password related rules.', action: parsers.objectParser, + group: 'PasswordPolicyOptions', }, playgroundPath: { env: 'PARSE_SERVER_PLAYGROUND_PATH', @@ -482,12 +489,14 @@ module.exports.ParseServerOptions = { env: 'PARSE_SERVER_SCHEMA', help: 'Defined schema', action: parsers.objectParser, + group: 'SchemaOptions', }, security: { env: 'PARSE_SERVER_SECURITY', help: 'The security options to identify and report weak security settings.', action: parsers.objectParser, default: {}, + group: 'SecurityOptions', }, serverCloseComplete: { env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE', @@ -628,6 +637,7 @@ module.exports.PagesOptions = { help: 'The URLs to the custom pages.', action: parsers.objectParser, default: {}, + group: 'PagesCustomUrlsOptions', }, enableLocalization: { env: 'PARSE_SERVER_PAGES_ENABLE_LOCALIZATION', diff --git a/src/Options/docs.js b/src/Options/docs.js index 856707e0fa..1173426675 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -20,7 +20,7 @@ * @property {Adapter} analyticsAdapter Adapter module for the analytics * @property {String} appId Your Parse Application ID * @property {String} appName Sets the app name - * @property {AuthAdapter[]} auth Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication + * @property {Auth} auth Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication * @property {Adapter} cacheAdapter Adapter module for the cache * @property {Number} cacheMaxSize Sets the maximum size for the in memory cache, defaults to 10000 * @property {Number} cacheTTL Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds) diff --git a/src/Options/index.js b/src/Options/index.js index 0411563a8a..9673236955 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -41,6 +41,10 @@ type RequestKeywordDenylist = { value: any, }; +type Auth = { + [string]: AuthAdapter, +}; + export interface ParseServerOptions { /* Your Parse Application ID :ENV: PARSE_SERVER_APPLICATION_ID */ @@ -149,7 +153,7 @@ export interface ParseServerOptions { allowCustomObjectId: ?boolean; /* Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication :ENV: PARSE_SERVER_AUTH_PROVIDERS */ - auth: ?(AuthAdapter[]); + auth: ?Auth; /* Max file size for uploads, defaults to 20mb :DEFAULT: 20mb */ maxUploadSize: ?string; diff --git a/src/Options/parsers.js b/src/Options/parsers.js index 812c1bc21f..1fa7e54d3a 100644 --- a/src/Options/parsers.js +++ b/src/Options/parsers.js @@ -36,7 +36,7 @@ function arrayParser(opt) { } else if (typeof opt === 'string') { return opt.split(','); } else { - throw new Error(`${opt} should be a comma separated string or an array`); + throw new Error(`${JSON.stringify(opt)} should be a comma separated string or an array`); } } diff --git a/src/ParseServer.js b/src/ParseServer.js index 04379ecfd3..8169ae6206 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -10,7 +10,7 @@ var batch = require('./batch'), fs = require('fs'); import { ParseServerOptions, LiveQueryServerOptions } from './Options'; -import defaults from './defaults'; +import { getDefaults } from './defaults'; import * as logging from './logger'; import Config from './Config'; import PromiseRouter from './PromiseRouter'; @@ -453,6 +453,7 @@ function addParseCloud() { } function injectDefaults(options: ParseServerOptions) { + const defaults = getDefaults(); Object.keys(defaults).forEach(key => { if (!Object.prototype.hasOwnProperty.call(options, key)) { options[key] = defaults[key]; diff --git a/src/defaults.js b/src/defaults.js index a2b105d8db..2e3daed598 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -1,35 +1,58 @@ import { nullParser } from './Options/parsers'; -const { ParseServerOptions } = require('./Options/Definitions'); -const logsFolder = (() => { - let folder = './logs/'; - if (typeof process !== 'undefined' && process.env.TESTING === '1') { - folder = './test_logs/'; - } - if (process.env.PARSE_SERVER_LOGS_FOLDER) { - folder = nullParser(process.env.PARSE_SERVER_LOGS_FOLDER); - } - return folder; -})(); +const ServerOptions = require('./Options/Definitions'); +const { ParseServerOptions } = ServerOptions; +export let DefaultMongoURI = ''; +export const getDefaults = () => { + const logsFolder = (() => { + let folder = './logs/'; + if (typeof process !== 'undefined' && process.env.TESTING === '1') { + folder = './test_logs/'; + } + if (process.env.PARSE_SERVER_LOGS_FOLDER) { + folder = nullParser(process.env.PARSE_SERVER_LOGS_FOLDER); + } + return folder; + })(); -const { verbose, level } = (() => { - const verbose = process.env.VERBOSE ? true : false; - return { verbose, level: verbose ? 'verbose' : undefined }; -})(); + const { verbose, level } = (() => { + const verbose = process.env.VERBOSE ? true : false; + return { verbose, level: verbose ? 'verbose' : undefined }; + })(); -const DefinitionDefaults = Object.keys(ParseServerOptions).reduce((memo, key) => { - const def = ParseServerOptions[key]; - if (Object.prototype.hasOwnProperty.call(def, 'default')) { - memo[key] = def.default; - } - return memo; -}, {}); + const DefinitionDefaults = Object.keys(ParseServerOptions).reduce((memo, key) => { + const def = ParseServerOptions[key]; + if (Object.prototype.hasOwnProperty.call(def, 'default')) { + memo[key] = def.default; + } + const group = def.group; + if (group && group !== 'SchemaOptions') { + const options = ServerOptions[group] || {}; + for (const _key in options) { + const val = options[_key]; + let env = process.env[val.env]; + if (val.default == null && env == null) { + continue; + } + if (memo[key] == null) { + memo[key] = {}; + } + if (val.action && env) { + env = val.action(env); + } + memo[key][_key] = env || val.default; + } + } + return memo; + }, {}); -const computedDefaults = { - jsonLogs: process.env.JSON_LOGS || false, - logsFolder, - verbose, - level, + const computedDefaults = { + jsonLogs: process.env.JSON_LOGS || false, + logsFolder, + verbose, + level, + }; + DefaultMongoURI = DefinitionDefaults.databaseURI; + return { ...DefinitionDefaults, ...computedDefaults }; }; -export default Object.assign({}, DefinitionDefaults, computedDefaults); -export const DefaultMongoURI = DefinitionDefaults.databaseURI; +export default getDefaults();