diff --git a/bin/browserid b/bin/browserid index 2eb922c05a66d4c8f321f7afc2e7c890261d15c9..9671ad3ee3632bc3dc0beb6e0e80dde64826261d 100755 --- a/bin/browserid +++ b/bin/browserid @@ -68,6 +68,12 @@ if (!config.get('keysigner_url')) { 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. @@ -75,7 +81,10 @@ if (!config.get('keysigner_url')) { // This is in front of logging on purpose. see issue #537 heartbeat.setup(app, function(cb) { // let's check stuff! first the heartbeat of our keysigner - heartbeat.check(config.get('keysigner_url'), cb); + heartbeat.check(config.get('keysigner_url'), function(rv) { + if (!rv) return cb(rv); + heartbeat.check(config.get('dbwriter_url'), cb); + }); }); // #2 - logging! all requests other than __heartbeat__ are logged @@ -141,7 +150,9 @@ app.use(function(req, resp, next) { config.performSubstitution(app); // #8 - handle /wsapi requests -wsapi.setup(app); +wsapi.setup({ + forward_writes: config.get('dbwriter_url') +}, app); // #9 - handle views for dynamicish content views.setup(app); diff --git a/bin/dbwriter b/bin/dbwriter index 79bfbf63baf3364de64126f76cce30b8ba1000fd..91296cae549aecc1fb51288e3a7591af9887cae6 100755 --- a/bin/dbwriter +++ b/bin/dbwriter @@ -1,3 +1,138 @@ #!/usr/bin/env node -require('./browserid'); +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const +fs = require('fs'), +path = require('path'), +url = require('url'), +http = require('http'); +urlparse = require('urlparse'), +express = require('express'); + +const +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/browserid/http_forward'), +shutdown = require('../lib/shutdown'), +views = require('../lib/browserid/views.js'); + +var app = undefined; + +app = express.createServer(); + +logger.info("dbwriter starting up"); + +// Setup health check / heartbeat middleware. +// This is in front of logging on purpose. see issue #537 +heartbeat.setup(app); + +// 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); + } + } +})); + +// 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(); + }); +} + +// 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(); +}); + +// 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"); + throw "internal error"; + } + realRespJSON.call(resp, obj); + }; + return next(); +}); + +// handle /wsapi requests +wsapi.setup({ + only_write_apis: true +}, app); + +// 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, function(readyForShutdown) { + logger.debug("closing database connection"); + db.close(readyForShutdown) +}); + +// 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, function(readyForShutdown) { + db.close(readyForShutdown) + }); + + var bindTo = config.get('bind_to'); + app.listen(bindTo.port, bindTo.host, function() { + logger.info("running on http://" + app.address().address + ":" + app.address().port); + }); +}); diff --git a/lib/configuration.js b/lib/configuration.js index 87230eea6f685b14f4eb799c75f9cff2927afacb..5151ffba82ff6125a9f0a01d9314ad7da94fabeb 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -158,6 +158,12 @@ if (process.env['KEYSIGNER_URL']) { 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 = { diff --git a/lib/wsapi.js b/lib/wsapi.js index 3c14d7d53e053d385f858ddf3bdd2924d9f5838c..b768db346b82e57b2862d7e0a936b7f2e3947caa 100644 --- a/lib/wsapi.js +++ b/lib/wsapi.js @@ -32,7 +32,6 @@ function clearAuthenticatedUser(session) { }); } - function isAuthed(req) { var who; try { @@ -84,7 +83,7 @@ exports.isAuthed = isAuthed; exports.bcryptPassword = bcryptPassword; exports.setAuthenticatedUser = setAuthenticatedUser; -exports.setup = function(app) { +exports.setup = function(options, app) { // XXX: we can and should make all of the logic below only take effect for POST requests // to /wsapi to reduce code run for other requests (cookie parsing, etc) @@ -182,6 +181,12 @@ exports.setup = function(app) { try { var api = require(path.join(__dirname, 'wsapi', f)); + + // don't register read apis if we are configured as a writer + if (options.only_write_apis && !api.writes_db) return; + + // XXX forward writes if options.forward_writes is defined + wsapis[operation] = api; // set up the argument validator diff --git a/scripts/run_locally.js b/scripts/run_locally.js index 6c48078ec856a57b424559dc710ef94bbeb0930a..9127578f76e8214fdeccec8917abea8970b0a596 100755 --- a/scripts/run_locally.js +++ b/scripts/run_locally.js @@ -17,6 +17,10 @@ var daemonsToRun = { PORT: 10003, HOST: HOST }, + dbwriter: { + PORT: 10004, + HOST: HOST + }, example: { path: path.join(__dirname, "..", "scripts", "serve_example.js"), PORT: 10001, @@ -32,6 +36,7 @@ var daemonsToRun = { process.env['LOG_TO_CONSOLE'] = 1; // all spawned processes will communicate with the local browserid +process.env['DBWRITER_URL'] = 'http://' + HOST + ":10004"; process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002"; process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify"; process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003";