// A Config object provides information about how a specific app is // configured. // mount is the URL for the root of the API; includes http, domain, etc. import { isBoolean, isString } from 'lodash'; import { pathToRegexp } from 'path-to-regexp'; import net from 'net'; import AppCache from './cache'; import DatabaseController from './Controllers/DatabaseController'; import { logLevels as validLogLevels } from './Controllers/LoggerController'; import { version } from '../package.json'; import { AccountLockoutOptions, DatabaseOptions, FileUploadOptions, IdempotencyOptions, LogLevels, PagesOptions, ParseServerOptions, SchemaOptions, SecurityOptions, } from './Options/Definitions'; import ParseServer from './cloud-code/Parse.Server'; import Deprecator from './Deprecator/Deprecator'; function removeTrailingSlash(str) { if (!str) { return str; } if (str.endsWith('/')) { str = str.substring(0, str.length - 1); } return str; } /** * Config keys that need to be loaded asynchronously. */ const asyncKeys = ['publicServerURL']; export class Config { static get(applicationId: string, mount: string) { const cacheInfo = AppCache.get(applicationId); if (!cacheInfo) { return; } const config = new Config(); config.applicationId = applicationId; Object.keys(cacheInfo).forEach(key => { if (key == 'databaseController') { config.database = new DatabaseController(cacheInfo.databaseController.adapter, config); } else { config[key] = cacheInfo[key]; } }); config.mount = removeTrailingSlash(mount); config.generateSessionExpiresAt = config.generateSessionExpiresAt.bind(config); config.generateEmailVerifyTokenExpiresAt = config.generateEmailVerifyTokenExpiresAt.bind( config ); config.version = version; return config; } async loadKeys() { await Promise.all( asyncKeys.map(async key => { if (typeof this[`_${key}`] === 'function') { try { this[key] = await this[`_${key}`](); } catch (error) { throw new Error(`Failed to resolve async config key '${key}': ${error.message}`); } } }) ); const cachedConfig = AppCache.get(this.appId); if (cachedConfig) { const updatedConfig = { ...cachedConfig }; asyncKeys.forEach(key => { updatedConfig[key] = this[key]; }); AppCache.put(this.appId, updatedConfig); } } static transformConfiguration(serverConfiguration) { for (const key of Object.keys(serverConfiguration)) { if (asyncKeys.includes(key) && typeof serverConfiguration[key] === 'function') { serverConfiguration[`_${key}`] = serverConfiguration[key]; delete serverConfiguration[key]; } } } static put(serverConfiguration) { Config.validateOptions(serverConfiguration); Config.validateControllers(serverConfiguration); Config.transformConfiguration(serverConfiguration); AppCache.put(serverConfiguration.appId, serverConfiguration); Config.setupPasswordValidator(serverConfiguration.passwordPolicy); return serverConfiguration; } static validateOptions({ customPages, publicServerURL, revokeSessionOnPasswordReset, expireInactiveSessions, sessionLength, defaultLimit, maxLimit, accountLockout, passwordPolicy, masterKeyIps, masterKey, maintenanceKey, maintenanceKeyIps, readOnlyMasterKey, allowHeaders, idempotencyOptions, fileUpload, pages, security, enforcePrivateUsers, enableInsecureAuthAdapters, schema, requestKeywordDenylist, allowExpiredAuthDataToken, logLevels, rateLimit, databaseOptions, extendSessionOnUse, allowClientClassCreation, }) { if (masterKey === readOnlyMasterKey) { throw new Error('masterKey and readOnlyMasterKey should be different'); } if (masterKey === maintenanceKey) { throw new Error('masterKey and maintenanceKey should be different'); } 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'; } this.validatePublicServerURL({ publicServerURL }); this.validateSessionConfiguration(sessionLength, expireInactiveSessions); this.validateIps('masterKeyIps', masterKeyIps); this.validateIps('maintenanceKeyIps', maintenanceKeyIps); this.validateDefaultLimit(defaultLimit); this.validateMaxLimit(maxLimit); this.validateAllowHeaders(allowHeaders); this.validateIdempotencyOptions(idempotencyOptions); this.validatePagesOptions(pages); this.validateSecurityOptions(security); this.validateSchemaOptions(schema); this.validateEnforcePrivateUsers(enforcePrivateUsers); this.validateEnableInsecureAuthAdapters(enableInsecureAuthAdapters); this.validateAllowExpiredAuthDataToken(allowExpiredAuthDataToken); this.validateRequestKeywordDenylist(requestKeywordDenylist); 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, appName, publicServerURL, _publicServerURL, emailVerifyTokenValidityDuration, emailVerifyTokenReuseIfValid, }) { const emailAdapter = userController.adapter; if (verifyUserEmails) { this.validateEmailConfiguration({ emailAdapter, appName, publicServerURL: publicServerURL || _publicServerURL, emailVerifyTokenValidityDuration, emailVerifyTokenReuseIfValid, }); } } 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 validateAllowClientClassCreation(allowClientClassCreation) { if (typeof allowClientClassCreation !== 'boolean') { throw 'Parse Server option allowClientClassCreation 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 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.'; } } static validateIdempotencyOptions(idempotencyOptions) { if (!idempotencyOptions) { return; } if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else 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) { if (accountLockout) { if ( typeof accountLockout.duration !== 'number' || accountLockout.duration <= 0 || accountLockout.duration > 99999 ) { throw 'Account lockout duration should be greater than 0 and less than 100000'; } if ( !Number.isInteger(accountLockout.threshold) || accountLockout.threshold < 1 || accountLockout.threshold > 999 ) { 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.'; } } } static validatePasswordPolicy(passwordPolicy) { if (passwordPolicy) { if ( passwordPolicy.maxPasswordAge !== undefined && (typeof passwordPolicy.maxPasswordAge !== 'number' || passwordPolicy.maxPasswordAge < 0) ) { throw 'passwordPolicy.maxPasswordAge must be a positive number'; } if ( passwordPolicy.resetTokenValidityDuration !== undefined && (typeof passwordPolicy.resetTokenValidityDuration !== 'number' || passwordPolicy.resetTokenValidityDuration <= 0) ) { throw 'passwordPolicy.resetTokenValidityDuration must be a positive number'; } if (passwordPolicy.validatorPattern) { if (typeof passwordPolicy.validatorPattern === 'string') { passwordPolicy.validatorPattern = new RegExp(passwordPolicy.validatorPattern); } else if (!(passwordPolicy.validatorPattern instanceof RegExp)) { throw 'passwordPolicy.validatorPattern must be a regex string or RegExp object.'; } } if ( passwordPolicy.validatorCallback && typeof passwordPolicy.validatorCallback !== 'function' ) { throw 'passwordPolicy.validatorCallback must be a function.'; } if ( passwordPolicy.doNotAllowUsername && typeof passwordPolicy.doNotAllowUsername !== 'boolean' ) { throw 'passwordPolicy.doNotAllowUsername must be a boolean value.'; } if ( passwordPolicy.maxPasswordHistory && (!Number.isInteger(passwordPolicy.maxPasswordHistory) || passwordPolicy.maxPasswordHistory <= 0 || passwordPolicy.maxPasswordHistory > 20) ) { throw 'passwordPolicy.maxPasswordHistory must be an integer ranging 0 - 20'; } if ( passwordPolicy.resetTokenReuseIfValid && typeof passwordPolicy.resetTokenReuseIfValid !== 'boolean' ) { throw 'resetTokenReuseIfValid must be a boolean value'; } if (passwordPolicy.resetTokenReuseIfValid && !passwordPolicy.resetTokenValidityDuration) { throw 'You cannot use resetTokenReuseIfValid without resetTokenValidityDuration'; } if ( passwordPolicy.resetPasswordSuccessOnInvalidEmail && typeof passwordPolicy.resetPasswordSuccessOnInvalidEmail !== 'boolean' ) { throw 'resetPasswordSuccessOnInvalidEmail must be a boolean value'; } } } // if the passwordPolicy.validatorPattern is configured then setup a callback to process the pattern static setupPasswordValidator(passwordPolicy) { if (passwordPolicy && passwordPolicy.validatorPattern) { passwordPolicy.patternValidator = value => { return passwordPolicy.validatorPattern.test(value); }; } } static validatePublicServerURL({ publicServerURL, required = false }) { if (!publicServerURL) { if (!required) { return; } throw 'The option publicServerURL is required.'; } const type = typeof publicServerURL; if (type === 'string') { if (!publicServerURL.startsWith('http://') && !publicServerURL.startsWith('https://')) { throw 'The option publicServerURL must be a valid URL starting with http:// or https://.'; } return; } if (type === 'function') { return; } throw `The option publicServerURL must be a string or function, but got ${type}.`; } static validateEmailConfiguration({ emailAdapter, appName, publicServerURL, emailVerifyTokenValidityDuration, emailVerifyTokenReuseIfValid, }) { if (!emailAdapter) { throw 'An emailAdapter is required for e-mail verification and password resets.'; } if (typeof appName !== 'string') { throw 'An app name is required for e-mail verification and password resets.'; } this.validatePublicServerURL({ publicServerURL, required: true }); if (emailVerifyTokenValidityDuration) { if (isNaN(emailVerifyTokenValidityDuration)) { throw 'Email verify token validity duration must be a valid number.'; } else if (emailVerifyTokenValidityDuration <= 0) { throw 'Email verify token validity duration must be a value greater than 0.'; } } if (emailVerifyTokenReuseIfValid && typeof emailVerifyTokenReuseIfValid !== 'boolean') { throw 'emailVerifyTokenReuseIfValid must be a boolean value'; } if (emailVerifyTokenReuseIfValid && !emailVerifyTokenValidityDuration) { throw 'You cannot use emailVerifyTokenReuseIfValid without emailVerifyTokenValidityDuration'; } } 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.'; } if (fileUpload.fileExtensions === undefined) { fileUpload.fileExtensions = FileUploadOptions.fileExtensions.default; } else if (!Array.isArray(fileUpload.fileExtensions)) { throw 'fileUpload.fileExtensions must be an array.'; } } static validateIps(field, masterKeyIps) { for (let ip of masterKeyIps) { if (ip.includes('/')) { ip = ip.split('/')[0]; } if (!net.isIP(ip)) { throw `The Parse Server option "${field}" contains an invalid IP address "${ip}".`; } } } static validateEnableInsecureAuthAdapters(enableInsecureAuthAdapters) { if (enableInsecureAuthAdapters && typeof enableInsecureAuthAdapters !== 'boolean') { throw 'Parse Server option enableInsecureAuthAdapters must be a boolean.'; } if (enableInsecureAuthAdapters) { Deprecator.logRuntimeDeprecation({ usage: 'insecure adapter' }); } } get mount() { var mount = this._mount; if (this.publicServerURL) { mount = this.publicServerURL; } return mount; } set mount(newValue) { this._mount = newValue; } static validateSessionConfiguration(sessionLength, expireInactiveSessions) { if (expireInactiveSessions) { if (isNaN(sessionLength)) { throw 'Session length must be a valid number.'; } else if (sessionLength <= 0) { throw 'Session length must be a value greater than 0.'; } } } 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.'; } } static validateMaxLimit(maxLimit) { if (maxLimit <= 0) { throw 'Max limit must be a value greater than 0.'; } } static validateAllowHeaders(allowHeaders) { if (![null, undefined].includes(allowHeaders)) { if (Array.isArray(allowHeaders)) { allowHeaders.forEach(header => { if (typeof header !== 'string') { throw 'Allow headers must only contain strings'; } else if (!header.trim().length) { throw 'Allow headers must not contain empty strings'; } }); } else { throw 'Allow headers must be an array'; } } } 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`; } if (databaseOptions.allowPublicExplain === undefined) { databaseOptions.allowPublicExplain = DatabaseOptions.allowPublicExplain.default; } else if (typeof databaseOptions.allowPublicExplain !== 'boolean') { throw `Parse Server option 'databaseOptions.allowPublicExplain' must be a boolean.`; } } 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`; } // Validate that the path is valid path-to-regexp syntax try { pathToRegexp(option.requestPath); } catch (error) { throw `rateLimit.requestPath "${option.requestPath}" is not valid: ${error.message}`; } 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`; } const options = Object.keys(ParseServer.RateLimitZone); if (option.zone && !options.includes(option.zone)) { const formatter = new Intl.ListFormat('en', { style: 'short', type: 'disjunction' }); throw `rateLimit.zone must be one of ${formatter.format(options)}`; } } } generateEmailVerifyTokenExpiresAt() { if (!this.verifyUserEmails || !this.emailVerifyTokenValidityDuration) { return undefined; } var now = new Date(); return new Date(now.getTime() + this.emailVerifyTokenValidityDuration * 1000); } generatePasswordResetTokenExpiresAt() { if (!this.passwordPolicy || !this.passwordPolicy.resetTokenValidityDuration) { return undefined; } const now = new Date(); return new Date(now.getTime() + this.passwordPolicy.resetTokenValidityDuration * 1000); } generateSessionExpiresAt() { if (!this.expireInactiveSessions) { return undefined; } var now = new Date(); return new Date(now.getTime() + this.sessionLength * 1000); } unregisterRateLimiters() { let i = this.rateLimits?.length; while (i--) { const limit = this.rateLimits[i]; if (limit.cloud) { this.rateLimits.splice(i, 1); } } } get invalidLinkURL() { return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`; } get invalidVerificationLinkURL() { return ( this.customPages.invalidVerificationLink || `${this.publicServerURL}/apps/invalid_verification_link.html` ); } get linkSendSuccessURL() { return ( this.customPages.linkSendSuccess || `${this.publicServerURL}/apps/link_send_success.html` ); } get linkSendFailURL() { return this.customPages.linkSendFail || `${this.publicServerURL}/apps/link_send_fail.html`; } get verifyEmailSuccessURL() { return ( this.customPages.verifyEmailSuccess || `${this.publicServerURL}/apps/verify_email_success.html` ); } get choosePasswordURL() { return this.customPages.choosePassword || `${this.publicServerURL}/apps/choose_password`; } get requestResetPasswordURL() { return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/request_password_reset`; } get passwordResetSuccessURL() { return ( this.customPages.passwordResetSuccess || `${this.publicServerURL}/apps/password_reset_success.html` ); } get parseFrameURL() { return this.customPages.parseFrameURL; } get verifyEmailURL() { 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() { return this.pages && this.pages.enableRouter && this.pages.pagesEndpoint ? this.pages.pagesEndpoint : 'apps'; } } export default Config; module.exports = Config;