packages/fxa-auth-server/config/index.ts (2,404 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/. */
import convict from 'convict';
import fs from 'fs';
import { makeRedisConfig } from 'fxa-shared/db/config';
import { tracingConfig } from 'fxa-shared/tracing/config';
import { CloudTasksConvictConfigFactory } from '@fxa/shared/cloud-tasks';
import path from 'path';
import url from 'url';
import { makeConvictMySQLConfig as makeMySQLConfig } from '@fxa/shared/db/mysql/core';
const DEFAULT_SUPPORTED_LANGUAGES = require('./supportedLanguages');
const ONE_DAY = 1000 * 60 * 60 * 24;
const FIVE_MINUTES = 1000 * 60 * 5;
convict.addFormats(require('convict-format-with-moment'));
convict.addFormats(require('convict-format-with-validator'));
const convictConf = convict({
env: {
doc: 'The current node.js environment',
default: 'prod',
format: ['dev', 'test', 'stage', 'prod'],
env: 'NODE_ENV',
},
apiVersion: {
doc: 'Number part of versioned endpoints - ex: /v1/account/status',
format: Number,
default: 1,
env: 'AUTH_API_VERSION',
},
// TODO: Remove this after we have synchronized login records to Firestore
firestore: {
credentials: {
client_email: {
default: 'test@localtest.com',
doc: 'GCP Client key credential',
env: 'FIRESTORE_CLIENT_EMAIL_CREDENTIAL',
format: String,
},
private_key: {
default: '',
doc: 'GCP Private key credential',
env: 'FIRESTORE_PRIVATE_KEY_CREDENTIAL',
format: String,
},
},
enabled: {
default: true,
doc: 'Whether to use firestore',
env: 'FIRESTORE_ENABLED',
format: Boolean,
},
keyFilename: {
default: path.resolve(__dirname, 'secret-key.json'),
doc: 'Path to GCP key file',
env: 'FIRESTORE_KEY_FILE',
format: String,
},
prefix: {
default: 'fxa-eb-',
doc: 'Firestore collection prefix',
env: 'FIRESTORE_COLLECTION_PREFIX',
format: String,
},
projectId: {
default: '',
doc: 'GCP Project id',
env: 'FIRESTORE_PROJECT_ID',
format: String,
},
},
// Prefixed with service name to disambiguate from above firestore config
authFirestore: {
credentials: {
client_email: {
default: 'test@localtest.com',
doc: 'GCP Client key credential',
env: 'AUTH_FIRESTORE_CLIENT_EMAIL_CREDENTIAL',
format: String,
},
private_key: {
default: '',
doc: 'GCP Private key credential',
env: 'AUTH_FIRESTORE_PRIVATE_KEY_CREDENTIAL',
format: String,
},
},
keyFilename: {
default: path.resolve(__dirname, 'secret-key.json'),
doc: 'Path to GCP key file',
env: 'AUTH_FIRESTORE_KEY_FILE',
format: String,
},
prefix: {
default: 'fxa-auth-',
doc: 'Firestore collection prefix',
env: 'AUTH_FIRESTORE_COLLECTION_PREFIX',
format: String,
},
projectId: {
default: '',
doc: 'GCP Project id',
env: 'AUTH_FIRESTORE_PROJECT_ID',
format: String,
},
ebPrefix: {
default: 'fxa-eb-',
doc: 'Event broker Firestore collection prefix',
env: 'AUTH_EB_FIRESTORE_COLLECTION_PREFIX',
format: String,
},
},
pubsub: {
audience: {
default: 'example.com',
doc: 'PubSub JWT Audience for incoming Push Notifications',
env: 'PUBSUB_AUDIENCE',
format: String,
},
authenticate: {
default: true,
doc: 'Authenticate that incoming Push Notification originate from Google',
env: 'PUBSUB_AUTHENTICATE',
format: Boolean,
},
verificationToken: {
default: '',
doc: 'PubSub Verification Token for incoming Push Notifications',
env: 'PUBSUB_VERIFICATION_TOKEN',
format: String,
},
},
geodb: {
dbPath: {
doc: 'Path to the maxmind database file',
default: require.resolve('fxa-geodb/db/cities-db.mmdb'),
env: 'GEODB_DBPATH',
format: String,
},
enabled: {
doc: 'kill-switch for geodb',
default: true,
env: 'GEODB_ENABLED',
format: Boolean,
},
logAccuracy: {
doc: 'emit log lines for accuracy and confidence',
default: false,
env: 'GEODB_LOG_ACCURACY',
format: Boolean,
},
locationOverride: {
doc: 'override for forcing location',
format: Object,
default: {},
env: 'GEODB_LOCATION_OVERRIDE',
},
},
appleAuthConfig: {
clientId: {
default: 'com.mozilla.firefox.accounts.auth',
env: 'APPLE_AUTH_CLIENT_ID',
format: String,
doc: 'Apple auth client id',
},
clientSecret: {
default: 'SSHH',
env: 'APPLE_AUTH_CLIENT_SECRET',
format: String,
doc: 'Apple auth client secret',
},
keyId: {
default: '',
env: 'APPLE_AUTH_KEY_ID',
format: String,
doc: 'Apple auth key 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',
},
privateKey: {
default: '',
env: 'APPLE_AUTH_PRIVATE_KEY',
format: String,
doc: 'Apple auth private key',
},
teamId: {
default: '',
env: 'APPLE_AUTH_TEAM_ID',
format: String,
doc: 'Apple auth team id',
},
tokenEndpoint: {
default: 'https://appleid.apple.com/auth/token',
env: 'APPLE_AUTH_TOKEN_ENDPOINT',
format: String,
doc: 'Apple auth token endpoint',
},
securityEventsClientIds: {
default: ['com.mozilla.firefox.accounts.auth'],
env: 'APPLE_AUTH_SECURITY_EVENTS_CLIENT_IDS',
format: Array,
doc: 'Apple auth security events client ids',
},
},
googleAuthConfig: {
clientId: {
default:
'218517873053-th4taguk9dvf03rrgk8sigon84oigf5l.apps.googleusercontent.com',
env: 'GOOGLE_AUTH_CLIENT_ID',
format: String,
doc: 'Google auth client id',
},
clientSecret: {
default: 'SSHH',
env: 'GOOGLE_AUTH_CLIENT_SECRET',
format: String,
doc: 'Google auth client secret',
},
redirectUri: {
default: 'http://localhost:3030/post_verify/third_party_auth/callback',
env: 'GOOGLE_AUTH_REDIRECT_URI',
format: String,
doc: 'Google auth redirect uri',
},
tokenEndpoint: {
default: 'https://oauth2.googleapis.com/token',
env: 'GOOGLE_AUTH_TOKEN_ENDPOINT',
format: String,
doc: 'Google auth token endpoint',
},
securityEventsClientIds: {
default: [
'218517873053-th4taguk9dvf03rrgk8sigon84oigf5l.apps.googleusercontent.com',
],
env: 'GOOGLE_AUTH_SECURITY_EVENTS_CLIENT_IDS',
format: Array,
doc: 'Google auth security events client ids',
},
},
googleMapsApiKey: {
default: '',
env: 'GOOGLE_MAPS_APIKEY',
format: String,
doc: 'Google Maps Services API key',
},
log: {
app: {
default: 'fxa-auth-server',
env: 'LOG_APP_NAME',
},
level: {
default: 'info',
env: 'LOG_LEVEL',
},
fmt: {
format: ['heka', 'pretty'],
default: 'heka',
env: 'LOG_FORMAT',
},
},
amplitude: {
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,
},
},
publicUrl: {
format: 'url',
default: 'http://localhost:9000',
env: 'PUBLIC_URL',
},
domain: {
format: 'url',
doc: 'Derived automatically from publicUrl',
default: undefined,
},
secretKeyFile: {
format: String,
default: path.resolve(__dirname, '../config/secret-key.json'),
env: 'SECRET_KEY_FILE',
},
publicKeyFile: {
format: String,
default: path.resolve(__dirname, '../config/public-key.json'),
env: 'PUBLIC_KEY_FILE',
},
oldPublicKeyFile: {
format: String,
doc: 'Previous publicKeyFile, used for key rotation',
default: undefined,
env: 'OLD_PUBLIC_KEY_FILE',
},
vapidKeysFile: {
doc: 'Keys to use for VAPID in push notifications',
format: String,
default: path.resolve(__dirname, '../config/vapid-keys.json'),
env: 'VAPID_KEYS_FILE',
},
database: {
mysql: {
auth: makeMySQLConfig('AUTH', 'fxa'),
},
},
listen: {
host: {
doc: 'The ip address the server should bind',
default: 'localhost',
format: String,
env: 'IP_ADDRESS',
},
port: {
doc: 'The port the server should bind',
default: 9000,
format: 'port',
env: 'PORT',
},
},
customsUrl: {
doc: "fraud / abuse server url; set to the string 'none' to disable",
default: 'http://localhost:7000',
env: 'CUSTOMS_SERVER_URL',
},
customsHttpAgent: {
maxSockets: {
doc: 'The maximum number of sockets to be opened per host',
default: 10000,
env: 'CUSTOMS_MAX_SOCKETS',
},
maxFreeSockets: {
doc: 'The maximum number of free sockets to keep open for a host',
default: 10,
env: 'CUSTOMS_MAX_FREE_SOCKETS',
},
timeoutMs: {
doc: 'The timeout in milliseconds for the sockets',
default: 30000,
env: 'CUSTOMS_TIMEOUT_MS',
},
freeSocketTimeoutMs: {
doc: 'The time in milliseconds for which a socket should remain open while unused',
default: 15000,
env: 'CUSTOMS_FREE_SOCKET_TIMEOUT_MS',
},
},
contentServer: {
url: {
doc: 'The url of the corresponding fxa-content-server instance',
default: 'http://localhost:3030',
env: 'CONTENT_SERVER_URL',
},
},
smtp: {
api: {
host: {
doc: 'host for test/mail_helper.js',
default: 'localhost',
env: 'MAILER_HOST',
},
port: {
doc: 'port for test/mail_helper.js',
default: 9001,
env: 'MAILER_PORT',
},
},
brandMessagingMode: {
doc: 'The type of messaging to show. Options are prelaunch, postlaunch, or none',
format: String,
default: 'none',
env: 'BRAND_MESSAGING_MODE',
},
host: {
doc: 'SMTP host for sending email',
default: 'localhost',
env: 'SMTP_HOST',
},
port: {
doc: 'SMTP port',
default: 25,
env: 'SMTP_PORT',
},
secure: {
doc: 'Connect to SMTP host securely',
default: false,
env: 'SMTP_SECURE',
},
user: {
doc: 'SMTP username',
format: String,
default: undefined,
env: 'SMTP_USER',
},
password: {
doc: 'SMTP password',
format: String,
default: undefined,
env: 'SMTP_PASS',
},
sender: {
doc: 'email address of the sender',
default: 'Mozilla <no-reply@lcip.org>',
env: 'SMTP_SENDER',
},
pool: {
default: false,
doc: 'Should pooling be enabled for sending mail?',
env: 'SMTP_POOL',
format: Boolean,
},
maxMessages: {
default: 10,
doc: 'Maximum number of messages to be sent via nodemailer before a new SES SMTP connection is made',
env: 'SMTP_MAX_MESSAGES',
format: Number,
},
maxConnections: {
default: 2,
doc: 'Maximum number of simultaneous connections to make against the SES SMTP server',
env: 'SMTP_MAX_CONNECTIONS',
format: Number,
},
prependVerificationSubdomain: {
enabled: {
doc: 'Flag to prepend a verification subdomain to verification emails',
default: false,
env: 'PREPEND_VERIFICATION_SUBDOMAIN_ENABLED',
},
subdomain: {
doc: 'Prepend this subdomain',
format: String,
default: 'confirm',
env: 'PREPEND_VERIFICATION_SUBDOMAIN_SUBDOMAIN',
},
},
firefoxDesktopUrl: {
doc: 'url to download Firefox page',
format: String,
default:
'https://firefox.com?utm_content=registration-confirmation&utm_medium=email&utm_source=fxa',
},
androidUrl: {
doc: 'url to Android product page',
format: String,
default:
'https://app.adjust.com/2uo1qc?campaign=fxa-conf-email&adgroup=android&creative=button&utm_source=email',
},
iosUrl: {
doc: 'url to IOS product page',
format: String,
default:
'https://app.adjust.com/2uo1qc?campaign=fxa-conf-email&adgroup=ios&creative=button&fallback=https%3A%2F%2Fitunes.apple.com%2Fapp%2Fapple-store%2Fid989804926%3Fpt%3D373246%26ct%3Dadjust_tracker%26mt%3D8&utm_source=email',
},
supportUrl: {
doc: 'url to Mozilla Support product page',
format: String,
default:
'https://support.mozilla.org/kb/im-having-problems-my-firefox-account',
},
redirectDomain: {
doc: 'Domain that mail urls are allowed to redirect to',
format: String,
default: 'firefox.com',
env: 'REDIRECT_DOMAIN',
},
privacyUrl: {
doc: 'url to Mozilla Accounts privacy page',
format: String,
default: 'https://www.mozilla.org/privacy/mozilla-accounts/',
},
passwordManagerInfoUrl: {
doc: 'url to Firefox password manager information',
format: String,
default:
'https://support.mozilla.org/kb/password-manager-remember-delete-change-and-import#w_viewing-and-deleting-passwords',
},
subscriptionTermsUrl: {
default:
'https://www.mozilla.org/about/legal/terms/firefox-private-network/',
doc: 'Subscription terms and cancellation policy URL',
env: 'SUBSCRIPTION_TERMS_URL',
format: String,
},
unsubscribeUrl: {
doc: 'URL to unsubscribe from MoCo and MoFo emails',
format: String,
env: 'UNSUBSCRIBE_EMAIL_LISTS_URL',
default:
'https://privacyportal.onetrust.com/webform/1350748f-7139-405c-8188-22740b3b5587/4ba08202-2ede-4934-a89e-f0b0870f95f0',
},
sesConfigurationSet: {
doc:
'AWS SES Configuration Set for SES Event Publishing. If defined, ' +
'X-SES-MESSAGE-TAGS headers will be added to emails. Only ' +
'intended for Production/Stage use.',
format: String,
default: '',
env: 'SES_CONFIGURATION_SET',
},
bounces: {
enabled: {
doc: 'Flag to enable checking for bounces before sending email',
format: Boolean,
default: true,
env: 'BOUNCES_ENABLED',
},
complaint: {
doc: 'Tiers of max allowed complaints per amount of milliseconds',
format: Object,
default: {
// 0 are allowed in the past day.
// 1 is allowed in the past year.
0: ONE_DAY,
1: 365 * ONE_DAY,
},
env: 'BOUNCES_COMPLAINT',
},
hard: {
doc: 'Tiers of max allowed hard bounces per amount of milliseconds',
format: Object,
default: {
// # of bounces allowed : time since last bounce
0: 3 * FIVE_MINUTES,
1: ONE_DAY,
},
env: 'BOUNCES_HARD',
},
soft: {
doc: 'Tiers of max allowed soft bounces per amount of milliseconds',
format: Object,
default: {
0: FIVE_MINUTES,
},
env: 'BOUNCES_SOFT',
},
ignoreTemplates: {
doc: 'Always ignore bounces from these email templates',
format: Array,
default: [
'verifyLoginCode',
'verifyLogin',
'recovery',
'unblockCode',
'subscriptionAccountFinishSetup',
],
env: 'BOUNCES_IGNORE_TEMPLATES',
},
},
connectionTimeout: {
doc: 'Milliseconds to wait for the connection to establish (default is 2 minutes)',
format: 'int',
default: 120000,
env: 'SMTP_CONNECTION_TIMEOUT',
},
greetingTimeout: {
doc: 'Milliseconds to wait for the greeting after connection is established (default is 30 seconds)',
format: 'int',
default: 30000,
env: 'SMTP_GREETING_TIMEOUT',
},
socketTimeout: {
doc: 'Milliseconds of inactivity to allow (default is 10 minutes)',
format: 'int',
default: 600000,
env: 'SMTP_SOCKET_TIMEOUT',
},
dnsTimeout: {
doc: 'Milliseconds to wait for DNS requests to be resolved (default is 30 seconds)',
format: 'int',
default: 30000,
env: 'SMTP_DNS_TIMEOUT',
},
},
maxEventLoopDelay: {
doc: 'Max event-loop delay before which incoming requests are rejected',
default: 0,
env: 'MAX_EVENT_LOOP_DELAY',
},
scrypt: {
maxPending: {
doc: 'Max number of scrypt hash operations that can be pending',
default: 0,
env: 'SCRYPT_MAX_PENDING',
},
},
i18n: {
defaultLanguage: {
format: String,
default: 'en',
env: 'DEFAULT_LANG',
},
supportedLanguages: {
format: Array,
default: DEFAULT_SUPPORTED_LANGUAGES,
env: 'SUPPORTED_LANGS',
},
},
redis: makeRedisConfig(),
subhubServerMessaging: {
region: {
doc: 'The region where the queues live',
format: String,
env: 'SUBHUB_REGION',
default: '',
},
subhubUpdatesQueueUrl: {
doc: 'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
format: String,
env: 'SUBHUB_QUEUE_URL',
default: '',
},
},
tokenLifetimes: {
accountResetToken: {
format: 'duration',
env: 'ACCOUNT_RESET_TOKEN_TTL',
default: '15 minutes',
},
passwordForgotToken: {
format: 'duration',
env: 'PASSWORD_FORGOT_TOKEN_TTL',
default: '24 hours',
},
passwordChangeToken: {
format: 'duration',
env: 'PASSWORD_CHANGE_TOKEN_TTL',
default: '15 minutes',
},
sessionTokenWithoutDevice: {
doc: 'Maximum age for session tokens without a device record, specify zero to disable',
format: 'duration',
env: 'SESSION_TOKEN_WITHOUT_DEVICE_TTL',
default: '4 weeks',
},
},
tokenPruning: {
enabled: {
doc: 'Turn on pruning for tokens',
format: Boolean,
default: true,
env: 'TOKEN_PRUNING_ENABLED',
},
maxAge: {
doc: 'Age at which to prune. (Set to 0 to disable token pruning)',
format: 'duration',
default: '1 month',
env: 'TOKEN_PRUNING_MAX_AGE',
},
},
verifierVersion: {
doc: 'verifer version for new and changed passwords',
format: 'int',
env: 'VERIFIER_VERSION',
default: 1,
},
snsTopicArn: {
doc: 'Amazon SNS topic on which to send account event notifications. Set to "disabled" to turn off the notifier',
format: String,
env: 'SNS_TOPIC_ARN',
default: '',
},
snsTopicEndpoint: {
doc: 'Amazon SNS topic endpoint',
format: String,
env: 'SNS_TOPIC_ENDPOINT',
default: undefined,
},
emailNotifications: {
region: {
doc: 'The region where the queues live, most likely the same region we are sending email e.g. us-east-1, us-west-2',
format: String,
env: 'BOUNCE_REGION',
default: '',
},
bounceQueueUrl: {
doc: 'The bounce queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
format: String,
env: 'BOUNCE_QUEUE_URL',
default: '',
},
complaintQueueUrl: {
doc: 'The complaint queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
format: String,
env: 'COMPLAINT_QUEUE_URL',
default: '',
},
deliveryQueueUrl: {
doc: 'The email delivery queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
format: String,
env: 'DELIVERY_QUEUE_URL',
default: '',
},
notificationQueueUrl: {
doc: 'Queue URL for notifications from fxa-email-service (eventually this will be the only email-related queue)',
format: String,
env: 'NOTIFICATION_QUEUE_URL',
default: '',
},
},
profileServerMessaging: {
region: {
doc: 'The region where the queues live',
format: String,
env: 'PROFILE_MESSAGING_REGION',
default: '',
},
profileUpdatesQueueUrl: {
doc: 'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
format: String,
env: 'PROFILE_UPDATES_QUEUE_URL',
default: '',
},
},
profileServer: {
url: {
doc: 'The url of the corresponding fxa-profile-server instance',
env: 'PROFILE_SERVER_URL',
format: 'url',
default: 'https://profile.accounts.firefox.com',
},
secretBearerToken: {
default: 'YOU MUST CHANGE ME',
doc: 'Secret for server-to-server bearer token auth for fxa-profile-server',
env: 'PROFILE_SERVER_AUTH_SECRET_BEARER_TOKEN',
format: 'String',
},
},
useHttps: {
doc: 'set to true to serve directly over https',
env: 'USE_TLS',
default: false,
},
keyPath: {
doc: 'path to SSL key in PEM format if serving over https',
env: 'TLS_KEY_PATH',
default: path.resolve(__dirname, '../key.pem'),
},
certPath: {
doc: 'path to SSL certificate in PEM format if serving over https',
env: 'TLS_CERT_PATH',
default: path.resolve(__dirname, '../cert.pem'),
},
lockoutEnabled: {
doc: 'Is account lockout enabled',
format: Boolean,
env: 'LOCKOUT_ENABLED',
default: false,
},
// A safety switch to disable device metadata updates,
// in case problems with the client logic cause server overload.
deviceUpdatesEnabled: {
doc: 'Are updates to device metadata enabled?',
format: Boolean,
env: 'DEVICE_UPDATES_ENABLED',
default: true,
},
// A safety switch to disable device-driven notifications,
// in case problems with the client logic cause server overload.
deviceNotificationsEnabled: {
doc: 'Are device-driven notifications enabled?',
format: Boolean,
env: 'DEVICE_NOTIFICATIONS_ENABLED',
default: true,
},
subhub: {
enabled: {
doc: 'Indicates whether talking to the SubHub server is enabled',
format: Boolean,
default: false,
env: 'SUBHUB_ENABLED',
},
useStubs: {
doc: 'Indicates whether to use stub methods for SubHub instead of talking to the server',
format: Boolean,
default: false,
env: 'SUBHUB_USE_STUBS',
},
stubs: {
plans: {
doc: 'Stub data used for plans',
format: Array,
env: 'SUBHUB_STUB_PLANS',
default: [],
},
},
url: {
doc: 'SubHub Server URL',
format: 'url',
default: 'https://subhub.services.mozilla.com/',
env: 'SUBHUB_URL',
},
key: {
doc: 'Authentication key to use when accessing SubHub server',
format: String,
default: 'YOU MUST CHANGE ME',
env: 'SUBHUB_KEY',
},
customerCacheTtlSeconds: {
doc: 'The number of seconds to cache a Stripe Customer response',
format: 'int',
default: 3600,
env: 'SUBHUB_CUSTOMER_CACHE_TTL_SECONDS',
},
plansCacheTtlSeconds: {
doc: 'The number of seconds to cache the list of plans from subhub',
format: 'int',
default: 64000000, // about a couple years
env: 'SUBHUB_PLANS_CACHE_TTL_SECONDS',
},
stripeTaxRatesCacheTtlSeconds: {
doc: 'The number of seconds to cache tax rates from Stripe',
format: 'int',
default: 64000000, // about a couple years
env: 'SUBHUB_TAX_RATES_CACHE_TTL_SECONDS',
},
},
subscriptions: {
enabled: {
doc: 'Indicates whether subscriptions APIs are enabled',
format: Boolean,
env: 'SUBSCRIPTIONS_ENABLED',
default: false,
},
paymentsServer: {
url: {
doc: 'The url of the corresponding fxa-payments-server instance',
env: 'PAYMENTS_SERVER_URL',
format: 'url',
default: 'https://subscriptions.firefox.com',
},
},
paypalNvpSigCredentials: {
enabled: {
doc: 'Indicates whether PayPal APIs are enabled',
format: Boolean,
env: 'SUBSCRIPTIONS_PAYPAL_ENABLED',
default: false,
},
sandbox: {
doc: 'PayPal Sandbox mode',
format: Boolean,
env: 'PAYPAL_SANDBOX',
default: true,
},
user: {
doc: 'PayPal NVP API User name',
format: String,
default: 'user',
env: 'PAYPAL_NVP_USER',
},
pwd: {
doc: 'PayPal NVP API password',
format: String,
default: 'user',
env: 'PAYPAL_NVP_PWD',
},
signature: {
doc: 'PayPal NVP API signature',
format: String,
default: 'user',
env: 'PAYPAL_NVP_SIGNATURE',
},
},
appStore: {
credentials: {
doc: 'Map of AppStore Connect credentials by app bundle ID',
format: Object,
default: {
// Cannot use an actual bundleId (e.g. 'org.mozilla.ios.FirefoxVPN') as the key
// due to https://github.com/mozilla/node-convict/issues/250
org_mozilla_ios_FirefoxVPN: {
issuerId: 'issuer_id',
serverApiKey: 'key',
serverApiKeyId: 'key_id',
},
},
env: 'APP_STORE_CREDENTIALS',
},
enabled: {
doc: 'Indicates whether the App Store API is enabled',
format: Boolean,
default: false,
env: 'SUBSCRIPTIONS_APP_STORE_API_ENABLED',
},
sandbox: {
doc: 'Apple App Store Sandbox mode',
format: Boolean,
env: 'APP_STORE_SANDBOX',
default: true,
},
},
playApiServiceAccount: {
credentials: {
client_email: {
default: 'test@localtest.com',
doc: 'The email of the service account to use for Play API calls',
env: 'SUBSCRIPTIONS_PLAY_CLIENT_EMAIL_CREDENTIAL',
format: String,
},
private_key: {
default: '',
doc: 'The private key of the service account to use for Play API calls',
env: 'SUBSCRIPTIONS_PLAY_PRIVATE_KEY_CREDENTIAL',
format: String,
},
},
keyFilename: {
default: '',
doc: 'Path to GCP key file',
env: 'SUBSCRIPTIONS_PLAY_KEY_FILE',
format: String,
},
projectId: {
default: '',
doc: 'GCP Project id for Play Store Account',
env: 'SUBSCRIPTIONS_PLAY_API_PROJECT_ID',
format: String,
},
enabled: {
doc: 'Indicates whether the Play API is enabled',
format: Boolean,
default: false,
env: 'SUBSCRIPTIONS_PLAY_API_ENABLED',
},
},
productConfigsFirestore: {
enabled: {
default: false,
doc: 'Whether to use Firestore for product configurations',
env: 'SUBSCRIPTIONS_FIRESTORE_CONFIGS_ENABLED',
format: Boolean,
},
schemaValidation: {
cdnUrlRegex: {
default: '^https://accounts-static.cdn.mozilla.net',
doc: 'CDN URL Regex',
env: 'SUBSCRIPTIONS_FIRESTORE_CONFIGS_CDN_URL_REGEX',
format: String,
},
},
},
sharedSecret: {
doc: 'Shared secret for authentication between backend subscription services',
format: String,
default: 'YOU MUST CHANGE ME',
env: 'SUBSCRIPTIONS_SHARED_SECRET',
},
stripeApiKey: {
default: '',
env: 'SUBHUB_STRIPE_APIKEY',
format: String,
doc: 'Stripe API key for direct Stripe integration',
},
stripeWebhookPayloadLimit: {
default: 1048576,
env: 'STRIPE_WEBHOOK_PAYLOAD_LIMIT',
format: 'int',
doc: 'The maximum payload size in bytes that will be accepted by the Stripe webhook endpoint',
},
stripeWebhookSecret: {
default: '',
env: 'STRIPE_WEBHOOK_SECRET',
format: String,
doc: 'A shared secret to authenticate Stripe webhook requests',
},
taxIds: {
doc: 'Mapping of currency to tax ID to show on invoices.',
format: Object,
default: {
EUR: 'EU1234',
CHF: 'CH1234',
},
env: 'TAXIDS',
},
transactionalEmails: {
// See also: https://jira.mozilla.com/browse/FXA-1148
enabled: {
doc: 'Indicates whether FxA sends transactional lifecycle emails for subscriptions (i.e. versus Marketing Cloud)',
format: Boolean,
env: 'SUBSCRIPTIONS_TRANSACTIONAL_EMAILS_ENABLED',
default: true,
},
},
unsupportedLocations: {
doc: 'list of unsupported locations according to ToS',
default: [
'CN',
'KP',
'IR',
'SY',
'CU',
'SD',
'BY',
'IQ',
'OM',
'RU',
'TR',
'TM',
'AE',
],
env: 'SUBSCRIPTIONS_UNSUPPORTED_LOCATIONS',
format: Array,
},
},
currenciesToCountries: {
doc: 'Mapping from ISO 4217 three-letter currency codes to list of ISO 3166-1 alpha-2 two-letter country codes: {"EUR": ["DE", "FR"], "USD": ["CA", "GB", "US" ]} Requirement for only one currency per country. Tested at runtime. Must be uppercased.',
format: Object,
default: {
USD: ['US', 'GB', 'NZ', 'MY', 'SG', 'CA', 'AS', 'GU', 'MP', 'PR', 'VI'],
EUR: ['FR', 'DE'],
},
env: 'CURRENCIES',
},
oauth: {
url: {
format: 'url',
doc: 'URL at which to verify OAuth tokens',
default: 'http://localhost:9000',
env: 'OAUTH_URL',
},
keepAlive: {
format: Boolean,
doc: 'Use HTTP keep-alive connections when talking to oauth server',
env: 'OAUTH_KEEPALIVE',
default: false,
},
clientIds: {
doc: 'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
format: Object,
default: {},
env: 'OAUTH_CLIENT_IDS',
},
oldSyncClientIds: {
doc: 'Client IDs of sync clients that migrated to OAuth.',
format: Array,
default: ['5882386c6d801776', '1b1a3e44c54fbb58'],
env: 'OAUTH_OLD_SYNC_CLIENT_IDS',
},
// A safety switch for disabling new signins/signups from particular clients,
// as a hedge against unexpected client behaviour.
disableNewConnectionsForClients: {
doc: 'Comma-separated list of oauth client ids for which new connections should be temporarily refused',
env: 'OAUTH_DISABLE_NEW_CONNECTIONS_FOR_CLIENTS',
format: Array,
default: [],
},
// Some safety switches to disable oauth-based device API operations,
// in case problems with the client logic cause server overload.
deviceAccessEnabled: {
doc: 'Is oauth-based access to the devices API allowed at all?',
format: Boolean,
env: 'OAUTH_DEVICE_ACCESS_ENABLED',
default: true,
},
deviceCommandsEnabled: {
doc: 'Are oauth-based devices allowed to use device commands?',
format: Boolean,
env: 'OAUTH_DEVICE_COMMANDS_ENABLED',
default: true,
},
clientInfoCacheTTL: {
doc: 'TTL for OAuth client details (in milliseconds)',
format: 'duration',
default: '3 days',
env: 'OAUTH_CLIENT_INFO_CACHE_TTL',
},
secretKey: {
doc: 'Shared secret for signing auth-to-oauth server JWT assertions',
env: 'OAUTH_SERVER_SECRET_KEY',
format: String,
default: 'megaz0rd',
},
jwtSecretKeys: {
doc: 'Comma-separated list of secret keys for verifying oauth-to-auth server JWTs',
env: 'OAUTH_SERVER_SECRETS',
format: Array,
default: ['megaz0rd'],
},
},
oauthServer: {
admin: {
whitelist: {
doc: 'An array of regexes. Passing any one will get through.',
default: ['@mozilla\\.com$'],
},
},
allowHttpRedirects: {
arg: 'allowHttpRedirects',
doc: 'If true, then it allows http OAuth redirect uris',
env: 'ALLOW_HTTP_REDIRECTS',
format: 'Boolean',
default: false,
},
audience: {
doc: 'audience for oauth JWTs',
format: 'url',
default: 'http://localhost:9000',
env: 'OAUTH_URL',
},
auth: {
jwtSecretKey: {
default: 'megaz0rd',
doc: 'Shared secret for signing oauth-to-auth server JWT assertions',
format: 'String',
env: 'AUTH_SERVER_SHARED_SECRET',
},
url: {
default: 'http://localhost:9000',
doc: 'The auth-server public URL',
env: 'AUTH_SERVER_URL',
format: 'url',
},
},
authServerSecrets: {
doc: 'Comma-separated list of secret keys for verifying server-to-server JWTs',
env: 'AUTH_SERVER_SECRETS',
format: 'Array',
default: [],
},
browserid: {
issuer: {
doc: 'We only accept assertions from this issuer',
env: 'ISSUER',
default: 'api.accounts.firefox.com',
},
maxSockets: {
doc: 'The maximum number of connections that the pool can use at once.',
env: 'BROWSERID_MAX_SOCKETS',
default: 10,
},
verificationUrl: {
doc: 'URL to the remote verifier we will use for fxa-assertions',
format: 'url',
env: 'VERIFICATION_URL',
default: 'https://verifier.accounts.firefox.com/v2',
},
},
clients: {
doc: 'Some pre-defined clients that will be inserted into the DB',
env: 'OAUTH_CLIENTS',
format: 'Array',
default: [],
},
clientManagement: {
enabled: {
doc: 'Enable client management in OAuth server routes. Do NOT set this to true in production.',
default: false,
format: 'Boolean',
env: 'CLIENT_MANAGEMENT_ENABLED',
},
},
clientIdToServiceNames: {
// This is used by oauth/db/index.js to identify pocket client ids so that it
// can store them separately in mysql.
// It's also used for amplitude stats
doc: 'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
default: {},
format: 'Object',
env: 'OAUTH_CLIENT_IDS',
},
disabledClients: {
doc: 'Comma-separated list of client ids for which service should be temporarily refused',
env: 'OAUTH_CLIENTS_DISABLED',
format: 'Array',
default: [],
},
scopes: {
doc: 'Some pre-defined list of scopes that will be inserted into the DB',
env: 'OAUTH_SCOPES',
format: 'Array',
default: [],
},
clientAddressDepth: {
doc: 'location of the client ip address in the remote address chain',
env: 'CLIENT_ADDRESS_DEPTH',
default: 3,
},
contentUrl: {
doc: 'URL to UI page in fxa-content-server that starts OAuth flow',
format: 'url',
env: 'CONTENT_URL',
default: 'https://accounts.firefox.com/oauth/',
},
db: {
driver: {
env: 'DB',
format: ['mysql'],
default: 'mysql',
},
autoUpdateClients: {
doc: 'If true, update clients from config file settings',
env: 'DB_AUTO_UPDATE_CLIENTS',
format: 'Boolean',
default: true,
},
},
env: {
arg: 'node-env',
doc: 'The current node.js environment',
env: 'NODE_ENV',
format: ['dev', 'test', 'stage', 'prod'],
default: 'prod',
},
events: {
enabled: {
doc: 'Whether or not config.events has to be properly set in production',
default: true,
format: 'Boolean',
env: 'EVENTS_ENABLED',
},
region: {
doc: 'AWS Region of fxa account events',
default: '',
format: 'String',
env: 'FXA_EVENTS_REGION',
},
queueUrl: {
doc: 'SQS queue url for fxa account events',
default: '',
format: 'String',
env: 'FXA_EVENTS_QUEUE_URL',
},
},
expiration: {
accessToken: {
doc: 'Access Tokens maximum expiration (can live shorter)',
format: 'duration',
// Warning: here be dragons. 365 minutes is 6 hours plus 5 minutes.
// This value is intended to be just slightly larger than the constant
// that determines if a JWT is stored in the database or not,
// SHORT_ACCESS_TTL_TOKEN_IN_MS (found in fxa-shared/oauth/constants), currently
// 6 hours. We want to keep this FXA_EXPIRATION_ACCESS_TOKEN default
// value greater than 6 hours, because tokens not backed by the
// database are validated slightly differently (see lib/oauth/token).
// Setting this FXA_EXPIRATION_ACCESS_TOKEN config value at or below
// SHORT_ACCESS_TTL_TOKEN_IN_MS should be done with caution. See #5143
// and the discussion in #6368.
default: '365 minutes',
env: 'FXA_EXPIRATION_ACCESS_TOKEN',
},
accessTokenExpiryEpoch: {
doc: 'Timestamp after which access token expiry is actively enforced',
format: 'timestamp',
default: '2017-01-01',
env: 'FXA_EXPIRATION_ACCESS_TOKEN_EXPIRY_EPOCH',
},
code: {
doc: 'Clients must trade codes for tokens before they expire',
format: 'duration',
default: '15 minutes',
env: 'FXA_EXPIRATION_CODE',
},
},
refreshToken: {
updateAfter: {
doc: 'lastUsedAt only gets updated in MySQL after this delay',
format: 'duration',
default: '24 hours',
env: 'FXA_REFRESH_TOKEN_UPDATE_AFTER',
},
},
git: {
commit: {
doc: 'Commit SHA when in stage/production',
format: 'String',
default: '',
},
},
jwtAccessTokens: {
enabled: {
doc: 'Whether or not JWT access tokens are enabled',
default: true,
format: 'Boolean',
env: 'JWT_ACCESS_TOKENS_ENABLED',
},
enabledClientIds: {
doc: 'JWT access tokens are only returned for client_ids in this list',
default: [],
format: 'Array',
env: 'JWT_ACCESS_TOKENS_ENABLED_CLIENT_IDS',
},
},
localRedirects: {
doc: 'When true, `localhost` and `localhost` always are legal redirects.',
default: false,
env: 'FXA_OAUTH_LOCAL_REDIRECTS',
},
mysql: {
createSchema: { default: false, env: 'CREATE_MYSQL_SCHEMA' },
user: { default: 'root', env: 'MYSQL_USERNAME' },
password: { default: '', env: 'MYSQL_PASSWORD' },
database: { default: 'fxa_oauth', env: 'MYSQL_DATABASE' },
host: { default: 'localhost', env: 'MYSQL_HOST' },
port: { default: '3306', env: 'MYSQL_PORT' },
connectionLimit: {
doc: 'The maximum number of connections that the pool can use at once.',
default: 10,
env: 'MYSQL_CONNECTION_LIMIT',
},
queueLimit: {
doc: 'The maximum number of connection requests the pool will queue before returning an error.',
default: 0,
env: 'MYSQL_QUEUE_LIMIT',
},
idleLimitMs: {
doc: 'The number of milliseconds a connection can be idle before it is closed.',
format: Number,
default: 10000,
env: 'MYSQL_IDLE_LIMIT_MS',
},
timezone: {
default: 'Z',
doc: 'The timezone configured on the MySQL server. This is used to type cast server date/time values to JavaScript `Date` object. Can be `local`, `Z`, or an offset in the form of or an offset in the form +HH:MM or -HH:MM.',
env: 'MYSQL_TIMEZONE',
format: 'String',
},
},
openid: {
keyFile: {
doc: 'Path to private key JWK to sign various kinds of JWT tokens',
default: '',
format: 'String',
env: 'FXA_OPENID_KEYFILE',
},
newKeyFile: {
doc: 'Path to private key JWK that will be used to sign JWTs in the future',
default: '',
format: 'String',
env: 'FXA_OPENID_NEWKEYFILE',
},
oldKeyFile: {
doc: 'Path to public key JWK that was used to sign JWTs in the past',
default: '',
format: 'String',
env: 'FXA_OPENID_OLDKEYFILE',
},
key: {
doc: 'Private key JWK to sign various kinds of JWT tokens',
default: {},
env: 'FXA_OPENID_KEY',
},
newKey: {
doc: 'Private key JWK that will be used to sign JWTs in the future',
default: {},
env: 'FXA_OPENID_NEWKEY',
},
oldKey: {
doc: 'Public key JWK that was used to sign JWTs in the past',
default: {},
env: 'FXA_OPENID_OLDKEY',
},
unsafelyAllowMissingActiveKey: {
doc: 'Do not error out if there is no active key; should only be used when initializing keys',
default: false,
format: 'Boolean',
env: 'FXA_OPENID_UNSAFELY_ALLOW_MISSING_ACTIVE_KEY',
},
issuer: {
doc: 'The value of the `iss` property of the id_token',
default: 'https://accounts.firefox.com',
env: 'FXA_OPENID_ISSUER',
},
ttl: {
doc: 'Number of milliseconds until id_token should expire',
default: '5 minutes',
format: 'duration',
env: 'FXA_OPENID_TTL',
},
},
ppid: {
enabled: {
doc: 'Whether pairwise pseudonymous identifiers (PPIDs) are enabled',
default: false,
format: 'Boolean',
env: 'PPID_ENABLED',
},
enabledClientIds: {
doc: 'client_ids that receive PPIDs',
default: [],
format: 'Array',
env: 'PPID_CLIENT_IDS',
},
rotatingClientIds: {
doc: 'client_ids that receive automatically rotating PPIDs based on server time',
default: [],
format: 'Array',
env: 'PPID_ROTATING_CLIENT_IDS',
},
rotationPeriodMS: {
doc: 'salt used in HKDF for PPIDs, converted to milliseconds',
format: 'duration',
default: '6 hours',
env: 'PPID_ROTATION_PERIOD',
},
salt: {
doc: 'salt used in HKDF for PPIDs',
default: 'YOU MUST CHANGE ME',
format: 'String',
env: 'PPID_SALT',
},
},
publicUrl: {
format: 'url',
default: 'http://localhost:9000',
env: 'PUBLIC_URL',
},
server: {
host: { env: 'HOST', default: 'localhost' },
port: { env: 'PORT', format: 'port', default: 9000 },
},
serverInternal: {
host: { env: 'HOST_INTERNAL', default: 'localhost' },
port: { env: 'PORT_INTERNAL', format: 'port', default: 9011 },
},
i18n: {
defaultLanguage: {
default: 'en',
format: 'String',
env: 'DEFAULT_LANG',
},
supportedLanguages: {
default: [],
format: 'Array',
env: 'SUPPORTED_LANGS',
},
},
unique: {
clientSecret: {
doc: 'Bytes of generated client_secrets',
default: 32,
},
code: { doc: 'Bytes of generated codes', default: 32 },
id: { doc: 'Bytes of generated DB ids', default: 8 },
token: { doc: 'Bytes of generated tokens', default: 32 },
developerId: { doc: 'Bytes of generated developer ids', default: 16 },
},
cacheControl: {
doc: 'Hapi: a string with the value of the "Cache-Control" header when caching is disabled',
format: 'String',
default: 'private, no-cache, no-store, must-revalidate',
},
},
metrics: {
flow_id_key: {
default: 'YOU MUST CHANGE ME',
doc: 'FlowId validation key, as used by content-server',
format: String,
env: 'FLOW_ID_KEY',
},
flow_id_expiry: {
doc: 'Time after which flowIds are considered stale.',
format: 'duration',
default: '2 hours',
env: 'FLOW_ID_EXPIRY',
},
},
statsd: {
enabled: {
doc: 'Enable StatsD',
format: Boolean,
default: false,
env: 'STATSD_ENABLE',
},
sampleRate: {
doc: 'Sampling rate for StatsD',
format: Number,
default: 1,
env: 'STATSD_SAMPLE_RATE',
},
maxBufferSize: {
doc: 'StatsD message buffer size in number of characters',
format: Number,
default: 500,
env: 'STATSD_BUFFER_SIZE',
},
host: {
doc: 'StatsD host to report to',
format: String,
default: 'localhost',
env: 'DD_AGENT_HOST',
},
port: {
doc: 'Port number of StatsD server',
format: Number,
default: 8125,
env: 'DD_DOGSTATSD_PORT',
},
prefix: {
doc: 'StatsD metrics name prefix',
format: String,
default: 'fxa-auth-server.',
env: 'STATSD_PREFIX',
},
},
corsOrigin: {
doc: 'Value for the Access-Control-Allow-Origin response header',
format: Array,
env: 'CORS_ORIGIN',
default: ['*'],
},
clientAddressDepth: {
doc: 'location of the client ip address in the remote address chain',
format: Number,
env: 'CLIENT_ADDRESS_DEPTH',
default: 3,
},
remoteAddressChainOverride: {
doc: 'Override address chain with this chain. Should be comma separated list of IPs',
format: String,
env: 'REMOTE_ADDRESS_CHAIN_OVERRIDE',
default: '',
},
signinConfirmation: {
forcedEmailAddresses: {
doc: 'Force sign-in confirmation for email addresses matching this regex.',
format: RegExp,
default: /.+@mozilla\.com$/,
env: 'SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX',
},
skipForEmailAddresses: {
doc: 'Comma separated list of email addresses that will always skip any non TOTP sign-in confirmation',
format: Array,
default: [],
env: 'SIGNIN_CONFIRMATION_SKIP_FOR_EMAIL_ADDRESS',
},
skipForNewAccounts: {
enabled: {
doc: 'Skip sign-in confirmation for newly-created accounts.',
default: true,
env: 'SIGNIN_CONFIRMATION_SKIP_FOR_NEW_ACCOUNTS',
},
maxAge: {
doc: 'Maximum age at which an account is considered "new".',
format: 'duration',
default: '4 hours',
env: 'SIGNIN_CONFIRMATION_MAX_AGE_OF_NEW_ACCOUNTS',
},
},
tokenVerificationCode: {
codeLength: {
doc: '(Deprecated) Number of digits to make up a token code',
default: 6,
env: 'SIGNIN_TOKEN_CODE_LENGTH',
},
codeLifetime: {
doc: '(Deprecated) How long code should be valid for',
format: 'duration',
default: '20 minutes',
env: 'SIGNIN_TOKEN_CODE_LIFETIME',
},
},
forceGlobally: {
doc: 'Force sign-in confirmation for all accounts',
format: Boolean,
default: false,
env: 'SIGNIN_CONFIRMATION_FORCE_GLOBALLY',
},
},
forcePasswordChange: {
forcedEmailAddresses: {
doc: 'Force password change for email addresses matching this regex.',
format: RegExp,
default: /^$/, // default is no one
env: 'FORCE_PASSWORD_CHANGE_EMAIL_REGEX',
},
},
securityHistory: {
ipProfiling: {
allowedRecency: {
doc: 'Length of time since previously verified event to allow skipping confirmation',
default: '72 hours',
format: 'duration',
env: 'IP_PROFILING_RECENCY',
},
},
ipHmacKey: {
doc: 'A secret to hash IP addresses for security history events',
default: 'changeme',
env: 'IP_HMAC_KEY',
},
},
lastAccessTimeUpdates: {
enabled: {
doc: 'enable updates to the lastAccessTime session token property',
format: Boolean,
default: true,
env: 'LASTACCESSTIME_UPDATES_ENABLED',
},
sampleRate: {
doc: 'sample rate for updates to the lastAccessTime session token property, in the range 0..1',
format: Number,
default: 0.3,
env: 'LASTACCESSTIME_UPDATES_SAMPLE_RATE',
},
earliestSaneTimestamp: {
doc: 'timestamp used as the basis of the fallback value for lastAccessTimeFormatted, currently pinned to the deployment of 1.96.4 / a0940d7dc51e2ba20fa18aa3a830810e35c9a9d9',
format: 'timestamp',
default: 1507081020000,
env: 'LASTACCESSTIME_EARLIEST_SANE_TIMESTAMP',
},
onOAuthTokenCreation: {
doc: 'update lastAccessTime in Redis when the session token is used to create an OAuth token',
format: Boolean,
default: true,
env: 'LASTACCESSTIME_UPDATES_ON_OAUTH_TOKEN_CREATION',
},
},
signinUnblock: {
codeLength: {
doc: 'Number of alphanumeric digits to make up an unblockCode',
default: 8,
env: 'SIGNIN_UNBLOCK_CODE_LENGTH',
},
codeLifetime: {
doc: 'How long an unblockCode should be valid for',
format: 'duration',
default: '1 hour',
env: 'SIGNIN_UNBLOCK_CODE_LIFETIME',
},
forcedEmailAddresses: {
doc: 'If feature enabled, force sign-in unblock for email addresses matching this regex.',
format: RegExp,
default: /^$/, // default is no one
env: 'SIGNIN_UNBLOCK_FORCED_EMAILS',
},
},
push: {
allowedServerRegex: {
doc: 'RegExp that validates the URI format of the Push Server',
format: RegExp,
// eslint-disable-next-line no-useless-escape
default:
/^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?::\d+)?(\/.*)?$/,
},
},
pushbox: {
enabled: {
doc: 'Indicates whether Pushbox integration enabled',
format: Boolean,
default: false,
env: 'PUSHBOX_ENABLED',
},
maxTTL: {
doc: 'Maximum TTL to set on items written to pushbox',
format: 'duration',
default: '28 days',
env: 'PUSHBOX_MAX_TTL',
},
database: makeMySQLConfig('PUSHBOX', 'pushbox'),
},
secondaryEmail: {
minUnverifiedAccountTime: {
doc: 'The minimum amount of time an account can be unverified before another account can use it for secondary email',
default: '1 day',
format: 'duration',
env: 'SECONDARY_EMAIL_MIN_UNVERIFIED_ACCOUNT_TIME',
},
},
signinCodeSize: {
doc: 'signinCode size in bytes',
default: 6,
format: 'nat',
env: 'SIGNIN_CODE_SIZE',
},
emailStatusPollingTimeout: {
doc: 'how long before emails status polling is considered stale',
default: '1 month',
format: 'duration',
env: 'EMAIL_STATUS_POLLING_TIMEOUT',
},
sentry: {
dsn: {
doc: 'Sentry DSN for error and log reporting',
default: '',
format: 'String',
env: 'SENTRY_DSN',
},
env: {
doc: 'Environment name to report to sentry. This is the most reliable way to determine the active environment.',
default: 'local',
format: ['local', 'ci', 'dev', 'stage', 'prod'],
env: 'SENTRY_ENV',
},
sampleRate: {
doc: 'Rate at which sentry errors are captured.',
default: 1.0,
format: 'Number',
env: 'SENTRY_SAMPLE_RATE',
},
serverName: {
doc: 'Name used by sentry to identify the server.',
default: 'fxa-auth-server',
format: 'String',
env: 'SENTRY_SERVER_NAME',
},
tracesSampleRate: {
doc: 'Rate at which sentry traces are captured',
default: 0,
format: 'Number',
env: 'SENTRY_TRACES_SAMPLE_RATE',
},
},
totp: {
serviceName: {
doc: 'Default service name to appear in authenticator',
default: 'Mozilla',
format: 'String',
env: 'TOTP_SERVICE_NAME',
},
step: {
doc: 'Default time step size (seconds)',
default: 30,
format: 'nat',
env: 'TOTP_STEP_SIZE',
},
window: {
doc: 'Tokens in the previous x-windows that should be considered valid',
default: 1,
format: 'nat',
env: 'TOTP_WINDOW',
},
recoveryCodes: {
length: {
doc: 'The length of a backup authentication code',
default: 10,
format: 'nat',
env: 'RECOVERY_CODE_LENGTH',
},
count: {
doc: 'Number of backup authentication codes to create',
default: 8,
env: 'RECOVERY_CODE_COUNT',
},
notifyLowCount: {
doc: 'Notify the user when there are less than these many backup authentication codes',
default: 2,
env: 'RECOVERY_CODE_NOTIFY_LOW_COUNT',
},
},
},
verificationReminders: {
rolloutRate: {
doc: 'Rollout rate for verification reminder emails, in the range 0 .. 1',
default: 1,
env: 'VERIFICATION_REMINDERS_ROLLOUT_RATE',
format: Number,
},
firstInterval: {
doc: 'Time since account creation after which the first reminder is sent',
default: '1 day',
env: 'VERIFICATION_REMINDERS_FIRST_INTERVAL',
format: 'duration',
},
secondInterval: {
doc: 'Time since account creation after which the second reminder is sent',
default: '5 days',
env: 'VERIFICATION_REMINDERS_SECOND_INTERVAL',
format: 'duration',
},
finalInterval: {
doc: 'Time since account creation after which the final reminder is sent',
default: '15 days',
env: 'VERIFICATION_REMINDERS_FINAL_INTERVAL',
format: 'duration',
},
redis: {
prefix: {
default: 'verificationReminders:',
doc: 'Key prefix for the verification reminders Redis pool',
env: 'VERIFICATION_REMINDERS_REDIS_PREFIX',
format: String,
},
maxConnections: {
default: 10,
doc: 'Maximum connection count for the verification reminders Redis pool',
env: 'VERIFICATION_REMINDERS_REDIS_MAX_CONNECTIONS',
format: 'nat',
},
minConnections: {
default: 1,
doc: 'Minimum connection count for the verification reminders Redis pool',
env: 'VERIFICATION_REMINDERS_REDIS_MIN_CONNECTIONS',
format: 'nat',
},
},
},
subscriptionAccountReminders: {
rolloutRate: {
doc: 'Rollout rate for subscriptionAccount reminder emails, in the range 0 .. 1',
default: 1,
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_ROLLOUT_RATE',
format: Number,
},
firstInterval: {
doc: 'Time since account creation after which the first reminder is sent',
default: '1 day',
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_FIRST_INTERVAL',
format: 'duration',
},
secondInterval: {
doc: 'Time since account creation after which the second reminder is sent',
default: '5 days',
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_SECOND_INTERVAL',
format: 'duration',
},
redis: {
prefix: {
default: 'subscriptionAccountReminders:',
doc: 'Key prefix for the verification reminders Redis pool',
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_REDIS_PREFIX',
format: String,
},
maxConnections: {
default: 10,
doc: 'Maximum connection count for the subscriptionAccount reminders Redis pool',
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_REDIS_MAX_CONNECTIONS',
format: 'nat',
},
minConnections: {
default: 1,
doc: 'Minimum connection count for the subscriptionAccount reminders Redis pool',
env: 'SUBSCRIPTION_ACCOUNT_REMINDERS_REDIS_MIN_CONNECTIONS',
format: 'nat',
},
},
},
cadReminders: {
rolloutRate: {
doc: 'Rollout rate in the range 0 .. 1',
default: 1,
env: 'CAD_REMINDERS_ROLLOUT_RATE',
format: Number,
},
firstInterval: {
doc: 'Time which the first reminder is sent',
default: '8 hours',
env: 'CAD_REMINDERS_FIRST_INTERVAL',
format: 'duration',
},
secondInterval: {
doc: 'Time which the second reminder is sent',
default: '3 days',
env: 'CAD_REMINDERS_SECOND_INTERVAL',
format: 'duration',
},
redis: {
prefix: {
default: 'cadReminders:',
doc: 'Key prefix for the cad reminders Redis pool',
env: 'CAD_REMINDERS_REDIS_PREFIX',
format: String,
},
maxConnections: {
default: 10,
doc: 'Maximum connection count for the cad reminders Redis pool',
env: 'CAD_REMINDERS_REDIS_MAX_CONNECTIONS',
format: 'nat',
},
minConnections: {
default: 1,
doc: 'Minimum connection count for the cad reminders Redis pool',
env: 'CAD_REMINDERS_REDIS_MIN_CONNECTIONS',
format: 'nat',
},
},
},
zendesk: {
username: {
doc: 'Zendesk Username for support interaction',
default: '',
env: 'ZENDESK_USERNAME',
format: String,
},
token: {
doc: 'Zendesk Token for support interaction',
default: '',
env: 'ZENDESK_TOKEN',
format: String,
},
subdomain: {
doc: 'Zendesk subdomain for support interaction',
default: 'mozilladev',
env: 'ZENDESK_SUBDOMAIN',
format: String,
},
productNameFieldId: {
doc: 'Zendesk support ticket custom field for the product name',
default: 360022027772,
env: 'ZENDESK_PRODUCT_NAME_FIELD_ID',
format: Number,
},
productFieldId: {
doc: 'Zendesk support ticket custom field for the product drop down',
default: 360047198211,
env: 'ZENDESK_PRODUCT_FIELD_ID',
format: Number,
},
locationCityFieldId: {
doc: 'Zendesk support ticket custom field for the city of the location',
default: 360026463311,
env: 'ZENDESK_LOCATION_CITY_FIELD_ID',
format: Number,
},
locationStateFieldId: {
doc: 'Zendesk support ticket custom field for the state/region of the location',
default: 360026463491,
env: 'ZENDESK_LOCATION_STATE_FIELD_ID',
format: Number,
},
locationCountryFieldId: {
doc: 'Zendesk support ticket custom field for the country of the location',
default: 360026463511,
env: 'ZENDESK_LOCATION_COUNTRY_FIELD_ID',
format: Number,
},
topicFieldId: {
doc: 'Zendesk support ticket custom field for topic',
default: 360028484432,
env: 'ZENDESK_TOPIC_FIELD_ID',
format: Number,
},
categoryFieldId: {
doc: 'Zendesk support ticket category field for category drop down',
default: 360047206172,
env: 'ZENDESK_CATEGORY_FIELD_ID',
format: Number,
},
appFieldId: {
doc: 'Zendesk support ticket custom field for product specific app or service',
default: 360030780972,
env: 'ZENDESK_APP_FIELD_ID',
format: Number,
},
productPlatformFieldId: {
doc: 'Zendesk support ticket custom field for the product platform',
default: 360047272851,
env: 'ZENDESK_PRODUCT_PLATFORM_FIELD_ID',
format: Number,
},
productVersionFieldId: {
doc: 'Zendesk support ticket custom field for product version',
default: 360047246812,
env: 'ZENDESK_PRODUCT_VERSION_FIELD_ID',
format: Number,
},
},
otp: {
step: {
doc: 'Default time step size (seconds)',
default: 10 * 60,
format: 'nat',
env: 'OTP_SIGNUP_STEP_SIZE',
},
window: {
doc: 'Tokens in the previous x-windows that should be considered valid',
default: 1,
format: 'nat',
env: 'OTP_SIGNUP_WINDOW',
},
digits: {
doc: 'Number of digits in token',
default: 6,
format: 'nat',
env: 'OTP_SIGNUP_DIGIT',
},
},
accountDestroy: {
requireVerifiedAccount: {
doc: 'Whether or not the account must be verified in order to destroy it.',
default: false,
format: Boolean,
env: 'ACCOUNT_DESTROY__REQUIRE_VERIFIED_ACCOUNT',
},
requireVerifiedSession: {
doc: 'Whether or not the account must have a verified session in order to destroy it.',
default: true,
format: Boolean,
env: 'ACCOUNT_DESTROY__REQUIRE_VERIFIED_SESSION',
},
},
passwordForgotOtp: {
digits: {
doc: 'Number of digits in token',
default: 8,
format: 'nat',
env: 'OTP_PASSWORD_FORGOT_DIGITS',
},
ttl: {
doc: 'Duration in seconds when the OTP is valid',
default: 10 * 60,
format: 'nat',
env: 'OTP_PASSWORD_FORGOT_TTL',
},
},
syncTokenserverUrl: {
default: 'http://localhost:8000/token',
doc: 'The url of the Firefox Sync tokenserver',
env: 'SYNC_TOKENSERVER_URL',
format: 'url',
},
support: {
secretBearerToken: {
default: 'YOU MUST CHANGE ME',
doc: 'Shared secret to access the support endpoint.',
env: 'SUPPORT_AUTH_SECRET_BEARER_TOKEN',
format: 'String',
},
ticketPayloadLimit: {
default: 131072,
doc: 'The payload limit in bytes, default is 2^17',
env: 'SUPPORT_TICKET_PAYLOAD_LIMIT',
format: Number,
},
},
tracing: tracingConfig,
accountEvents: {
enabled: {
default: true,
doc: 'Flag to enable account event logging. Currently only email based events',
env: 'ACCOUNT_EVENTS_ENABLED',
format: Boolean,
},
},
gleanMetrics: {
enabled: {
default: true,
doc: 'Enable Glean metrics logging',
env: 'AUTH_GLEAN_ENABLED',
format: Boolean,
},
applicationId: {
default: 'accounts_backend_dev',
doc: 'The Glean application id',
env: 'AUTH_GLEAN_APP_ID',
format: String,
},
channel: {
default: 'development',
doc: 'The application channel, e.g. development, stage, production, etc.',
env: 'AUTH_GLEAN_APP_CHANNEL',
format: String,
},
loggerAppName: {
default: 'fxa-auth-api',
doc: 'Used to form the mozlog logger name',
env: 'AUTH_GLEAN_LOGGER_APP_NAME',
format: String,
},
},
certificateSignDisableRolloutRate: {
default: 0,
doc: 'Rollout rate for disabling certificate signing, in the range 0 .. 1',
env: 'CERTIFICATE_SIGN_DISABLE_ROLLOUT_RATE',
format: Number,
},
cms: {
enabled: {
default: false,
doc: 'Whether to use CMS',
env: 'CMS_ENABLED',
format: Boolean,
},
legacyMapper: {
mapperCacheTTL: {
default: 60,
doc: 'Strapi client Firestore cache TTL in seconds',
env: 'CMS_LEGACY_MAPPER_CACHE_TTL',
format: Number,
},
},
strapiClient: {
graphqlApiUri: {
default: '',
doc: 'Base URL for GraphQL API',
env: 'STRAPI_CLIENT_GRAPHQL_API_URI',
format: String,
},
apiKey: {
default: '',
doc: 'GraphQL Content API key for Strapi to fetch RP-provided content',
env: 'STRAPI_CLIENT_API_KEY',
format: String,
},
memCacheTTL: {
default: 300,
doc: 'Strapi client memory cache TTL in seconds',
env: 'STRAPI_CLIENT_MEM_CACHE_TTL',
format: Number,
},
firestoreCacheCollectionName: {
default: 'fxa-auth-server-strapi-query-cache',
doc: 'Firestore collection name to store CMS query cache',
env: 'STRAPI_CLIENT_FIRESTORE_CACHE_COLLECTION_NAME',
format: String,
},
firestoreCacheTTL: {
default: 1800,
doc: 'Strapi client Firestore cache TTL in seconds',
env: 'STRAPI_CLIENT_FIRESTORE_CACHE_TTL',
format: Number,
},
firestoreOfflineCacheTTL: {
default: 604800,
doc: 'Strapi client Firestore offline cache TTL in seconds, used for when Strapi is down',
env: 'STRAPI_CLIENT_FIRESTORE_OFFLINE_CACHE_TTL',
format: Number,
},
},
},
cloudTasks: CloudTasksConvictConfigFactory(),
cloudScheduler: {
oidc: {
aud: {
default: '',
doc: 'The audience value of the id token payload.',
env: `AUTH_CLOUDSCHEDULER_OIDC_AUD`,
format: String,
},
serviceAccountEmail: {
default: '',
doc: 'The GCP service account email address.',
env: `AUTH_CLOUDSCHEDULER_OIDC_EMAIL`,
format: String,
},
},
deleteUnverifiedAccounts: {
sinceDays: {
default: 15,
doc: 'The time since which unverified accounts should be deleted.',
env: `AUTH_CLOUDSCHEDULER_DELETE_UNVERIFIED_ACCOUNTS_SINCE_DAYS`,
format: Number,
},
durationDays: {
default: 15,
doc:
'The duration to delete accounts from the since day. For example, ' +
'if sinceDay is 15 and duration is 15, unverified accounts created ' +
'between 15 and 30 days ago will be deleted.',
env: `AUTH_CLOUDSCHEDULER_DELETE_UNVERIFIED_ACCOUNTS_DURATION_DAYS`,
format: Number,
},
taskLimit: {
default: 200,
env: `AUTH_CLOUDSCHEDULER_DELETE_UNVERIFIED_ACCOUNTS_TASK_LIMIT`,
format: Number,
},
},
},
recoveryPhone: {
enabled: {
default: false,
doc: 'Enable recovery phone feature',
env: 'RECOVERY_PHONE__ENABLED',
format: Boolean,
},
allowedRegions: {
default: ['CA', 'US'],
doc: 'Allowed regions for recovery phone',
env: 'RECOVERY_PHONE__ALLOWED_REGIONS',
format: Array,
},
otp: {
kind: {
default: 'recovery-phone-code',
doc: 'An identifier for the type of otp codes being sent out',
env: 'RECOVERY_PHONE__OTP__KIND',
format: String,
},
digits: {
default: 6,
doc: 'The number of digits in an otp code',
env: 'RECOVERY_PHONE__OTP__DIGITS',
format: Number,
},
},
maxRegistrationsPerNumber: {
default: 5,
doc: 'Max number of uids that can be associated to the same phone number.',
env: 'RECOVERY_PHONE__MAX_UID_PER_NUMBER',
format: Number,
},
sms: {
from: {
default: ['15005550006'],
doc: 'The twilio number messages are sent from.',
env: 'RECOVERY_PHONE__SMS__FROM',
format: Array,
},
maxMessageSegmentLength: {
default: 1,
doc: 'Max allowed sms message segment length',
env: 'RECOVERY_PHONE__SMS__MAX_MESSAGE_SEGMENT_LENGTH',
format: Number,
},
validNumberPrefixes: {
default: ['+1'], // USA and Canada
doc: 'Allowed phone number prefixes. Controls the numbers that a message can be sent to.',
env: 'RECOVERY_PHONE__SMS__VALID_NUMBER_PREFIXES',
format: Array,
},
validCountryCodes: {
default: ['US', 'CA'], // USA and Canada
doc: 'Allowed phone number prefixes. Controls the countries that a message can be sent to.',
env: 'RECOVERY_PHONE__SMS__VALID_COUNTRY_CODES',
format: Array,
},
maxRetries: {
default: 3,
doc: 'Number of times an sms message can be retried in the event a sms sending rate limit is encountered. Note that we use an exponential back off here. e.g. 3 would mean up to a 1s, 2s and then 4s retry.',
env: 'RECOVERY_PHONE__SMS__MAX_RETRIES',
format: Number,
},
smsPumpingRiskThreshold: {
default: 75,
doc: 'Max sms pumping risk score allowed. Value is from 0 to 100. e.g. 74-90 is moderate risk. See twilio docs for more info. https://www.twilio.com/docs/lookup/v2-api/sms-pumping-risk',
env: 'RECOVERY_PHONE__SMS__SMS_PUMPING_RISK_THRESHOLD',
format: Number,
},
extraLookupFields: {
default: [],
doc: 'Extra data to fetch about phone numbers being registered for sms backup.',
env: 'RECOVERY_PHONE__SMS__EXTRA_LOOKUP_FIELDS',
format: Array,
},
},
},
twilio: {
credentialMode: {
default: '',
doc: 'Which credential set to use. Options are "", "test", "default", or "apiKeys".',
env: 'RECOVERY_PHONE__TWILIO__CREDENTIAL_MODE',
format: String,
},
testAccountSid: {
default: 'AC_REPLACEMEWITHKEY',
doc: 'Twilio Testing Account ID. Note must be used for tests leveraging Twilio magic phone numbers. Required when credentialMode is test.',
env: 'RECOVERY_PHONE__TWILIO__TEST_ACCOUNT_SID',
format: String,
},
testAuthToken: {
default: '',
doc: 'Twilio Testing Account Auth Token. Note must be used for tests leverage Twilio magic phone numbers. Required when credentialMode is test.',
env: 'RECOVERY_PHONE__TWILIO__TEST_AUTH_TOKEN',
format: String,
},
accountSid: {
default: 'AC_REPLACEMEWITHKEY',
doc: 'Twilio Account ID. Required when credentialMode is default or apiKeys.',
env: 'RECOVERY_PHONE__TWILIO__ACCOUNT_SID',
format: String,
},
authToken: {
default: '',
doc: 'Twilio Auth Token to access api. Note, using apiKey/apiSecret is preferred. Required when credentialMode is default.',
env: 'RECOVERY_PHONE__TWILIO__AUTH_TOKEN',
},
apiKey: {
default: '',
doc: 'An api key used to access the twilio rest api. Required when credentialMode is apiKeys.',
env: 'RECOVERY_PHONE__TWILIO__API_KEY',
},
apiSecret: {
default: '',
doc: 'A secret used in conjunction with the apiKey to access the twilio rest api. Required when credentialMode is apiKeys.',
env: 'RECOVERY_PHONE__TWILIO__API_SECRET',
},
webhookUrl: {
default: '',
doc: 'Webhook url registered with twilio for message status updates',
env: 'RECOVERY_PHONE__TWILIO__WEBHOOK_URL',
},
validateWebhookCalls: {
default: true,
doc: 'Controls if twilio signature is validated during webhook calls from twilio',
env: 'RECOVERY_PHONE__TWILIO__VALIDATE_WEBHOOK_CALLS',
},
fxaPublicKey: {
default: '',
doc: 'A key used to to for validating signature in webhook calls.',
env: 'RECOVERY_PHONE__TWILIO__FXA_PUBLIC_KEY',
},
fxaPrivateKey: {
default: '',
doc: 'A private key used for signing messages provided to twilio webhook calls.',
env: 'RECOVERY_PHONE__TWILIO__FXA_PRIVATE_KEY',
},
},
recordedFuture: {
identityApiKey: {
default: '',
doc: 'API key to access the Identity API of Recorded Future',
format: String,
env: 'RECORDED_FUTURE__IDENTITY_API_KEY',
},
},
});
// 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.
let envConfig = path.join(__dirname, `${convictConf.get('env')}.json`);
envConfig = `${envConfig},${process.env.CONFIG_FILES || ''}`;
const files = envConfig.split(',').filter(fs.existsSync);
convictConf.loadFile(files);
convictConf.validate();
// set the public url as the issuer domain for assertions
convictConf.set('domain', url.parse(convictConf.get('publicUrl')).host);
// derive fxa-auth-mailer configuration from our content-server url
const baseUri = convictConf.get('contentServer.url');
convictConf.set('smtp.accountSettingsUrl', `${baseUri}/settings`);
convictConf.set(
'smtp.accountRecoveryCodesUrl',
`${baseUri}/settings/two_step_authentication/replace_codes`
);
convictConf.set('smtp.verificationUrl', `${baseUri}/verify_email`);
convictConf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`);
convictConf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`);
convictConf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`);
convictConf.set(
'smtp.initiatePasswordChangeUrl',
`${baseUri}/settings/change_password`
);
convictConf.set('smtp.verifyLoginUrl', `${baseUri}/complete_signin`);
convictConf.set(
'smtp.accountFinishSetupUrl',
`${baseUri}/post_verify/finish_account_setup/set_password`
);
convictConf.set('smtp.reportSignInUrl', `${baseUri}/report_signin`);
convictConf.set(
'smtp.revokeAccountRecoveryUrl',
`${baseUri}/settings#recovery-key`
);
convictConf.set(
'smtp.createAccountRecoveryUrl',
`${baseUri}/settings/account_recovery`
);
convictConf.set(
'smtp.verifyPrimaryEmailUrl',
`${baseUri}/verify_primary_email`
);
convictConf.set(
'smtp.verifySecondaryEmailUrl',
`${baseUri}/verify_secondary_email`
);
convictConf.set('smtp.subscriptionSettingsUrl', `${baseUri}/subscriptions`);
convictConf.set('smtp.subscriptionSupportUrl', `${baseUri}/support`);
convictConf.set('smtp.syncUrl', `${baseUri}/connect_another_device`);
convictConf.set('isProduction', convictConf.get('env') === 'prod');
//sns endpoint is not to be set in production
if (convictConf.has('snsTopicEndpoint') && convictConf.get('env') !== 'dev') {
throw new Error('snsTopicEndpoint is only allowed in dev env');
}
if (convictConf.get('env') === 'dev') {
if (!process.env.AWS_ACCESS_KEY_ID) {
process.env.AWS_ACCESS_KEY_ID = 'DEV_KEY_ID';
}
if (!process.env.AWS_SECRET_ACCESS_KEY) {
process.env.AWS_SECRET_ACCESS_KEY = 'DEV_ACCESS_KEY';
}
}
if (convictConf.get('oauthServer.openid.keyFile')) {
const keyFile = path.resolve(
__dirname,
'..',
convictConf.get('oauthServer.openid.keyFile')
);
convictConf.set('oauthServer.openid.keyFile', keyFile);
// If the file doesnt exist, or contains an empty object, then there's no active key.
convictConf.set('oauthServer.openid.key', null);
if (fs.existsSync(keyFile)) {
const key = JSON.parse(fs.readFileSync(keyFile, 'utf-8'));
if (key && Object.keys(key).length > 0) {
convictConf.set('oauthServer.openid.key', key);
}
}
} else if (
Object.keys(convictConf.get('oauthServer.openid.key')).length === 0
) {
convictConf.set('oauthServer.openid.key', null);
}
if (convictConf.get('oauthServer.openid.newKeyFile')) {
const newKeyFile = path.resolve(
__dirname,
'..',
convictConf.get('oauthServer.openid.newKeyFile')
);
convictConf.set('oauthServer.openid.newKeyFile', newKeyFile);
// If the file doesnt exist, or contains an empty object, then there's no new key.
convictConf.set('oauthServer.openid.newKey', null);
if (fs.existsSync(newKeyFile)) {
const newKey = JSON.parse(fs.readFileSync(newKeyFile, 'utf-8'));
if (newKey && Object.keys(newKey).length > 0) {
convictConf.set('oauthServer.openid.newKey', newKey);
}
}
} else if (
Object.keys(convictConf.get('oauthServer.openid.newKey')).length === 0
) {
convictConf.set('oauthServer.openid.newKey', null);
}
if (convictConf.get('oauthServer.openid.oldKeyFile')) {
const oldKeyFile = path.resolve(
__dirname,
'..',
convictConf.get('oauthServer.openid.oldKeyFile')
);
convictConf.set('oauthServer.openid.oldKeyFile', oldKeyFile);
// If the file doesnt exist, or contains an empty object, then there's no old key.
convictConf.set('oauthServer.openid.oldKey', null);
if (fs.existsSync(oldKeyFile)) {
const oldKey = JSON.parse(fs.readFileSync(oldKeyFile, 'utf-8'));
if (oldKey && Object.keys(oldKey).length > 0) {
convictConf.set('oauthServer.openid.oldKey', oldKey);
}
}
} else if (
Object.keys(convictConf.get('oauthServer.openid.oldKey')).length === 0
) {
convictConf.set('oauthServer.openid.oldKey', null);
}
// Ensure secrets are not set to their default values in production.
if (convictConf.get('isProduction')) {
const SECRET_SETTINGS = [
'metrics.flow_id_key',
'oauth.jwtSecretKeys',
'oauth.secretKey',
'profileServer.secretBearerToken',
];
for (const key of SECRET_SETTINGS) {
if (convictConf.get(key) === convictConf.default(key)) {
throw new Error(`Config '${key}' must be set in production`);
}
}
}
export type conf = typeof convictConf;
export type ConfigType = ReturnType<conf['getProperties']>;
export { convictConf as config };
export default convictConf;