diff --git a/bin/browserid b/bin/browserid index 70fdce33c78a2625fa69887fd1ad0828cd27f225..936647854c8c1908e335b8bbf923a6552a42bfdf 100755 --- a/bin/browserid +++ b/bin/browserid @@ -101,10 +101,11 @@ app.use(function(req, resp, next) { // #5 - redirection! redirect requests to the "verifier" or to the "dbwriter" // processes if (config.get('verifier_url')) { + var verifier_url = urlparse(config.get('verifier_url')).validate().normalize(); app.use(function(req, res, next) { if (/^\/verify$/.test(req.url)) { forward( - config.get('verifier_url'), req, res, + verifier_url, req, res, function(err) { if (err) { logger.error("error forwarding request:", err); @@ -136,7 +137,7 @@ config.performSubstitution(app); // #8 - handle /wsapi requests wsapi.setup({ - forward_writes: config.get('dbwriter_url') + forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly() }, app); // #9 - handle views for dynamicish content diff --git a/bin/dbwriter b/bin/dbwriter index 4d92017b0130935d251243fab026fb72e3d30022..456a78837d9406c83193fdf54fa2a4b9c8b42e6b 100755 --- a/bin/dbwriter +++ b/bin/dbwriter @@ -10,13 +10,11 @@ path = require('path'), url = require('url'), http = require('http'); urlparse = require('urlparse'), -express = require('express'); - -const +express = require('express'), wsapi = require('../lib/wsapi.js'), -httputils = require('../lib/httputils.js'), +httputils = require('../lib/httputils.js'); secrets = require('../lib/secrets.js'), -db = require('../lib/db.js'), +db = require('../lib/db.js'); config = require('../lib/configuration.js'), heartbeat = require('../lib/heartbeat.js'), metrics = require('../lib/metrics.js'), @@ -108,7 +106,7 @@ db.open(config.get('database'), function (error) { if (error) { logger.error("can't open database: " + error); // let async logging flush, then exit 1 - return setTimeout(function() { process.exit(1); }, 0); + return process.nextTick(function() { process.exit(1); }); } // shut down express gracefully on SIGINT diff --git a/bin/keysigner b/bin/keysigner index b0478d04089c93ed4441e2955a37b039ae827f8e..b2002a722fdb8049d6137d7f3f3854e395a21a1b 100755 --- a/bin/keysigner +++ b/bin/keysigner @@ -19,7 +19,11 @@ metrics = require('../lib/metrics.js'), logger = require('../lib/logging.js').logger, heartbeat = require('../lib/heartbeat'), shutdown = require('../lib/shutdown'), -computecluster = require('compute-cluster'); +computecluster = require('compute-cluster'), +urlparse = require('urlparse'); + +const HOSTNAME = urlparse(config.get('public_url')).host; +logger.info("Certs will be issued from: " + HOSTNAME); // create an express server var app = express.createServer(); @@ -71,12 +75,15 @@ try { process.exit(1); } + // and our single function app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) { var startTime = new Date(); cc.enqueue({ pubkey: req.body.pubkey, - email: req.body.email + email: req.body.email, + validityPeriod: config.get('certificate_validity_ms'), + hostname: HOSTNAME }, function (err, r) { var reqTime = new Date - startTime; statsd.timing('certification_time', reqTime); diff --git a/config/local.json b/config/local.json new file mode 100644 index 0000000000000000000000000000000000000000..82aefccb452a01096ceae22aae71b26b7626deff --- /dev/null +++ b/config/local.json @@ -0,0 +1,18 @@ +{ + "verifier": { "bind_to": { "port": 10000 } }, + "keysigner": { "bind_to": { "port": 10003 } }, + "dbwriter": { "bind_to": { "port": 10004 } }, + "proxy": { "bind_to": { "port": 10006 } }, + "browserid": { + "bind_to": { + "port": 10002 + } + }, + "use_minified_resources": false, + "database": { + "driver": "json" + }, + "express_log_format": "dev", + "email_to_console": true +} + diff --git a/lib/configuration.js b/lib/configuration.js index e5c910b0e1687e5463b1edfc2e08720471f65c73..d1fb9248d801ba3ec7091a13a496888daffd575e 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -18,7 +18,9 @@ urlparse = require('urlparse'), secrets = require('./secrets'), temp = require('temp'), semver = require('semver'), -fs = require('fs'); +fs = require('fs'), +convict = require('convict'), +cjson = require('cjson'); // verify the proper version of node.js is in use try { @@ -33,205 +35,182 @@ try { process.exit(1); } -var g_config = { -}; - -// get the value for a given key, this mechanism allows the rest of the -// application to reach in and set -exports.get = function(key) { - if (key === 'env') return process.env['NODE_ENV']; - return g_config[key]; -} - -// allow the application to set configuration variables -// iff we're in a test_ environment -exports.set = function(key, val) { - if (!/^test_/.test(exports.get('env'))) - throw "you may only set configuration variables in a test_ environment " + - "(not in '" + exports.get('env') + "')"; - g_config[key] = val; -} - -// *** the various deployment configurations *** -const g_configs = { }; - -// production is the configuration that runs on our -// public service (browserid.org) -g_configs.production = { - URL: 'https://browserid.org', - use_minified_resources: true, - var_path: '/home/browserid/var/', +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", + format: 'string = "https://browserid.org"', + env: '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", + }, + 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' + }, database: { - driver: "mysql", - user: 'browserid', - create_schema: true, - may_write: false + driver: 'string ["json", "mysql"] = "json"', + user: 'string?', + create_schema: 'boolean = true', + may_write: 'boolean = true', + name: { + format: 'string?', + env: 'DATABASE_NAME' + } + }, + smtp: { + host: 'string?', + user: 'string?', + pass: 'string?' }, statsd: { - enabled: false + enabled: { + doc: "enable UDP based statsd reporting", + format: 'boolean = true', + env: 'ENABLE_STATSD' + }, + host: "string?", + port: "integer{1,65535}?" }, - bcrypt_work_factor: 12, - authentication_duration_ms: (2 * 7 * 24 * 60 * 60 * 1000), - certificate_validity_ms: (24 * 60 * 60 * 1000), - min_time_between_emails_ms: (60 * 1000), - // may be specified to manipulate the maximum number of compute processes - max_compute_processes: undefined, - // return a 503 if a compute process would take over 10s to complete - max_compute_duration: 10, - disable_primary_support: false, - // code_version is a property in the session_context response... When - // enabled it causes frontend code to employ cache busting logic. issue #226 - enable_code_version: false, - /* - , http_proxy: { - port: 3128, - host: "127.0.0.1" - } - */ - default_lang: ['en-US'], - supported_languages: ['en-US'], - // Contains po files - locale_directory: 'locale' -}; - -// local development configuration -g_configs.local = { - URL: 'http://127.0.0.1:10002', - email_to_console: true, // don't send email, just dump verification URLs to console. - use_minified_resources: false, - var_path: path.join(__dirname, "..", "var"), - database: { - driver: "json", - may_write: false + 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', }, - bcrypt_work_factor: g_configs.production.bcrypt_work_factor, - authentication_duration_ms: g_configs.production.authentication_duration_ms, - certificate_validity_ms: g_configs.production.certificate_validity_ms, - min_time_between_emails_ms: g_configs.production.min_time_between_emails_ms, - max_compute_processes: undefined, - max_compute_duration: 10, - disable_primary_support: false, - // code_version is a property in the session_context response... When - // enabled it causes frontend code to employ cache busting logic. issue #226 - enable_code_version: false, -/* - , http_proxy: { - port: 3128, - host: "127.0.0.1" - } -*/ - default_lang: g_configs.production.default_lang, - // it-CH and db-LB are debug - supported_languages: [ - 'af', 'ca', 'cs', 'db-LB', 'de', 'en-US', 'eo', 'es', 'es-MX', 'eu', 'fr', - 'fy', 'gd', 'gl', 'hr', 'it', 'ja', 'lij', 'lt', 'ml', 'nl', 'pl', 'rm', - 'ro', 'ru', 'sk', 'sl', 'son', 'sq', 'sr', 'tr', 'zh-CN', 'zh-TW', - 'it-CH', 'db-LB' - ], - locale_directory: g_configs.production.locale_directory -}; - -// test environments are variations on local -g_configs.test_json = JSON.parse(JSON.stringify(g_configs.local)); -g_configs.test_json.database = { - driver: "json" -}; - -g_configs.test_mysql = JSON.parse(JSON.stringify(g_configs.local)); -g_configs.test_mysql.database = { - driver: "mysql", - user: "test", - create_schema: true -}; - -if (undefined !== process.env['NODE_EXTRA_CONFIG']) { - eval(fs.readFileSync(process.env['NODE_EXTRA_CONFIG']) + ''); -} - -// default deployment is local -if (undefined === process.env['NODE_ENV']) { - process.env['NODE_ENV'] = 'local'; -} - -g_config = g_configs[process.env['NODE_ENV']]; - -if (g_config === undefined) throw "unknown environment: " + exports.get('env'); - -// what url are we running under? -{ - var ourURL = process.env['BROWSERID_URL'] || g_config['URL']; - var purl = urlparse(ourURL).validate().normalize().originOnly(); - - g_config.URL = purl.toString(); - g_config.hostname = purl.host; - g_config.scheme = purl.scheme; - g_config.port = purl.port || (purl.scheme == 'https' ? 443 : 80); -} - -if (process.env['VERIFIER_URL']) { - var url = urlparse(process.env['VERIFIER_URL']).validate().normalize(); - if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; - g_config.verifier_url = url; -} - -if (process.env['KEYSIGNER_URL']) { - var url = urlparse(process.env['KEYSIGNER_URL']).validate().normalize(); - if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; - g_config.keysigner_url = url; -} - -if (process.env['DBWRITER_URL']) { - var url = urlparse(process.env['DBWRITER_URL']).validate().normalize(); - if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; - g_config.dbwriter_url = url; -} - -// extract smtp params from the environment -if (!g_config.smtp) { - g_config.smtp = { - host: process.env['SMTP_HOST'], - user: process.env['SMTP_USER'], - pass: process.env['SMTP_PASS'] - }; -} + authentication_duration_ms: { + doc: "How long may a user stay signed?", + format: 'integer = 1209600000' + }, + 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?' + }, + default_lang: 'array { string }* = [ "en-US" ]', + supported_languages: 'array { string }* = [ "en-US" ]', + locale_directory: 'string = "locale"', + express_log_format: 'string [ "default", "dev" ] = "default"', + keysigner_url: { + format: 'string?', + env: 'KEYSIGNER_URL' + }, + verifier_url: { + format: 'string?', + env: 'VERIFIER_URL' + }, + dbwriter_url: { + format: 'string?', + env: 'DBWRITER_URL' + }, + process_type: 'string', + email_to_console: 'boolean = false' +}); -// now handle ephemeral database configuration. Used in testing. -if (g_config.database.driver === 'mysql') { - if (process.env['MYSQL_DATABASE_NAME']) { - g_config.database.database = process.env['MYSQL_DATABASE_NAME']; - } -} else if (g_config.database.driver === 'json') { - if (process.env['JSON_DATABASE_PATH']) { - g_config.database.path = process.env['JSON_DATABASE_PATH']; - } +// 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); + }); } -// allow work factor to be twaddled from the environment -if (process.env['BCRYPT_WORK_FACTOR']) { - g_config.bcrypt_work_factor = parseInt(process.env['BCRYPT_WORK_FACTOR']); +// 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]); } -// allow the number of cores used to be specified from the environment, -// default will something reasonable. -if (process.env['MAX_COMPUTE_PROCESSES']) { - g_config.max_compute_processes = parseInt(process.env['MAX_COMPUTE_PROCESSES']); -} +// 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); -// allow var_path to be specified in the environment -if (process.env['VAR_PATH']) { - g_config.var_path = process.env['VAR_PATH']; +// 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")); } -// allow statsd to be enabled from the environment -if (process.env['ENABLE_STATSD']) { - g_config.statsd = { enabled: true }; +// 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'); } -// what host/port shall we bind to? -g_config.bind_to = { - host: process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1", - port: process.env['PORT'] || 0 -}; +// validate the configuration based on the above specification +conf.validate(); /* * Install middleware that will perform textual replacement on served output @@ -242,27 +221,15 @@ g_config.bind_to = { * if the host, port, or scheme are different than https://browserid.org:443 * (all source files always should have the production hostname written into them) */ -exports.performSubstitution = function(app) { - if (g_config['URL'] != 'https://browserid.org') { +module.exports.performSubstitution = function(app) { + if (conf.get('public_url') != 'https://browserid.org') { app.use(postprocess.middleware(function(req, buffer) { - return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), g_config['URL']); + return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), conf.get('public_url')); })); } }; -g_config['express_log_format'] = (exports.get('env') === 'production' ? 'default' : 'dev'); - -// 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'. -g_config['process_type'] = path.basename(process.argv[1], ".js"); - -// only allow the dbwriter process to write to the database (or the unit tests) -g_config.database.may_write = (g_config.process_type === 'dbwriter' || - g_config.process_type === 'vows' || - g_config.process_type === 'db-test'); - // log the process_type -setTimeout(function() { - require("./logging.js").logger.info("process type is " + g_config["process_type"]); -}, 0); +process.nextTick(function() { + require("./logging.js").logger.info("process type is " + conf.get("process_type")); +}); diff --git a/lib/db/mysql.js b/lib/db/mysql.js index 93a7ce18c1b076cd06beb34e0ad03e4d5e95cebb..8b8839635b94a59297da75df9651d1457412d4f3 100644 --- a/lib/db/mysql.js +++ b/lib/db/mysql.js @@ -89,7 +89,7 @@ exports.open = function(cfg, cb) { }); // let's figure out the database name - var database = cfg.database; + var database = cfg.name; if (!database) database = "browserid"; // create the client diff --git a/lib/email.js b/lib/email.js index 541f1315647a2d20ed26c62c51ef3ee9cce54e43..8634eede0b965ffd995b10afc022589c12ab0f36 100644 --- a/lib/email.js +++ b/lib/email.js @@ -11,7 +11,7 @@ config = require('./configuration.js'), logger = require('./logging.js').logger; /* if smtp parameters are configured, use them */ -var smtp_params = config.get('smtp'); +try { var smtp_params = config.get('smtp'); } catch(e) {}; if (smtp_params && smtp_params.host) { emailer.SMTP = { host: smtp_params.host }; logger.info("delivering email via SMTP host: " + emailer.SMTP.host); @@ -46,7 +46,7 @@ exports.setInterceptor = function(callback) { //TODO send in localeContext function doSend(landing_page, email, site, secret, langContext) { - var url = config.get('URL') + "/" + landing_page + "?token=" + encodeURIComponent(secret), + var url = config.get('public_url') + "/" + landing_page + "?token=" + encodeURIComponent(secret), _ = langContext.gettext, format = langContext.format; diff --git a/lib/keysigner/ca.js b/lib/keysigner/ca.js index 75937a871083fcd9d8739f610b3408ab4f308c7d..bc7be67f663cd32d6f7ce11b245021477a0f73cf 100644 --- a/lib/keysigner/ca.js +++ b/lib/keysigner/ca.js @@ -9,13 +9,9 @@ var jwcert = require('jwcrypto/jwcert'), jws = require('jwcrypto/jws'), path = require("path"), fs = require("fs"), - config = require('../configuration.js'), secrets = require('../secrets.js'), - logger = require('../logging.js').logger; - -const HOSTNAME = config.get('hostname'); -logger.info("Certs will be issued from: " + HOSTNAME); - + logger = require('../logging.js').logger, + urlparse = require('urlparse'); try { const secret_key = secrets.loadSecretKey(); @@ -35,19 +31,19 @@ function parseCert(serializedCert) { return cert; } -function certify(email, publicKey, expiration) { +function certify(hostname, email, publicKey, expiration) { if (expiration == null) throw "expiration cannot be null"; - return new jwcert.JWCert(HOSTNAME, expiration, new Date(), publicKey, {email: email}).sign(secret_key); + return new jwcert.JWCert(hostname, expiration, new Date(), publicKey, {email: email}).sign(secret_key); } -function verifyChain(certChain, cb) { +function verifyChain(hostname, certChain, cb) { // raw certs return jwcert.JWCert.verifyChain( certChain, new Date(), function(issuer, next) { // for now we only do browserid.org issued keys - if (issuer != HOSTNAME) + if (issuer != hostname) return next(null); next(exports.PUBLIC_KEY); diff --git a/lib/keysigner/keysigner-compute.js b/lib/keysigner/keysigner-compute.js index d6fdaddc713f80e31432b23dbf9be495b2d3fdbf..5a64adb0c92e6e8172a70b4ff51946097ad4a7f4 100644 --- a/lib/keysigner/keysigner-compute.js +++ b/lib/keysigner/keysigner-compute.js @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const -config = require('../configuration'), ca = require('./ca.js'); process.on('message', function(m) { @@ -14,8 +13,8 @@ process.on('message', function(m) { // same account, we certify the key // we certify it for a day for now var expiration = new Date(); - expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms')); - var cert = ca.certify(m.email, pk, expiration); + expiration.setTime(new Date().valueOf() + m.validityPeriod); + var cert = ca.certify(m.hostname, m.email, pk, expiration); process.send({"success": cert}); } catch(e) { process.send({"error": e ? e.toString() : "unknown"}); diff --git a/lib/logging.js b/lib/logging.js index e6bd272da089ccb2ce48bb3dfcaa4f25eda3473e..5b13b397e7207cd7b28a5cc03437c56d6c34a0a7 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -40,7 +40,7 @@ exports.logger = new (winston.Logger)({ timestamp: true, filename: filename, colorize: true, - handleExceptions: true + handleExceptions: !!process.env['LOG_TO_CONSOLE'] })] }); @@ -51,6 +51,6 @@ exports.enableConsoleLogging = function() { }); }; -winston.handleExceptions(); + if (process.env['LOG_TO_CONSOLE']) exports.enableConsoleLogging(); diff --git a/lib/primary.js b/lib/primary.js index c0996da11b540f415c373fc61583f8041d8e8832..6a0959bba0efa6a9921776a661126030092d2b59 100644 --- a/lib/primary.js +++ b/lib/primary.js @@ -22,6 +22,8 @@ const WELL_KNOWN_URL = "/.well-known/browserid"; // cache .well-known/browserid for six hours const MAX_CACHE_MS = (6 * 60 * 60 * 1000); +const HOSTNAME = urlparse(config.get('public_url')).host; + function parseWellKnownBody(body, domain) { var v = JSON.parse(body); @@ -191,7 +193,7 @@ exports.verifyAssertion = function(assertion, cb) { bundle.certificates, new Date(), function(issuer, next) { // issuer cannot be the browserid - if (issuer === config.get('hostname')) { + if (issuer === HOSTNAME) { cb("cannot authenticate to browserid with a certificate issued by it."); } else { exports.getPublicKey(issuer, function(err, pubKey) { @@ -205,7 +207,7 @@ exports.verifyAssertion = function(assertion, cb) { tok.parse(bundle.assertion); // audience must be browserid itself - var want = urlparse(config.get('URL')).originOnly(); + var want = urlparse(config.get('public_url')).originOnly(); var got = urlparse(tok.audience).originOnly(); if (want.toString() !== got.toString()) { diff --git a/lib/verifier/certassertion.js b/lib/verifier/certassertion.js index 497de7cea7efeae05cb800f7110bc4910c68e04e..b437fe4f9abbc5ecbcb38b66ae71e21557ceda50 100644 --- a/lib/verifier/certassertion.js +++ b/lib/verifier/certassertion.js @@ -13,7 +13,8 @@ vep = require("jwcrypto/vep"), config = require("../configuration.js"), logger = require("../logging.js").logger, secrets = require('../secrets.js'), -primary = require('../primary.js'); +primary = require('../primary.js'), +urlparse = require('urlparse'); try { const publicKey = secrets.loadPublicKey(); @@ -23,7 +24,9 @@ try { setTimeout(function() { process.exit(1); }, 0); } -logger.debug("This verifier will accept assertions issued by " + config.get('hostname')); +const HOSTNAME = urlparse(config.get('public_url')).host; + +logger.debug("This verifier will accept assertions issued by " + HOSTNAME); // compare two audiences: // *want* is what was extracted from the assertion (it's trusted, we @@ -102,10 +105,10 @@ function verify(assertion, audience, successCB, errorCB) { ultimateIssuer = issuer; // allow other retrievers for testing - if (issuer === config.get('hostname')) return next(publicKey); + if (issuer === HOSTNAME) return next(publicKey); else if (config.get('disable_primary_support')) { return errorCB("this verifier doesn't respect certs issued from domains other than: " + - config.get('hostname')); + HOSTNAME); } // XXX: this network work happening inside a compute process. @@ -135,7 +138,7 @@ function verify(assertion, audience, successCB, errorCB) { // NOTE: for "delegation of authority" support we'll need to make this check // more sophisticated var domainFromEmail = principal.email.replace(/^.*@/, ''); - if (ultimateIssuer != config.get('hostname') && ultimateIssuer !== domainFromEmail) + if (ultimateIssuer != HOSTNAME && ultimateIssuer !== domainFromEmail) { return errorCB("issuer issue '" + ultimateIssuer + "' may not speak for emails from '" + domainFromEmail + "'"); diff --git a/lib/wsapi.js b/lib/wsapi.js index 66ce3e625752223552e15d9ba5728e789e0b86ff..8b7c2881a750fa4c94462e883f3ac69a8d83052b 100644 --- a/lib/wsapi.js +++ b/lib/wsapi.js @@ -31,7 +31,6 @@ statsd = require('./statsd'); bcrypt = require('./bcrypt'); i18n = require('./i18n'); - var abide = i18n.abide({ supported_languages: config.get('supported_languages'), default_lang: config.get('default_lang'), @@ -246,7 +245,7 @@ exports.setup = function(options, app) { !wsapis[operation].disallow_forward) { forwardedOperations.push(operation); - var forward_url = options.forward_writes + "wsapi/" + operation; + var forward_url = options.forward_writes + "/wsapi/" + operation; wsapis[operation].process = function(req, res) { forward(forward_url, req, res, function(err) { if (err) { @@ -322,14 +321,11 @@ exports.setup = function(options, app) { wsapis[operation].validate(req, resp, function() { if (wsapis[operation].i18n) { abide(req, resp, function () { - console.log('WSAPI running i18n code'); - wsapis[operation].process(req, resp); + wsapis[operation].process(req, resp); }); } else { - console.log('WSAPI SKIPPING i18n code'); wsapis[operation].process(req, resp); } - }); } else { next(); diff --git a/lib/wsapi/cert_key.js b/lib/wsapi/cert_key.js index 22f65a432e2e8aa66e33a006305d948082ce7c6c..84b1b7582dd865174d381eae326723a3661c410a 100644 --- a/lib/wsapi/cert_key.js +++ b/lib/wsapi/cert_key.js @@ -7,7 +7,8 @@ db = require('../db.js'), httputils = require('../httputils'), logger = require('../logging.js').logger, forward = require('../http_forward.js'), -config = require('../configuration.js'); +config = require('../configuration.js'), +urlparse = require('urlparse'); exports.method = 'post'; exports.writes_db = false; @@ -21,7 +22,7 @@ exports.process = function(req, res) { if (!owned) return httputils.badRequest(res, "that email does not belong to you"); // forward to the keysigner! - var keysigner = config.get('keysigner_url'); + var keysigner = urlparse(config.get('keysigner_url')); keysigner.path = '/wsapi/cert_key'; forward(keysigner, req, res, function(err) { if (err) { diff --git a/lib/wsapi/stage_email.js b/lib/wsapi/stage_email.js index e26980150a4a05b9a24cf7f77adc5f952ce714f8..c5b562304f4ca262e5eff7b2e66e025bb435b783 100644 --- a/lib/wsapi/stage_email.js +++ b/lib/wsapi/stage_email.js @@ -38,7 +38,6 @@ exports.process = function(req, res) { req.session.pendingAddition = secret; res.json({ success: true }); - // let's now kick out a verification email! email.sendAddAddressEmail(req.body.email, req.body.site, secret, langContext); }); diff --git a/package.json b/package.json index a2cd5c9afcad895d5997cb7f7c328c4225b311cc..62d82da3def771bb5ac45baea2041fd2effbeef4 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "bcrypt": "0.4.1", "compute-cluster": "0.0.6", "connect": "1.7.2", + "convict": "0.0.6", + "cjson": "0.0.6", "client-sessions": "0.0.3", "connect-cookie-session": "0.0.2", "connect-logger-statsd": "0.0.1", @@ -30,7 +32,7 @@ "devDependencies": { "vows": "0.5.13", "aws-lib": "0.0.5" - }, + }, "scripts": { "postinstall": "./scripts/generate_ephemeral_keys.sh", "test": "./scripts/run_all_tests.sh", diff --git a/scripts/run_locally.js b/scripts/run_locally.js index b5670b35839e2f68aa422541b9b893a46b9ce7bc..fa12dca0bd0c5581c5cef70e6f4e49d21edcd372 100755 --- a/scripts/run_locally.js +++ b/scripts/run_locally.js @@ -3,10 +3,8 @@ * 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/. */ - -const +const path = require('path'), spawn = require('child_process').spawn, -path = require('path'), config = require('../lib/configuration.js'), temp = require('temp'), secrets = require('../lib/secrets.js'); @@ -16,18 +14,9 @@ exports.daemons = daemons = {}; const HOST = process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1"; var daemonsToRun = { - verifier: { - PORT: 10000, - HOST: HOST - }, - keysigner: { - PORT: 10003, - HOST: HOST - }, - dbwriter: { - PORT: 10004, - HOST: HOST - }, + verifier: { }, + keysigner: { }, + dbwriter: { }, example: { path: path.join(__dirname, "..", "scripts", "serve_example.js"), PORT: 10001, @@ -39,18 +28,17 @@ var daemonsToRun = { PORT: 10005, HOST: HOST }, - proxy: { - PORT: 10006, - HOST: HOST - }, - browserid: { - PORT: 10002, - HOST: HOST - } + proxy: { }, + browserid: { } }; // route outbound HTTP through our in-tree proxy to always test said codepath -process.env['HTTP_PROXY'] = HOST + ":10006"; +process.env['HTTP_PROXY'] = HOST + ":10006"; + +process.env['HOST'] = HOST + +// use the "local" configuration +process.env['CONFIG_FILES'] = path.join(__dirname, '..', 'config', 'local.json'); // all spawned process that use handle primaries should know about "shimmed" // primaries @@ -66,16 +54,18 @@ process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002"; process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify"; process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003"; +process.env['URL'] = process.env['BROWSERID_URL']; + // if the environment is a 'test_' environment, then we'll use an // ephemeral database if (config.get('env').substr(0,5) === 'test_') { if (config.get('database').driver === 'mysql') { - process.env['MYSQL_DATABASE_NAME'] = - process.env['MYSQL_DATABASE_NAME'] ||"browserid_tmp_" + secrets.generate(6); - console.log("temp mysql database:", process.env['MYSQL_DATABASE_NAME']); + process.env['DATABASE_NAME'] = + process.env['DATABASE_NAME'] || "browserid_tmp_" + secrets.generate(6); + console.log("temp mysql database:", process.env['DATABASE_NAME']); } else if (config.get('database').driver === 'json') { - process.env['JSON_DATABASE_PATH'] = process.env['JSON_DATABASE_PATH'] || temp.path({suffix: '.db'}); - console.log("temp json database:", process.env['JSON_DATABASE_PATH']); + process.env['DATABASE_NAME'] = process.env['DATABASE_NAME'] || temp.path({suffix: '.db'}); + console.log("temp json database:", process.env['DATABASE_NAME']); } } diff --git a/scripts/test_db_connectivity.js b/scripts/test_db_connectivity.js index f7c8bb71a9201d2c6736ec78ab5cc80349bd298e..3b1bad4283c7252c0c3f8b99466585097d85c43b 100755 --- a/scripts/test_db_connectivity.js +++ b/scripts/test_db_connectivity.js @@ -6,6 +6,11 @@ // a simple script to test to see if we can connect to // the database using the present configuration. +const path = require('path'); + +if (!process.env['CONFIG_FILES']) { + process.env['CONFIG_FILES'] = path.join(__dirname, "..", "config", "local.json"); +} const configuration = require('../lib/configuration.js'), diff --git a/tests/ca-test.js b/tests/ca-test.js index 3f0a749b9c15281b0deebdde72b54878fb224661..ecce1e0897dc7e4c7103bde217444a26d2a4ade7 100755 --- a/tests/ca-test.js +++ b/tests/ca-test.js @@ -32,7 +32,7 @@ suite.addBatch({ topic: function() { var expiration = new Date(); expiration.setTime(new Date().valueOf() + 5000); - return ca.certify(email_addr, kp.publicKey, expiration); + return ca.certify('127.0.0.1', email_addr, kp.publicKey, expiration); }, "parses" : function(cert_raw, err) { var cert = ca.parseCert(cert_raw); @@ -42,7 +42,7 @@ suite.addBatch({ // FIXME we might want to turn this into a true async test // rather than one that is assumed to be synchronous although // it has an async structure - ca.verifyChain([cert_raw], function(pk) { + ca.verifyChain('127.0.0.1', [cert_raw], function(pk) { assert.isTrue(kp.publicKey.equals(pk)); }); } diff --git a/tests/cert-emails-test.js b/tests/cert-emails-test.js index 8e1a129c50cfa495a5fd4c6b3d47a50de6460157..d58dea80a63c7fe6f2e1e16a95168febe59f5e38 100755 --- a/tests/cert-emails-test.js +++ b/tests/cert-emails-test.js @@ -103,7 +103,7 @@ suite.addBatch({ assert.strictEqual(r.code, 200); }, "returns a proper cert": function(err, r) { - ca.verifyChain([r.body], function(pk) { + ca.verifyChain('127.0.0.1', [r.body], function(pk) { assert.isTrue(kp.publicKey.equals(pk)); }); }, @@ -127,7 +127,7 @@ suite.addBatch({ topic: function(full_assertion) { var cb = this.callback; // extract public key at the tail of the chain - ca.verifyChain(full_assertion.certificates, function(pk) { + ca.verifyChain('127.0.0.1', full_assertion.certificates, function(pk) { if (!pk) cb(false); diff --git a/tests/lib/start-stop.js b/tests/lib/start-stop.js index 3f619c527f147c70a9706e2e8dde25af5b4a6abc..d7485e469285845f34916b3f1d7a34675a3f0849 100644 --- a/tests/lib/start-stop.js +++ b/tests/lib/start-stop.js @@ -84,16 +84,13 @@ exports.addStartupBatches = function(suite) { suite.addBatch({ "specifying an ephemeral database": { topic: function() { - if (config.get('database').driver === 'mysql') { - process.env['MYSQL_DATABASE_NAME'] = config.get('database').database; - } else if (config.get('database').driver === 'json') { - process.env['JSON_DATABASE_PATH'] = config.get('database').path; - } + config.set("database.name", process.env['DATABASE_NAME']); return true; }, "should work": function(x) { - var cfg = process.env['MYSQL_DATABASE_NAME'] || process.env['JSON_DATABASE_PATH']; - assert.equal(typeof cfg, 'string'); + assert.equal(typeof config.get('database.name'), 'string'); + assert.equal(typeof process.env['DATABASE_NAME'], 'string'); + assert.equal(process.env['DATABASE_NAME'], config.get('database.name')); } } }); diff --git a/tests/lib/test_env.js b/tests/lib/test_env.js index eaeda5d1f719d72f9270d612073846c2af109f80..c45a2fa3f3e344bffe86360bd11d04187ea29e70 100644 --- a/tests/lib/test_env.js +++ b/tests/lib/test_env.js @@ -18,8 +18,8 @@ if (undefined === process.env['NODE_ENV']) { // if the environment is a 'test_' environment, then we'll use an // ephemeral database if (process.env['NODE_ENV'] === 'test_mysql') { - process.env['MYSQL_DATABASE_NAME'] = "browserid_tmp_" + + process.env['DATABASE_NAME'] = "browserid_tmp_" + require('../../lib/secrets.js').generate(6); } else if (process.env['NODE_ENV'] === 'test_json') { - process.env['JSON_DATABASE_PATH'] = require('temp').path({suffix: '.db'}); + process.env['DATABASE_NAME'] = require('temp').path({suffix: '.db'}); }