Newer
Older
#!/usr/bin/env node
/* ***** 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 ***** */
http = require('http');
Lloyd Hilaiel
committed
sessions = require('connect-cookie-session'),
urlparse = require('urlparse');
// add lib/ to the require path
require.paths.unshift(path.join(__dirname, '..', 'lib'));
const
wsapi = require('browserid/wsapi.js'),
ca = require('browserid/ca.js'),
httputils = require('httputils.js'),
secrets = require('secrets.js'),
db = require('db.js'),
config = require('configuration.js'),
heartbeat = require('heartbeat.js'),
metrics = require("metrics.js"),
Lloyd Hilaiel
committed
logger = require("logging.js").logger
forward = require('browserid/http_forward'),
Lloyd Hilaiel
committed
shutdown = require('shutdown');
Lloyd Hilaiel
committed
var app = undefined;
Lloyd Hilaiel
committed
app = express.createServer();
logger.info("browserid server starting up");
const COOKIE_SECRET = secrets.hydrateSecret('browserid_cookie', config.get('var_path'));
// 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);
}
function internal_redirector(new_url, suppress_noframes) {
if (suppress_noframes)
resp.removeHeader('x-frame-options');
req.url = new_url;
return next();
};
}
Lloyd Hilaiel
committed
app.set("views", path.join(__dirname, "..", "resources", "views"));
app.set('view options', {
production: config.get('use_minified_resources')
});
// this should probably be an internal redirect
// as soon as relative paths are figured out.
app.get('/sign_in', function(req, res, next ) {
Lloyd Hilaiel
committed
metrics.userEntry(req);
res.render('dialog.ejs', {
title: 'A Better Way to Sign In',
layout: 'dialog_layout.ejs',
useJavascript: true,
production: config.get('use_minified_resources')
Lloyd Hilaiel
committed
});
app.get("/unsupported_dialog", function(req,res) {
res.render('unsupported_dialog.ejs', {layout: 'dialog_layout.ejs', useJavascript: false});
});
app.get('/register_iframe', internal_redirector('/dialog/register_iframe.html',true));
// Used for a relay page for communication.
// Allow the relay to be run within a frame
res.removeHeader('x-frame-options');
res.render('relay.ejs', {
layout: false,
production: config.get('use_minified_resources')
app.get('/', function(req,res) {
Lloyd Hilaiel
committed
res.render('index.ejs', {title: 'A Better Way to Sign In', fullpage: true});
});
Ben Adida
committed
app.get("/signup", function(req, res) {
res.render('signup.ejs', {title: 'Sign Up', fullpage: false});
Lloyd Hilaiel
committed
});
app.get("/forgot", function(req, res) {
res.render('forgot.ejs', {title: 'Forgot Password', fullpage: false, email: req.query.email});
});
Ben Adida
committed
app.get("/signin", function(req, res) {
res.render('signin.ejs', {title: 'Sign In', fullpage: false});
Lloyd Hilaiel
committed
});
Ben Adida
committed
app.get("/about", function(req, res) {
res.render('about.ejs', {title: 'About', fullpage: false});
Lloyd Hilaiel
committed
});
Ben Adida
committed
app.get("/tos", function(req, res) {
Lloyd Hilaiel
committed
res.render('tos.ejs', {title: 'Terms of Service', fullpage: false});
});
Ben Adida
committed
app.get("/privacy", function(req, res) {
Lloyd Hilaiel
committed
res.render('privacy.ejs', {title: 'Privacy Policy', fullpage: false});
});
Ben Adida
committed
app.get("/verify_email_address", function(req, res) {
res.render('verifyuser.ejs', {title: 'Complete Registration', fullpage: true, token: req.query.token});
Lloyd Hilaiel
committed
});
Ben Adida
committed
app.get("/add_email_address", function(req,res) {
res.render('verifyemail.ejs', {title: 'Verify Email Address', fullpage: false});
Lloyd Hilaiel
committed
});
// REDIRECTS
REDIRECTS = {
"/manage": "/",
"/users": "/",
Pete Fritchman
committed
"/users/": "/",
"/primaries" : "/developers",
"/primaries/" : "/developers",
"/developers" : "https://github.com/mozilla/browserid/wiki/How-to-Use-BrowserID-on-Your-Site"
};
// set up all the redirects
// oh my watch out for scope issues on var url - closure time
for (var url in REDIRECTS) {
(function(from,to) {
app.get(from, function(req, res) {
res.redirect(to);
});
})(url, REDIRECTS[url]);
}
// register all the WSAPI handlers
wsapi.setup(app);
Pete Fritchman
committed
// setup health check / heartbeat
heartbeat.setup(app, function(cb) {
// let's check stuff! first the heartbeat of our keysigner
heartbeat.check(config.get('keysigner_url'), cb);
});
Pete Fritchman
committed
// the public key
app.get("/pk", function(req, res) {
// vep bundle of JavaScript
app.get("/vepbundle", function(req, res) {
Lloyd Hilaiel
committed
fs.readFile(__dirname + "/../node_modules/jwcrypto/vepbundle.js", function(error, content) {
if (error) {
res.writeHead(500);
res.end("oops");
console.log(error);
} else {
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(content);
res.end();
}
});
});
Lloyd Hilaiel
committed
shutdown.installUpdateHandler(app, function(readyForShutdown) {
logger.debug("closing database connection");
db.close(readyForShutdown)
Lloyd Hilaiel
committed
};
Lloyd Hilaiel
committed
// request to logger, dev formatted which omits personal data in the requests
app.use(express.logger({
Lloyd Hilaiel
committed
format: config.get('express_log_format'),
stream: {
write: function(x) {
logger.info(typeof x === 'string' ? x.trim() : x);
Lloyd Hilaiel
committed
}
}
}));
// if these are verify requests, we'll redirect them off
// to the verifier
if (config.get('verifier_url')) {
app.use(function(req, res, next) {
if (/^\/verify$/.test(req.url)) {
forward(
config.get('verifier_url'), req, res,
function(err) {
if (err) {
logger.error("error forwarding request:", err);
}
});
Lloyd Hilaiel
committed
} else {
return next();
}
});
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// over SSL?
var overSSL = (config.get('scheme') == 'https');
app.use(express.cookieParser());
var cookieSessionMiddleware = sessions({
secret: COOKIE_SECRET,
key: COOKIE_KEY,
cookie: {
path: '/wsapi',
httpOnly: true,
// IMPORTANT: we allow users to go 1 weeks on the same device
// without entering their password again
maxAge: config.get('authentication_duration_ms'),
secure: overSSL
}
});
// cookie sessions && cache control
app.use(function(req, resp, next) {
// cookie sessions are only applied to calls to /wsapi
// as all other resources can be aggressively cached
// by layers higher up based on cache control headers.
// the fallout is that all code that interacts with sessions
// should be under /wsapi
if (/^\/wsapi/.test(req.url)) {
// explicitly disallow caching on all /wsapi calls (issue #294)
resp.setHeader('Cache-Control', 'no-cache, max-age=0');
// we set this parameter so the connect-cookie-session
// sends the cookie even though the local connection is HTTP
// (the load balancer does SSL)
if (overSSL)
req.connection.proxySecure = true;
return cookieSessionMiddleware(req, resp, next);
} else {
Lloyd Hilaiel
committed
return next();
config.performSubstitution(app);
// 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();
});
app.use(express.bodyParser());
// Check CSRF token early. POST requests are only allowed to
// /wsapi and they always must have a valid csrf token
app.use(function(req, resp, next) {
// only on POSTs
if (req.method == "POST") {
var denied = false;
if (!/^\/wsapi/.test(req.url)) { // post requests only allowed to /wsapi
denied = true;
logger.warn("CSRF validation failure: POST only allowed to /wsapi urls. not '" + req.url + "'");
}
else if (req.session === undefined) { // there must be a session
denied = true;
logger.warn("CSRF validation failure: POST calls to /wsapi require an active session");
}
// the session must have a csrf token
else if (typeof req.session.csrf !== 'string') {
denied = true;
logger.warn("CSRF validation failure: POST calls to /wsapi require an csrf token to be set");
Lloyd Hilaiel
committed
}
// and the token must match what is sent in the post body
else if (req.body.csrf != req.session.csrf) {
denied = true;
// if any of these things are false, then we'll block the request
logger.warn("CSRF validation failure, token mismatch. got:" + req.body.csrf + " want:" + req.session.csrf);
}
if (denied) return httputils.badRequest(resp, "CSRF violation");
}
return next();
});
Ben Adida
committed
// a tweak to get the content type of host-meta correct
app.use(function(req, resp, next) {
if (req.url === '/.well-known/host-meta') {
resp.setHeader('content-type', 'text/xml');
}
next();
});
// Strict Transport Security
app.use(function(req, resp, next) {
if (overSSL) {
// expires in 30 days, include subdomains like www
resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains");
}
next();
});
// prevent framing
app.use(function(req, resp, next) {
resp.setHeader('x-frame-options', 'DENY');
next();
});
Lloyd Hilaiel
committed
// add the actual URL handlers other than static
router(app);
// use the express 'static' middleware for serving of static files (cache headers, HTTP range, etc)
app.use(express.static(path.join(__dirname, "..", "resources", "static")));
// open the databse
db.open(config.get('database'), function () {
Lloyd Hilaiel
committed
// shut down express gracefully on SIGINT
shutdown.handleTerminationSignals(app, function(readyForShutdown) {
db.close(readyForShutdown)
});
Lloyd Hilaiel
committed
var bindTo = config.get('bind_to');
app.listen(bindTo.port, bindTo.host, function() {
logger.info("running on http://" + app.address().address + ":" + app.address().port);
});
});