diff --git a/lib/primary.js b/lib/primary.js index 85fd504d4a78d02dbe8e3ad12c3be63ef84a5c57..5f7a29188c09c5fe7d5f19f1e997d29907224ffe 100644 --- a/lib/primary.js +++ b/lib/primary.js @@ -19,11 +19,10 @@ config = require("./configuration.js"); 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; +var g_shim_cache = {}; + function parseWellKnownBody(body, domain) { var v = JSON.parse(body); @@ -34,9 +33,15 @@ function parseWellKnownBody(body, domain) { if (-1 === got.indexOf(k)) throw "missing required key: " + k; }); + // Allow SHIMMED_PRIMARIES to change example.com into 127.0.0.1:10005 + var url_prefix = 'https://' + domain; + if (g_shim_cache[domain]) { + url_prefix = g_shim_cache[domain].origin; + } + var urls = { - auth: 'https://' + domain + v.authentication, - prov: 'https://' + domain + v.provisioning, + auth: url_prefix + v.authentication, + prov: url_prefix + v.provisioning, }; // validate the urls @@ -50,10 +55,6 @@ function parseWellKnownBody(body, domain) { }; } -// a cache of network responses. We want to move this into -// fast and efficient external key/value storage as we scale -var g_cache = { }; - exports.checkSupport = function(domain, cb) { if (!cb) throw "missing required callback function"; @@ -65,35 +66,44 @@ exports.checkSupport = function(domain, cb) { return process.nextTick(function() { cb("invalid domain"); }); } - // check cache age - if (g_cache[domain]) { - if (!g_cache[domain].when || (new Date() - g_cache[domain].when) > MAX_CACHE_MS) { - delete g_cache[domain]; - } + getWellKnown(domain, cb); +}; - if (g_cache[domain]) { - logger.debug("returning primary support status for '" + domain + "' from cache"); - return process.nextTick(function() { cb(null, g_cache[domain].status); }); - } - } +// Support "shimmed primaries" for local development. That is an environment variable that is any number of +// CSV values of the form: +// <domain>|<origin>|<path to .well-known/browserid>, +// where 'domain' is the domain that we would like to shim. 'origin' is the origin to which traffic should +// be directed, and 'path to .well-known/browserid' is a path to the browserid file for the domain +// +// defining this env var will pre-seed the cache so local testing can take place. example: +// +// SHIMMED_PRIMARIES=eyedee.me|http://127.0.0.1:10005|example/primary/.well-known/browserid - function cacheAndReturn(cacheValue, publicKey) { - g_cache[domain] = { - when: new Date(), - status: cacheValue, - publicKey: publicKey +if (process.env['SHIMMED_PRIMARIES']) { + var shims = process.env['SHIMMED_PRIMARIES'].split(','); + shims.forEach(function(shim) { + var a = shim.split('|'); + var domain = a[0], origin = a[1], path = a[2]; + var body = require('fs').readFileSync(path); + g_shim_cache[domain] = { + origin: origin, + body: body }; - cb(null, cacheValue); - } + logger.info("inserted primary info for '" + domain + "' into cache, TODO point at '" + origin + "'"); + }); +} +var getWellKnown = function (domain, cb) { function handleResponse(res) { + var msg; if (res.statusCode !== 200) { - logger.debug(domain + ' is not a browserid primary - non-200 response code to ' + WELL_KNOWN_URL); - return cacheAndReturn(false); + logger.debug(' is not a browserid primary - non-200 response code to ' + WELL_KNOWN_URL); + return cb("can't get public key for " + domain); } if (res.headers['content-type'].indexOf('application/json') !== 0) { - logger.debug(domain + ' is not a browserid primary - non "application/json" response to ' + WELL_KNOWN_URL); - return cacheAndReturn(false); + msg = domain + ' is not a browserid primary - non "application/json" response to ' + WELL_KNOWN_URL + logger.debug(msg); + return cb(msg); } var body = ""; @@ -102,20 +112,29 @@ exports.checkSupport = function(domain, cb) { try { var r = parseWellKnownBody(body, domain); logger.info(domain + ' is a valid browserid primary'); - return cacheAndReturn(r.urls, r.publicKey); + return cb(null, r.urls, r.publicKey); } catch(e) { - logger.debug(domain + ' is a broken browserid primary, malformed dec of support: ' + e.toString()); - return cacheAndReturn(false); + msg = domain + ' is a broken browserid primary, malformed dec of support: ' + e.toString(); + logger.debug(msg); + return cb(msg); } }); }; + if (g_shim_cache[domain]) { + var body = g_shim_cache[domain].body, + r = parseWellKnownBody(body, domain); + return cb(null, r.urls, r.publicKey); + } + // now we need to check to see if domain purports to being a primary // for browserid var httpProxy = config.has('http_proxy') ? config.get('http_proxy') : null; var req; if (httpProxy && httpProxy.port && httpProxy.host) { + // In production we use Squid as a reverse proxy cache to reduce how often + // we request this resource. req = http.get({ host: httpProxy.host, port: httpProxy.port, @@ -133,48 +152,15 @@ exports.checkSupport = function(domain, cb) { } req.on('error', function(e) { - logger.debug(domain + ' is not a browserid primary: ' + e.toString()); - cacheAndReturn(false); + var msg = domain + ' is not a browserid primary: ' + e.toString(); + logger.debug(msg); + cb(msg); }); }; -// Support "shimmed primaries" for local development. That is an environment variable that is any number of -// CSV values of the form: -// <domain>|<origin>|<path to .well-known/browserid>, -// where 'domain' is the domain that we would like to shim. 'origin' is the origin to which traffic should -// be directed, and 'path to .well-known/browserid' is a path to the browserid file for the domain -// -// defining this env var will pre-seed the cache so local testing can take place. example: -// -// SHIMMED_PRIMARIES=eyedee.me|http://127.0.0.1:10005|example/primary/.well-known/browserid - -if (process.env['SHIMMED_PRIMARIES']) { - var shims = process.env['SHIMMED_PRIMARIES'].split(','); - shims.forEach(function(shim) { - var a = shim.split('|'); - var domain = a[0], origin = a[1], path = a[2]; - var body = require('fs').readFileSync(path); - var r = parseWellKnownBody(body, domain); - r.urls.auth = r.urls.auth.replace('https://' + domain, origin); - r.urls.prov = r.urls.prov.replace('https://' + domain, origin); - - g_cache[domain] = { - when: new Date(), - status: r.urls, - publicKey: r.publicKey - }; - - logger.info("inserted primary info for '" + domain + "' into cache, pointed at '" + origin + "'"); - }); -} - exports.getPublicKey = function(domain, cb) { - exports.checkSupport(domain, function(err, rv) { - if (err) return cb(err); - var pubKey; - if (rv) pubKey = g_cache[domain].publicKey; - if (!pubKey) return cb("can't get public key for " + domain); - cb(null, pubKey); + exports.checkSupport(domain, function(err, urls, publicKey) { + cb(err, publicKey); }); }; @@ -189,7 +175,6 @@ exports.verifyAssertion = function(assertion, cb) { } catch(e) { return process.nextTick(function() { cb("malformed assertion: " + e); }); } - jwcert.JWCert.verifyChain( bundle.certificates, new Date(), function(issuer, next) { diff --git a/lib/wsapi/address_info.js b/lib/wsapi/address_info.js index bfccae4b9af457f43520e46dcfc2aa81de5d67fa..68284f964fd8f325adabbf53c91d3a75e4201b4b 100644 --- a/lib/wsapi/address_info.js +++ b/lib/wsapi/address_info.js @@ -31,15 +31,15 @@ exports.process = function(req, resp) { return httputils.badRequest(resp, "invalid email address"); } - primary.checkSupport(m[1], function(err, rv) { + primary.checkSupport(m[1], function(err, urls, publicKey) { if (err) { logger.warn('error checking "' + m[1] + '" for primary support: ' + err); return httputils.serverError(resp, "can't check email address"); } - if (rv) { - rv.type = 'primary'; - resp.json(rv); + if (urls) { + urls.type = 'primary'; + resp.json(urls); } else { db.emailKnown(email, function(err, known) { if (err) { diff --git a/scripts/check_primary_support b/scripts/check_primary_support index ef913d3bdb493af6c43f748ff1af2516b9608f9b..3a2327fb8671d7d6facc461973f0a8bbce7b0197 100755 --- a/scripts/check_primary_support +++ b/scripts/check_primary_support @@ -16,10 +16,10 @@ if (process.argv.length !== 3) { process.exit(1); } -primary.checkSupport(process.argv[2], function(err, rv) { +primary.checkSupport(process.argv[2], function(err, urls, publicKey) { if (err) { process.stderr.write("error: " + err + "\n"); process.exit(1); } - console.log(rv); + console.log(urls, publicKey); });