#!/usr/bin/env node /* 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/. */ const fs = require('fs'), path = require('path'), url = require('url'), http = require('http'); urlparse = require('urlparse'), express = require('express'); const assets = require('../lib/static_resources').all, cachify = require('connect-cachify'), i18n = require('../lib/i18n.js'), wsapi = require('../lib/wsapi.js'), httputils = require('../lib/httputils.js'), secrets = require('../lib/secrets.js'), db = require('../lib/db.js'), config = require('../lib/configuration.js'), heartbeat = require('../lib/heartbeat.js'), metrics = require('../lib/metrics.js'), logger = require('../lib/logging.js').logger, forward = require('../lib/http_forward'), shutdown = require('../lib/shutdown'), views = require('../lib/browserid/views.js'); var app = undefined; app = express.createServer(); logger.info("browserid server starting up"); // verify that we have a keysigner configured if (!config.get('keysigner_url')) { logger.error('missing required configuration - url for the keysigner (KEYSIGNER_URL in env)'); process.exit(1); } // verify that we have a dbwriter configured if (!config.get('dbwriter_url')) { logger.error('missing required configuration - url for the dbwriter (DBWRITER_URL in env)'); process.exit(1); } // NOTE: ordering of middleware registration is important in this file, it is the // order in which middleware will be invoked as requests are processed. // #1 - Setup health check / heartbeat middleware. // This is in front of logging on purpose. see issue #537 heartbeat.setup(app); // #2 - logging! all requests other than __heartbeat__ are logged app.use(express.logger({ format: config.get('express_log_format'), stream: { write: function(x) { logger.info(typeof x === 'string' ? x.trim() : x); } } })); // #2.1 - localization app.use(i18n.abide({ supported_languages: config.get('supported_languages'), default_lang: config.get('default_lang'), debug_lang: config.get('debug_lang'), locale_directory: config.get('locale_directory'), disable_locale_check: config.get('disable_locale_check') })); // limit all content bodies to 10kb, at which point we'll forcefully // close down the connection. app.use(express.limit("10kb")); var statsd_config = config.get('statsd'); if (statsd_config && statsd_config.enabled) { logger_statsd = require("connect-logger-statsd"); app.use(logger_statsd({ host: statsd_config.hostname || "localhost", port: statsd_config.port || 8125, prefix: statsd_config.prefix || "browserid.webhead." })); } // #3 - Add Strict-Transport-Security headers if we're serving over SSL if (config.get('scheme') == 'https') { app.use(function(req, resp, next) { // expires in 30 days, include subdomains like www resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains"); next(); }); } // #4 - prevent framing of everything. content underneath that needs to be // framed must explicitly remove the x-frame-options app.use(function(req, resp, next) { resp.setHeader('x-frame-options', 'DENY'); 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( verifier_url, req, res, function(err) { if (err) { logger.error("error forwarding request:", err); } }); } else { return next(); } }); } // #6 - verify all JSON responses are objects - prevents regression on issue #217 app.use(function(req, resp, next) { var realRespJSON = resp.json; resp.json = function(obj) { if (!obj || typeof obj !== 'object') { logger.error("INTERNAL ERROR! *all* json responses must be objects"); return httputils.serverError(resp, "broken internal API implementation"); } realRespJSON.call(resp, obj); }; return next(); }); var static_root = path.join(__dirname, "..", "resources", "static"); app.use(cachify.setup(assets(config.get('supported_languages')), { prefix: config.get('cachify_prefix'), production: config.get('use_minified_resources'), root: static_root, })); // #7 - perform response substitution to support local/dev/beta environments // (specifically, this replaces URLs in responses, e.g. https://browserid.org // with https://diresworb.org) config.performSubstitution(app); // #8 - handle /wsapi requests wsapi.setup({ forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly() }, app); // #9 - handle views for dynamicish content views.setup(app); function doShutdown(readyForShutdownCB) { require('../lib/bcrypt.js').shutdown(); db.close(readyForShutdownCB) } // #11 - calls to /code_update from localhost will restart the daemon, // this feature is not externally accessible and is only used by // the update logic shutdown.installUpdateHandler(app, doShutdown); // #12 if the BROWSERID_FAKE_VERIFICATION env var is defined, we'll include // fake_verification.js. This is used during testing only and should // never be included in a production deployment if (process.env['BROWSERID_FAKE_VERIFICATION']) { require('../lib/browserid/fake_verification.js').addVerificationWSAPI(app); } // if nothing else has caught this request, serve static files, but ensure // that proper vary headers are installed to prevent unwanted caching app.use(function(req, res, next) { res.setHeader('Vary', 'Accept-Encoding,Accept-Language'); next(); }); app.use(express.static(static_root)); // open the databse 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); } // shut down express gracefully on SIGINT shutdown.handleTerminationSignals(app, doShutdown); var bindTo = config.get('bind_to'); app.listen(bindTo.port, bindTo.host, function() { logger.info("running on http://" + app.address().address + ":" + app.address().port); // #13 if the CREATE_TEST_USERS env var is defined, we'll try to create // some test users if (process.env['CREATE_TEST_USERS']) { logger.warn("creating test users... this can take a while..."); require('../lib/bcrypt').encrypt( config.get('bcrypt_work_factor'), "THE PASSWORD", function(err, hash) { if (err) { logger.error("error creating test users - bcrypt encrypt pass: " + err); process.exit(1); } var want = parseInt(process.env['CREATE_TEST_USERS']); var have = 0; for (i = 1; i <= want; i++) { db.addTestUser(i + "@loadtest.domain", hash, function(err, email) { if (++have == want) { logger.warn("created " + want + " test users"); } }); } }); } }); });