packages/fxa-content-server/server/lib/configuration.js (1,080 lines of code) (raw):

/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /*eslint-disable camelcase */ 'use strict'; const convict = require('convict'); const fs = require('fs'); const path = require('path'); const versionInfo = require('./version'); const { OAUTH_SCOPE_SUBSCRIPTIONS } = require('fxa-shared/oauth/constants'); const DEFAULT_SUPPORTED_LANGUAGES = require('../../../../libs/shared/l10n/src/lib/supported-languages.json'); convict.addFormats(require('convict-format-with-moment')); convict.addFormats(require('convict-format-with-validator')); convict.addFormats( require('fxa-shared/configuration/convict-format-allow-list').format ); const conf = (module.exports = convict({ allowed_iframe_contexts: { default: [], doc: 'DEPRECATED - context query parameters allowed to embed FxA within an IFRAME', format: Array, }, allowed_metrics_flow_cors_origins: { default: [null], doc: 'Origins that are allowed to request the /metrics-flow endpoint', env: 'ALLOWED_METRICS_FLOW_ORIGINS', format: Array, }, allowed_parent_origins: { default: [], doc: 'DEPRECATED - Origins that are allowed to embed FxA within an IFRAME', env: 'ALLOWED_PARENT_ORIGINS', format: Array, }, amplitude: { disabled: { default: false, doc: 'Disable amplitude events', env: 'AMPLITUDE_DISABLED', format: Boolean, }, schemaValidation: { default: true, doc: 'Validate events against a JSON schema', env: 'AMPLITUDE_SCHEMA_VALIDATION', format: Boolean, }, rawEvents: { default: false, doc: 'Log raw Amplitude events', env: 'AMPLITUDE_RAW_EVENTS', format: Boolean, }, }, are_dist_resources: { default: false, doc: 'Check if the resources are under the /dist directory', format: Boolean, }, cachify_prefix: { default: 'v', doc: 'The prefix for cachify hashes in URLs', }, cert_path: { default: path.resolve(__dirname, '..', '..', 'cert.pem'), doc: 'The location of the SSL certificate in pem format', }, client_metrics: { max_event_offset: { default: '2 days', doc: 'Maximum event offset', env: 'CLIENT_METRICS_MAX_EVENT_OFFSET', format: 'duration', }, stderr_collector_disabled: { default: false, doc: 'disable client metrics output to stderr', env: 'DISABLE_CLIENT_METRICS_STDERR', }, }, client_sessions: { cookie_name: 'session', duration: { default: '1 day', format: 'duration', }, secret: 'YOU MUST CHANGE ME', }, clientAddressDepth: { default: 3, doc: 'location of the client ip address in the remote address chain', env: 'CLIENT_ADDRESS_DEPTH', format: Number, }, coppa: { enabled: { default: true, doc: 'Is the COPPA age check enabled?', env: 'COPPA_ENABLED', format: Boolean, }, }, csp: { enabled: { default: false, doc: 'Send "Content-Security-Policy" header', env: 'CSP_ENABLED', }, reportUri: { default: '/_/csp-violation', doc: 'Location of "report-uri" for full, blocking CSP rules', env: 'CSP_REPORT_URI', }, reportOnly: { default: false, doc: 'DEPRECATED - Only send the "Content-Security-Policy-Report-Only" header', env: 'CSP_REPORT_ONLY', }, reportOnlyEnabled: { default: false, doc: 'Send "Content-Security-Policy-Report-Only" header', env: 'CSP_REPORT_ONLY_ENABLED', }, reportOnlyUri: { default: '/_/csp-violation-report-only', doc: 'Location of "report-uri" for report-only CSP rules', env: 'CSP_REPORT_ONLY_URI', }, }, disable_locale_check: { default: false, doc: 'Skip checking for gettext .mo files for supported locales', }, disable_route_logging: { default: false, doc: 'Disable route logging completely. Useful for trimming CI logs.', env: 'DISABLE_ROUTE_LOGGING', }, env: { default: 'production', doc: "What environment are we running in? Note: all hosted environments are 'production'.", env: 'NODE_ENV', format: ['production', 'development'], }, version: { default: versionInfo.version, }, featureFlags: { enabled: { default: true, doc: 'Enable feature flagging', env: 'FEATURE_FLAGS_ENABLED', format: Boolean, }, interval: { default: '30 seconds', doc: 'The refresh interval for feature-flagging', env: 'FEATURE_FLAGS_INTERVAL', format: 'duration', }, redis: { host: { default: 'localhost', doc: 'Redis host name or IP address', env: 'FEATURE_FLAGS_REDIS_HOST', format: String, }, password: { default: '', doc: 'Redis password', env: 'REDIS_PASSWORD', sensitive: true, format: String, }, initialBackoff: { default: '100 milliseconds', doc: 'Initial backoff for feature-flagging Redis connection retries, increases exponentially with each attempt', env: 'FEATURE_FLAGS_REDIS_TIMEOUT', format: 'duration', }, maxConnections: { default: 1, doc: 'Maximum connection count for feature-flagging Redis pool', env: 'FEATURE_FLAGS_REDIS_MAX_CONNECTIONS', format: 'nat', }, maxPending: { default: 10, doc: 'Maximum waiting client count for feature-flagging Redis pool', env: 'FEATURE_FLAGS_REDIS_MAX_PENDING', format: 'nat', }, minConnections: { default: 1, doc: 'Minimum connection count for feature-flagging Redis pool', env: 'FEATURE_FLAGS_REDIS_MIN_CONNECTIONS', format: 'nat', }, port: { default: 6379, doc: 'Redis port', env: 'FEATURE_FLAGS_REDIS_PORT', format: 'port', }, retryCount: { default: 5, doc: 'Retry limit for feature-flagging Redis connection attempts', env: 'FEATURE_FLAGS_REDIS_RETRY_COUNT', format: 'int', }, }, sendFxAStatusOnSettings: { default: false, doc: 'Sends a webchannel message on the settings page to request auth data from the browser', format: Boolean, env: 'FEATURE_FLAGS_FXA_STATUS_ON_SETTINGS', }, keyStretchV2: { default: true, doc: 'Enables V2 key stretching', format: Boolean, env: 'FEATURE_FLAGS_KEY_STRETCH_V2', }, recoveryCodeSetupOnSyncSignIn: { default: false, doc: 'Enables setting up a recovery code after a Sync sign in', format: Boolean, env: 'FEATURE_FLAGS_RECOVERY_CODE_SETUP_ON_SYNC_SIGN_IN', }, recoveryPhonePasswordReset2fa: { default: false, doc: 'Enables recovery phone codes for 2FA in password reset', format: Boolean, env: 'FEATURE_FLAGS_RECOVERY_PHONE_PASSWORD_RESET_2FA', }, }, showReactApp: { emailFirstRoutes: { default: false, doc: 'Enable users to visit the React version of "email first" routes', format: Boolean, env: 'REACT_CONVERSION_EMAIL_FIRST_ROUTES', }, simpleRoutes: { default: false, doc: 'Enable users to visit the React version of "simple" routes', format: Boolean, env: 'REACT_CONVERSION_SIMPLE_ROUTES', }, resetPasswordRoutes: { default: false, doc: 'Enable users to visit the React version of "reset_password" routes', format: Boolean, env: 'REACT_CONVERSION_RESET_PASSWORD_ROUTES', }, oauthRoutes: { default: false, doc: 'Enable users to visit the React version of routes requiring oauth', format: Boolean, env: 'REACT_CONVERSION_OAUTH_ROUTES', }, signInRoutes: { default: false, doc: 'Enable users to visit the React version of "signin" routes', format: Boolean, env: 'REACT_CONVERSION_SIGNIN_ROUTES', }, signUpRoutes: { default: false, doc: 'Enable users to visit the React version of "signup" routes', format: Boolean, env: 'REACT_CONVERSION_SIGNUP_ROUTES', }, pairRoutes: { default: false, doc: 'Enable users to visit the React version of "pair" routes', format: Boolean, env: 'REACT_CONVERSION_PAIR_ROUTES', }, postVerifyOtherRoutes: { default: false, doc: 'Enable users to visit the React version of any other "post verify" routes', format: Boolean, env: 'REACT_CONVERSION_POST_VERIFY_OTHER_ROUTES', }, postVerifyThirdPartyAuthRoutes: { default: false, doc: 'Enable users to visit the React version of third party auth "post verify" routes', format: Boolean, env: 'REACT_CONVERSION_POST_VERIFY_THIRD_PARTY_AUTH', }, postVerifyCADViaQRRoutes: { default: false, doc: 'Enable users to visit the React version of "post verify CAD via QR code" routes', format: Boolean, env: 'REACT_CONVERSION_POST_VERIFY_CAD_VIA_QR_ROUTES', }, webChannelExampleRoutes: { default: false, doc: 'Enable users to visit the React version of "web channel example" routes', format: Boolean, env: 'REACT_CONVERSION_WEB_CHANNEL_EXAMPLE_ROUTES', }, }, brandMessagingMode: { default: 'none', doc: 'The type of messaging to show. Options are prelaunch, postlaunch, or none', env: 'BRAND_MESSAGING_MODE', format: String, }, flow_id_expiry: { default: '2 hours', doc: 'Time after which flow ids are considered stale', env: 'FLOW_ID_EXPIRY', format: 'duration', }, flow_id_key: { default: 'YOU MUST CHANGE ME', doc: 'HMAC key used to verify flow event data', env: 'FLOW_ID_KEY', format: String, }, flow_metrics_disabled: { default: false, doc: 'Disable flow metrics output to stderr', env: 'FLOW_METRICS_DISABLED', format: Boolean, }, fxa_client_configuration: { max_age: { default: '1 day', doc: 'Cache max age for /.well-known/fxa-client-configuration, in ms', format: 'duration', }, }, fxaccount_url: { default: 'http://localhost:9000', doc: 'The url of the Firefox Account auth server', env: 'FXA_URL', format: 'url', }, serverGleanMetrics: { enabled: { default: true, doc: 'Enable Glean metrics logging', env: 'CONTENT_SERVER_GLEAN_ENABLED', format: Boolean, }, applicationId: { default: 'accounts_backend_dev', doc: 'The Glean application id', env: 'CONTENT_SERVER_GLEAN_APP_ID', format: String, }, channel: { default: 'development', doc: 'The application channel, e.g. development, stage, production, etc.', env: 'CONTENT_SERVER_GLEAN_APP_CHANNEL', format: String, }, loggerAppName: { default: 'fxa-content', doc: 'Used to form the mozlog logger name', env: 'CONTENT_SERVER_GLEAN_LOGGER_APP_NAME', format: String, }, }, nimbusPreview: { default: false, doc: 'Enables preview mode for nimbus experiments for development and testing.', format: Boolean, env: 'NIMBUS_PREVIEW', }, glean: { enabled: { default: false, env: 'GLEAN_ENABLED', format: Boolean, }, applicationId: { default: 'accounts_frontend_dev', env: 'GLEAN_APPLICATION_ID', format: String, }, uploadEnabled: { default: true, env: 'GLEAN_UPLOAD_ENABLED', format: Boolean, }, appChannel: { default: 'development', env: 'GLEAN_APP_CHANNEL', format: String, }, serverEndpoint: { default: 'https://incoming.telemetry.mozilla.org', env: 'GLEAN_SERVER_ENDPOINT', format: 'url', }, // debug configs logPings: { default: true, doc: 'log pings to the console', env: 'GLEAN_LOG_PINGS', format: Boolean, }, debugViewTag: { default: '', doc: 'the tag with which to log pings to The Glean Debug View (https://mozilla.github.io/glean/book/user/debugging/index.html#glean-debug-view)', env: 'GLEAN_DEBUG_VIEW_TAG', format: String, }, }, settings_gql_url: { default: 'http://localhost:8290', doc: 'The URL of the Firefox Account settings GraphQL server', env: 'FXA_GQL_URL', format: 'url', }, googleAuthConfig: { enabled: { default: true, env: 'GOOGLE_AUTH_ENABLED', format: String, }, clientId: { default: '218517873053-th4taguk9dvf03rrgk8sigon84oigf5l.apps.googleusercontent.com', env: 'GOOGLE_AUTH_CLIENT_ID', format: String, doc: 'Google auth client id', }, redirectUri: { default: 'http://localhost:3030/post_verify/third_party_auth/callback', env: 'GOOGLE_AUTH_REDIRECT_URI', format: String, doc: 'Google auth redirect uri', }, authorizationEndpoint: { default: 'https://accounts.google.com/o/oauth2/v2/auth', env: 'GOOGLE_AUTH_AUTHORIZATION_ENDPOINT', format: String, doc: 'Google auth token endpoint', }, }, appleAuthConfig: { enabled: { default: true, env: 'APPLE_AUTH_ENABLED', format: String, }, clientId: { default: 'com.mozilla.firefox.accounts.auth', env: 'APPLE_AUTH_CLIENT_ID', format: String, doc: 'Apple auth client id', }, redirectUri: { default: 'https://localhost.dev:3030/post_verify/third_party_auth/callback', env: 'APPLE_AUTH_REDIRECT_URI', format: String, doc: 'Apple auth redirect uri', }, authorizationEndpoint: { default: 'https://appleid.apple.com/auth/authorize', env: 'APPLE_AUTH_AUTHORIZATION_ENDPOINT', format: String, doc: 'Apple auth token endpoint', }, }, geodb: { dbPath: { default: path.resolve(__dirname, '../../../fxa-geodb/db/cities-db.mmdb'), doc: 'Path to maxmind database file', env: 'GEODB_DBPATH', format: String, }, enabled: { default: true, doc: 'Feature flag for geolocation', env: 'GEODB_ENABLED', format: Boolean, }, }, hsts_max_age: { default: 31536000, // a year doc: 'Max age of the STS directive in seconds', // Note: This format is a number because the value needs to be in seconds format: Number, }, http_port: { default: 3080, doc: 'HTTP port for local dev', env: 'HTTP_PORT', format: 'port', }, http_proxy: { host: { default: undefined, format: String, }, port: { default: undefined, format: 'port', }, }, i18n: { debugLang: { default: 'it-CH', format: String, }, defaultLang: { default: 'en', format: String, }, fonts: { unsupportedLanguages: { default: [], doc: 'DEPRECATED: These languages should use system fonts instead of Fira Sans', format: Array, }, }, localeSubdirSuffix: { default: '', doc: 'Enable alternative localized resources for Mozilla Online with private use subtag', env: 'I18N_LOCALE_SUBDIR_SUFFIX', format: ['', '_x_mococn'], }, supportedLanguages: { default: DEFAULT_SUPPORTED_LANGUAGES, doc: 'List of languages this deployment should detect and display localized strings.', env: 'I18N_SUPPORTED_LANGUAGES', format: Array, }, translationDirectory: { default: path.resolve(__dirname, '../../app/i18n/'), doc: 'The directory where per-locale .json files containing translations reside', env: 'I18N_TRANSLATION_DIR', format: String, }, translationType: { default: 'key-value-json', doc: 'The file format used for the translations', env: 'I18N_TRANSLATION_TYPE', format: String, }, }, jsResourcePath: { default: 'bundle', doc: 'The directory where the JavaScript resources are served from', format: String, }, key_path: { default: path.resolve(__dirname, '..', '..', 'key.pem'), doc: 'The location of the SSL key in pem format', }, logging: { app: { default: 'fxa-content-server' }, fmt: { default: 'heka', format: ['heka', 'pretty'], }, level: { default: 'info', env: 'LOG_LEVEL', }, }, marketing_email: { enabled: { default: true, doc: 'Feature flag for communication preferences in settings', env: 'FXA_MARKETING_EMAIL_ENABLED', format: Boolean, }, preferences_url: { default: 'https://basket.allizom.org/fxa/', doc: 'User facing URL where a user can manage their email preferences', env: 'FXA_MARKETING_EMAIL_PREFERENCES_URL', format: 'url', }, }, mxRecordValidation: { enabled: { default: true, doc: 'Feature flag for MX record validation', env: 'FXA_MX_RECORD_VALIDATION', format: Boolean, }, exclusions: { default: [], doc: 'List of domains that we will not perform MX record validation on', format: Array, env: 'FXA_MX_RECORD_EXCLUSIONS', }, }, oauth_client_id: { default: 'ea3ca969f8c6bb0d', doc: 'The client_id of the content server', env: 'FXA_OAUTH_CLIENT_ID', format: String, }, oauth_client_id_map: { default: { dcdb5ae7add825d2: '123done', '325b4083e32fe8e7': '321done', }, doc: 'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }', env: 'OAUTH_CLIENT_IDS', format: Object, }, oauth_url: { default: 'http://localhost:9000', doc: 'The url of the Firefox Account OAuth server', env: 'FXA_OAUTH_URL', format: 'url', }, oauth: { prompt_none: { enabled: { default: true, doc: 'Is prompt=none enabled globally?', env: 'OAUTH_PROMPT_NONE_ENABLED', format: Boolean, }, enabled_client_ids: { // 123done enabled for functional tests, 321done is not. default: ['dcdb5ae7add825d2', '7f368c6886429f19', '32aaeb6f1c21316a'], doc: 'client_ids for which prompt=none is enabled', env: 'OAUTH_PROMPT_NONE_ENABLED_CLIENT_IDS', format: Array, }, }, react_feature_flags: { enabled_client_ids: { // 123done enabled for functional tests, 321done is not. default: ['dcdb5ae7add825d2', '7f368c6886429f19'], doc: 'client_ids for which feature flags in react are supported', env: 'OAUTH_REACT_FEATURE_FLAGS_ENABLED_CLIENT_IDS', format: Array, }, }, }, openid_configuration: { claims_supported: ['aud', 'exp', 'iat', 'iss', 'sub'], id_token_signing_alg_values_supported: ['RS256'], response_types_supported: ['code', 'token'], scopes_supported: ['openid', 'profile', 'email'], subject_types_supported: ['public'], token_endpoint_auth_methods_supported: ['client_secret_post'], }, page_template_root: { default: path.resolve(__dirname, '..', 'templates', 'pages'), doc: 'The root path of server-rendered page templates', }, page_template_subdirectory: { default: 'dist', doc: 'Subdirectory of page_template_root for server-rendered page templates', env: 'PAGE_TEMPLATE_SUBDIRECTORY', format: ['src', 'dist'], }, nimbus: { host: { default: 'http://localhost:8001', doc: 'Base URI for cirrus (Nimbus experimentation endpoint).', env: 'NIMBUS_CIRRUS_HOST', format: String, }, timeout: { default: 200, doc: 'Amount of time in milliseconds to wait for a response from cirrus', env: 'NIMBUS_CIRRUS_TIMEOUT', format: Number, }, }, pairing: { clients: { default: [ '3c49430b43dfba77', // Reference browser 'a2270f727f45f648', // Fenix '1b1a3e44c54fbb58', // Firefox for iOS ], doc: 'OAuth Client IDs that are allowed to pair. Remove all clients from this list to disable pairing.', env: 'PAIRING_CLIENTS', format: Array, }, server_base_uri: { default: 'wss://channelserver.services.mozilla.com', doc: 'The url of the Pairing channel server.', env: 'PAIRING_SERVER_BASE_URI', }, }, port: { default: 3030, doc: 'HTTPS port for local dev', env: 'PORT', format: 'port', }, process_type: 'ephemeral', profile_images_url: { default: 'http://localhost:1112', doc: 'The url of the Firefox Account Profile Image Server', env: 'FXA_PROFILE_IMAGES_URL', format: 'url', }, profile_url: { default: 'http://localhost:1111', doc: 'The url of the Firefox Account Profile Server', env: 'FXA_PROFILE_URL', format: 'url', }, public_url: { default: 'http://localhost:3030', doc: 'The publically visible URL of the deployment', env: 'PUBLIC_URL', }, recovery_codes: { count: { default: 8, doc: 'The default number of backup authentication codes to create', env: 'RECOVERY_CODE_COUNT', format: 'nat', }, length: { default: 10, doc: 'The default length of a backup authentication code', env: 'RECOVERY_CODE_LENGTH', format: 'nat', }, }, redirect_port: { default: 80, doc: 'Redirect port for HTTPS', env: 'REDIRECT_PORT', format: 'port', }, redirect_check: { allow_list: { default: '*.mozilla.org,*.mozilla.com,*.mozaws.net,*.mozgcp.net,*.firefox.com,firefox.com,localhost'.split( ',' ), doc: `A comma separated list of hostname rules to let through on redirects. Rules support wildcards. - A postfix wildcard is not allowed, e.g. 'foo.*' is invalid. - Empty fragments are not allowed. This means things like 'foo. .bar', '..', '.' are all invalid. - Partial matching is allowed. So 'foo.b*.bar' would match 'foo.baz.bar'. - Note that for partial matching each segment is evaluated in isolation. This means that 'foo.b*.bar' would not allow 'foo.b.az.bar'. `, env: 'REDIRECT_CHECK_ALLOW_LIST', format: 'allowlist', }, }, route_log_format: { default: 'default_fxa', format: ['default_fxa', 'dev_fxa', 'default', 'dev', 'short', 'tiny'], }, scopedKeys: { enabled: { default: true, doc: 'Enable Scoped Key OAuth features', env: 'SCOPED_KEYS_ENABLED', format: Boolean, }, validation: { default: { 'https://identity.mozilla.com/apps/lockbox': { redirectUris: [ 'https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/', 'https://mozilla-lockbox.github.io/fxa/ios-redirect.html', 'https://lockbox.firefox.com/fxa/ios-redirect.html', 'https://lockbox.firefox.com/fxa/android-redirect.html', ], }, 'https://identity.mozilla.com/apps/notes': { redirectUris: [ 'https://dee85c67bd72f3de1f0a0fb62a8fe9b9b1a166d7.extensions.allizom.org/', 'https://mozilla.github.io/notes/fxa/android-redirect.html', ], }, 'https://identity.mozilla.com/apps/oldsync': { redirectUris: [ 'https://lockbox.firefox.com/fxa/ios-redirect.html', 'https://lockbox.firefox.com/fxa/android-redirect.html', 'https://accounts.firefox.com/oauth/success/a2270f727f45f648', // Fenix 'https://accounts.firefox.com/oauth/success/3c49430b43dfba77', // Reference browser 'https://accounts.firefox.com/oauth/success/85da77264642d6a1', // Firefox for FireTV 'https://accounts.firefox.com/oauth/success/1b1a3e44c54fbb58', // Firefox for iOS 'urn:ietf:wg:oauth:2.0:oob:pair-auth-webchannel', 'urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel', // Firefox for iOS Rust client ], }, 'https://identity.mozilla.com/apps/send': { redirectUris: [ 'https://send.firefox.com/oauth', 'https://send.firefox.com/fxa/android-redirect.html', 'https://send2.dev.lcip.org/oauth', ], }, 'https://identity.mozilla.com/apps/123done': { redirectUris: [ 'http://localhost:8080/api/oauth', 'https://stage-123done.herokuapp.com/api/oauth', ], }, 'https://identity.thunderbird.net/apps/sync': { redirectUris: [ 'urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel', // Thunderbird 'net.thunderbird.android://mozilla-accounts-redirect', // Thunderbird for Android (release) 'net.thunderbird.android.beta://mozilla-accounts-redirect', // Thunderbird for Android (beta) 'net.thunderbird.android.daily://mozilla-accounts-redirect', // Thunderbird for Android (daily) 'net.thunderbird.android.debug://mozilla-accounts-redirect', // Thunderbird for Android (development/debug build) ], }, }, doc: 'Validates redirect uris for requested scopes', env: 'SCOPED_KEYS_VALIDATION', format: Object, }, }, sentry: { dsn: { default: '', doc: 'Sentry config for reporting errors. If not set, then no errors reported.', env: 'SENTRY_DSN', format: String, }, env: { doc: 'Environment name to report to sentry', default: 'local', format: ['local', 'ci', 'dev', 'stage', 'prod'], env: 'SENTRY_ENV', }, clientName: { default: 'fxa-content-client', }, serverName: { default: 'fxa-content-server', }, sampleRate: { default: 1, doc: 'Sentry config for client side errors. If not set, then no errors reported.', env: 'SENTRY_SAMPLE_RATE', format: Number, }, tracesSampleRate: { doc: 'Rate at which sentry traces are captured', default: 0, format: 'Number', env: 'SENTRY_TRACES_SAMPLE_RATE', }, }, sourceMapType: { default: 'source-map', doc: 'Type of source maps created. See https://webpack.js.org/configuration/devtool/', env: 'SOURCE_MAP_TYPE', format: String, }, webpackModeOverride: { default: undefined, doc: 'Tells webpack how to optimize build. See https://webpack.js.org/configuration/mode/', env: 'WEBPACK_MODE_OVERRIDE', format: String, }, rolloutRates: { keyStretchV2: { default: 0, doc: 'The rollout rate for key stretching changes. Valid values are from 0 to 1.0', env: 'ROLLOUT_KEY_STRETCH_V2', format: Number, }, generalizedReactApp: { default: 0, doc: 'The rollout rate for the generalized react app experiment. Valid values are from 0 to 1.0. Applies to react route groups that are enabled but not set to fullProdRollout', env: 'ROLLOUT_GENERALIZED_REACT_APP', format: Number, }, }, statsd: { enabled: { doc: 'Enable StatsD', format: Boolean, default: false, env: 'METRIC_ENABLED', }, sampleRate: { doc: 'Sampling rate for StatsD', format: Number, default: 1, env: 'METRIC_SAMPLE_RATE', }, maxBufferSize: { doc: 'StatsD message buffer size in number of characters', format: Number, default: 500, env: 'METRIC_BUFFER_SIZE', }, host: { doc: 'StatsD host to report to', format: String, default: 'localhost', env: 'METRIC_HOST', }, port: { doc: 'Port number of StatsD server', format: Number, default: 8125, env: 'METRIC_PORT', }, prefix: { doc: 'StatsD metrics name prefix for content server', format: String, default: 'fxa-content.', env: 'METRIC_PREFIX', }, }, proxy_settings: { default: false, doc: 'Indicates if settings requests should proxy to fxa-settings. This should only be true for local development.', env: 'PROXY_SETTINGS', format: Boolean, }, static_directory: { default: 'dist', doc: 'Directory that static files are served from.', env: 'STATIC_DIRECTORY', format: String, }, static_settings_directory: { default: 'prod', doc: 'Directory in fxa-settings build folder that contains the target output.', env: 'STATIC_SETTINGS_DIRECTORY', format: ['dev', 'stage', 'prod'], }, static_max_age: { default: '10 minutes', doc: 'Cache max age for static assets, in ms', env: 'STATIC_MAX_AGE', format: 'duration', }, static_resource_url: { default: undefined, doc: 'The origin of the static resources', env: 'STATIC_RESOURCE_URL', format: 'url', }, subscriptions: { enabled: { default: false, doc: 'Indicates whether subscriptions APIs are enabled', env: 'SUBSCRIPTIONS_ENABLED', format: Boolean, }, managementClientId: { default: '59cceb6f8c32317c', doc: 'OAuth client ID for subscriptions management pages', env: 'SUBSCRIPTIONS_MANAGEMENT_CLIENT_ID', format: String, }, managementScopes: { default: `profile ${OAUTH_SCOPE_SUBSCRIPTIONS}`, doc: 'OAuth scopes needed for the subscription management pages to access auth server APIs', env: 'SUBSCRIPTIONS_MANAGEMENT_SCOPES', format: String, }, managementTokenTTL: { default: 1800, doc: 'OAuth token time-to-live (in seconds) for subscriptions management pages', env: 'SUBSCRIPTIONS_MANAGEMENT_TOKEN_TTL', format: 'nat', }, managementUrl: { default: 'http://localhost:3031', doc: 'The publicly visible URL of the subscription management server', env: 'SUBSCRIPTIONS_MANAGEMENT_URL', format: String, }, allowUnauthenticatedRedirects: { default: true, doc: 'Whether to allow any redirects to Payments for an unauthenticated user', env: 'SUBSCRIPTIONS_UNAUTHED_REDIRECTS', format: Boolean, }, useFirestoreProductConfigs: { default: false, doc: 'Feature flag on whether to expect Firestore (and not Stripe metadata) based product and plan configurations', env: 'SUBSCRIPTIONS_FIRESTORE_CONFIGS_ENABLED', format: Boolean, }, }, sync_tokenserver_url: { default: 'http://localhost:8000/token', doc: 'The url of the Firefox Sync tokenserver', env: 'SYNC_TOKENSERVER_URL', format: 'url', }, template_path: { default: path.resolve(__dirname, '..', 'templates'), doc: 'The location of server-rendered templates', }, tests: { coverage: { excludeFiles: [ '/scripts/../tests/', '/scripts/vendor/', 'require_config', ], globalThreshold: 90, threshold: 50, }, }, use_https: false, var_path: { default: path.resolve(__dirname, '..', 'var'), doc: 'The path where deployment specific resources will be sought (keys, etc), and logs will be kept.', env: 'VAR_PATH', }, l10n: { baseUrl: { default: '/settings/static', doc: 'The path (or url) where ftl files are held.', env: 'L10N_BASE_URL', }, }, })); // At the time this file is required, we'll determine the "process name" for this proc // if we can determine what type of process it is (browserid or verifier) based // on the path, we'll use that, otherwise we'll name it 'ephemeral'. conf.set('process_type', path.basename(process.argv[1], '.js')); // Always send CSP headers in development mode if (conf.get('env') === 'development') { conf.set('csp.enabled', true); } const DEV_CONFIG_PATH = path.join(__dirname, '..', 'config', 'local.json'); let files; // handle configuration files. you can specify a CSV list of configuration // files to process, which will be overlayed in order, in the CONFIG_FILES // environment variable if (process.env.CONFIG_FILES && process.env.CONFIG_FILES.trim() !== '') { files = process.env.CONFIG_FILES.split(',').filter(fs.existsSync); } else if (fs.existsSync(DEV_CONFIG_PATH)) { files = [DEV_CONFIG_PATH]; } if (files) { conf.loadFile(files); } if (!process.env.NODE_ENV) { process.env.NODE_ENV = conf.get('env'); } if (!conf.has('public_url')) { conf.set('public_url', 'https://' + conf.get('issuer')); } if (!conf.has('static_resource_url')) { conf.set('static_resource_url', conf.get('public_url')); } // For ops consistency with Browserid, we support HTTP_PROXY // special handling of HTTP_PROXY env var if (process.env.HTTP_PROXY) { const p = process.env.HTTP_PROXY.split(':'); conf.set('http_proxy.host', p[0]); conf.set('http_proxy.port', p[1]); } // But under the covers... OpenID and OAuth libraries need // HTTP_PROXY_HOST, HTTP_PROXY_PORT, HTTPS_PROXY_HOST and HTTPS_PROXY_PORT if (conf.has('http_proxy.host')) { process.env.HTTP_PROXY_HOST = conf.get('http_proxy.host'); process.env.HTTPS_PROXY_HOST = conf.get('http_proxy.host'); } if (conf.has('http_proxy.port')) { process.env.HTTP_PROXY_PORT = conf.get('http_proxy.port'); process.env.HTTPS_PROXY_PORT = conf.get('http_proxy.port'); } // Setup WebPack bundle path for production if (conf.get('env') === 'production') { conf.set('jsResourcePath', `bundle-${versionInfo.commit}`); } // Ensure that supportedLanguages includes defaultLang. const defaultLang = conf.get('i18n.defaultLang'); const supportedLanguages = conf.get('i18n.supportedLanguages'); if (supportedLanguages.indexOf(defaultLang) === -1) { throw new Error( 'Configuration error: defaultLang (' + defaultLang + ') is missing from supportedLanguages' ); } // Ensure that static resources have been generated for each languages in the supported language list // Static resources are generated for each language in the default supported languages list, at least until issue #1434 is fixed const staticallyGeneratedLanguages = conf.default('i18n.supportedLanguages'); const missingLangs = []; supportedLanguages.forEach(function (l) { if (staticallyGeneratedLanguages.indexOf(l) === -1) { missingLangs.push(l); } }); if (missingLangs.length) { throw new Error( 'Configuration error: (' + missingLangs.join(', ') + ') is missing from the default list of supportedLanguages' ); } const areDistResources = conf.get('static_directory') === 'dist'; conf.set('are_dist_resources', areDistResources); // TODO: convict 6+ doesn't like the schema definition we've got // for `scopedKeys.validation`. It doesn't cause runtime problems // but fails validation. We should change it to pass validation. // const options = { // strict: true, // }; // validate the configuration based on the above specification // conf.validate(options);