Newer
Older
/* 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/. */
Lloyd Hilaiel
committed
/*
* An abstraction which contains various pre-set deployment
* environments and adjusts runtime configuration appropriate for
* the current environmnet (specified via the NODE_ENV env var)..
*
* usage is
* exports.configure(app);
*/
Lloyd Hilaiel
committed
const
postprocess = require('postprocess'),
path = require('path'),
urlparse = require('urlparse'),
secrets = require('./secrets'),
Lloyd Hilaiel
committed
temp = require('temp'),
semver = require('semver'),
fs = require('fs'),
convict = require('convict'),
cjson = require('cjson');
Lloyd Hilaiel
committed
Austin King
committed
// Side effect - Adds default_bid and dev_bid to express.logger formats
require('./custom_logger');
Lloyd Hilaiel
committed
// verify the proper version of node.js is in use
try {
Lloyd Hilaiel
committed
var required = 'unknown';
// extract required node version from package.json
required = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"))).engines.node;
if (!semver.satisfies(process.version, required)) throw false;
Lloyd Hilaiel
committed
} catch (e) {
Lloyd Hilaiel
committed
process.stderr.write("update node! verision " + process.version +
" is not " + required +
(e ? " (" + e + ")" : "") + "\n");
Lloyd Hilaiel
committed
process.exit(1);
}
var conf = module.exports = convict({
env: {
// XXX: should we deprecate this configuration paramater?
doc: "What environment are we running in? Note: all hosted environments are 'production'. ",
format: 'string ["production", "local", "test_mysql", "test_json"] = "production"',
env: 'NODE_ENV'
},
bind_to: {
host: {
doc: "The ip address the server should bind",
format: 'string = "127.0.0.1"',
env: 'IP_ADDRESS'
},
port: {
doc: "The port the server should bind",
format: 'integer{1,65535}?',
env: 'PORT'
}
},
public_url: {
doc: "The publically visible URL of the deployment",
Lloyd Hilaiel
committed
format: 'string = "https://login.persona.org"',
env: 'PUBLIC_URL'
},
public_static_url: {
doc: "The publically visible URL from which static resources are served",
format: 'string',
env: 'PUBLIC_STATIC_URL'
},
public_verifier_url: {
doc: "The publically visible URL where incoming verification requests are handled",
format: 'string',
env: 'PUBLIC_VERIFIER_URL'
},
scheme: {
// XXX should we deprecate scheme as it's redundant and derived from 'public_url' ?
doc: "The scheme of the public URL. Calculated from the latter.",
format: "string",
},
Austin King
committed
cachify_prefix: {
doc: "The prefix for cachify hashes in URLs",
format: 'string = "v"'
},
use_minified_resources: {
doc: "Should the server serve minified resources?",
format: 'boolean = true',
env: 'MINIFIED'
},
var_path: {
doc: "The path where deployment specific resources will be sought (keys, etc), and logs will be kept.",
format: 'string?',
env: 'VAR_PATH'
},
driver: 'string ["json", "mysql"] = "json"',
user: {
format: 'string?',
env: 'MYSQL_USER'
},
password: {
format: 'string?',
env: 'MYSQL_PASSWORD'
},
Lloyd Hilaiel
committed
host: 'string?',
create_schema: 'boolean = true',
may_write: 'boolean = true',
name: {
format: 'string?',
env: 'DATABASE_NAME'
max_query_time_ms: {
format: 'integer = 5000',
doc: "The maximum amount of time we'll allow a query to run before considering the database to be sick",
env: 'MAX_QUERY_TIME_MS'
},
max_reconnect_attempts: {
format: 'integer = 1',
doc: "The maximum number of times we'll attempt to reconnect to the database before failing all outstanding queries"
}
},
smtp: {
host: 'string?',
user: 'string?',
pass: 'string?',
port: 'integer = 25'
Lloyd Hilaiel
committed
},
enabled: {
doc: "enable UDP based statsd reporting",
format: 'boolean = true',
env: 'ENABLE_STATSD'
},
host: "string?",
port: "integer{1,65535}?"
Austin King
committed
kpi_backend_sample_rate: {
doc: "Float between 0 and 1 inclusive, for the % of user flows that should send back KPI JSON blobs. Example: 0.5 would be 50% traffic.",
format: 'number = 0.0',
env: 'KPI_BACKEND_SAMPLE_RATE'
},
Austin King
committed
kpi_backend_db_url: 'string = "http://localhost/wsapi/interaction_data"',
bcrypt_work_factor: {
doc: "How expensive should we make password checks (to mitigate brute force attacks) ? Each increment is 2x the cost.",
format: 'integer{6,20} = 12',
env: 'BCRYPT_WORK_FACTOR',
Lloyd Hilaiel
committed
},
authentication_duration_ms: {
doc: "How long may a user stay signed?",
Lloyd Hilaiel
committed
format: 'integer = 2419200000'
Lloyd Hilaiel
committed
ephemeral_session_duration_ms: {
doc: "How long a user on a shared computer shall be authenticated",
format: 'integer = 3600000' // 1 hour
Lloyd Hilaiel
committed
},
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
certificate_validity_ms: {
doc: "For how long shall certificates issued by BrowserID be valid?",
format: 'integer = 86400000'
},
max_compute_processes: {
doc: "How many computation processes will be spun. Default is good, based on the number of CPU cores on the machine.",
format: 'union { number{1, 256}; null; } = null',
env: 'MAX_COMPUTE_PROCESSES'
},
max_compute_duration: {
doc: "What is the longest (in seconds) we'll let the user wait before returning a 503?",
format: 'integer = 10'
},
disable_primary_support: {
doc: "Disables primary support when true",
format: 'boolean = false'
},
enable_code_version: {
doc: "When enabled, will cause a 'code version' to be returned to frontend code in `/wsapi/session_context` calls",
format: 'boolean = false'
},
min_time_between_emails_ms: {
doc: "What is the most frequently we'll allow emails to be sent to the same user?",
format: 'integer = 60000'
},
http_proxy: {
port: 'integer{1,65535}?',
host: 'string?'
},
Austin King
committed
default_lang: 'string = "en-US"',
Austin King
committed
debug_lang: 'string = "it-CH"',
Austin King
committed
supported_languages: {
doc: "List of languages this deployment should detect and display localized strings.",
format: 'array { string }* = [ "en-US" ]',
env: 'SUPPORTED_LANGUAGES'
},
disable_locale_check: {
doc: "Skip checking for gettext .mo files for supported locales",
format: 'boolean = false'
},
locale_directory: 'string = "locale"',
Austin King
committed
express_log_format: 'string [ "default_bid", "dev_bid", "default", "dev", "short", "tiny" ] = "default"',
keysigner_url: {
format: 'string?',
env: 'KEYSIGNER_URL'
},
verifier_url: {
format: 'string?',
env: 'VERIFIER_URL'
},
dbwriter_url: {
format: 'string?',
env: 'DBWRITER_URL'
},
browserid_url: {
format: 'string?',
env: 'BROWSERID_URL'
},
static_url: {
format: 'string?',
env: 'STATIC_URL'
},
process_type: 'string',
Lloyd Hilaiel
committed
email_to_console: 'boolean = false',
declaration_of_support_timeout_ms: {
doc: "The amount of time we wait for a server to respond with a declaration of support, before concluding that they are not a primary. Only relevant when our local proxy is in use, not in production or staging",
format: 'integer = 15000'
Shane Tomlinson
committed
},
enable_development_menu: {
doc: "Whether or not the development menu can be accessed",
format: 'boolean = false'
},
proxy_idps: {
doc: "A mapping of domain names to urls, which maps popular email services to shimmed IDP deployments.",
format: 'object { } *?'
Lloyd Hilaiel
committed
}
Lloyd Hilaiel
committed
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// 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"));
// 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']) {
var files = process.env['CONFIG_FILES'].split(',');
files.forEach(function(file) {
var c = cjson.load(file);
// now support process-specific "overlays". That is,
// .browserid.port will override .port for the "browserid" process
// first try to extract *our* overlay
var overlay = c[conf.get('process_type')];
// now remove all overlays from the top level config
fs.readdirSync(path.join(__dirname, '..', 'bin')).forEach(function(type) {
delete c[type];
});
// load the base config and the overlay in order
conf.load(c);
if (overlay) conf.load(overlay);
});
Lloyd Hilaiel
committed
}
// special handling of HTTP_PROXY env var
if (process.env['HTTP_PROXY']) {
var p = process.env['HTTP_PROXY'].split(':');
conf.set('http_proxy.host', p[0]);
conf.set('http_proxy.port', p[1]);
Lloyd Hilaiel
committed
// set the 'scheme' of the server based on the public_url (which is needed for
// things like
conf.set('scheme', urlparse(conf.get('public_url')).scheme);
// if var path has not been set, let's default to var/
if (!conf.has('var_path')) {
conf.set('var_path', path.join(__dirname, "..", "var"));
}
// test environments may dictate which database to use.
if (conf.get('env') === 'test_json') {
conf.set('database.driver', 'json');
} else if (conf.get('env') === 'test_mysql') {
conf.set('database.driver', 'mysql');
Lloyd Hilaiel
committed
}
Lloyd Hilaiel
committed
// if static and verifier urls are not explicitly set, default them to the
// same as the public url (used in ephemeral and local deployments)
if (!conf.has('public_static_url')) {
conf.set('public_static_url', conf.get('public_url'));
}
if (!conf.has('public_verifier_url')) {
conf.set('public_verifier_url', conf.get('public_url'));
}
// augment the cachify prefix with the public_static_url
var prefix = urlparse(conf.get('public_static_url') + "/" + conf.get('cachify_prefix'));
conf.set('cachify_prefix', prefix.normalize().toString());
// validate the configuration based on the above specification
conf.validate();
Lloyd Hilaiel
committed
Lloyd Hilaiel
committed
/*
* Install middleware that will perform textual replacement on served output
* to re-write urls as needed for this particular environment.
*
* Note, for a 'local' environment, no re-write is needed because this is
* handled at a higher level. For other environments, only perform re-writing
Lloyd Hilaiel
committed
* if the host, port, or scheme are different than https://login.persona.org:443
* (all source files always should have the production hostname written into them)
Lloyd Hilaiel
committed
*/
module.exports.performSubstitution = function(app) {
Lloyd Hilaiel
committed
if (conf.get('public_url') != 'https://login.persona.org' ||
conf.get('public_static_url') != 'https://static.login.persona.org' ||
conf.get('public_verifier_url') != 'https://verifier.login.persona.org')
{
Lloyd Hilaiel
committed
app.use(postprocess(function(req, buffer) {
Lloyd Hilaiel
committed
return buffer.toString()
.replace(new RegExp('https://login.persona.org', 'g'), conf.get('public_url'))
.replace(new RegExp('https://static.login.persona.org', 'g'), conf.get('public_static_url'))
.replace(new RegExp('https://verifier.login.persona.org', 'g'), conf.get('public_verifier_url'));
Lloyd Hilaiel
committed
}));
}
};
Lloyd Hilaiel
committed
// log the process_type
process.nextTick(function() {
Lloyd Hilaiel
committed
var logging = require("./logging.js").logger;
logging.info("process type is " + conf.get("process_type"));