diff --git a/browserid/app.js b/browserid/app.js index e116604c8b7f46b8b3567be21cbe8c43a2c6e773..c646a2bc75950cc45829ca212c92ed6eb5831459 100644 --- a/browserid/app.js +++ b/browserid/app.js @@ -21,71 +21,71 @@ const COOKIE_SECRET = secrets.hydrateSecret('cookie_secret', VAR_DIR); const COOKIE_KEY = 'browserid_state'; function handler(request, response, next) { - // dispatch! - var urlpath = url.parse(request.url).pathname; + // dispatch! + var urlpath = url.parse(request.url).pathname; - if (urlpath === '/sign_in') { - // a little remapping! - request.url = "/dialog/index.html"; - next(); - } else if (urlpath === '/register_iframe') { - request.url = "/dialog/register_iframe.html"; - next(); - } else if (/^\/wsapi\/\w+$/.test(urlpath)) { - try { - var method = path.basename(urlpath); - wsapi[method](request, response); - } catch(e) { - var errMsg = "oops, error executing wsapi method: " + method + " (" + e.toString() +")"; - console.log(errMsg); - httputils.fourOhFour(response, errMsg); - } - } else if (/^\/users\/[^\/]+.xml$/.test(urlpath)) { - var identity = path.basename(urlpath).replace(/.xml$/, '').replace(/^acct:/, ''); - - webfinger.renderUserPage(identity, function (resultDocument) { - if (resultDocument === undefined) { - httputils.fourOhFour(response, "I don't know anything about: " + identity + "\n"); - } else { - httputils.xmlResponse(response, resultDocument); - } - }); - } else if (urlpath === "/code_update") { - console.log("code updated. shutting down."); - process.exit(); - } else { - next(); + if (urlpath === '/sign_in') { + // a little remapping! + request.url = "/dialog/index.html"; + next(); + } else if (urlpath === '/register_iframe') { + request.url = "/dialog/register_iframe.html"; + next(); + } else if (/^\/wsapi\/\w+$/.test(urlpath)) { + try { + var method = path.basename(urlpath); + wsapi[method](request, response); + } catch(e) { + var errMsg = "oops, error executing wsapi method: " + method + " (" + e.toString() +")"; + console.log(errMsg); + httputils.fourOhFour(response, errMsg); } + } else if (/^\/users\/[^\/]+.xml$/.test(urlpath)) { + var identity = path.basename(urlpath).replace(/.xml$/, '').replace(/^acct:/, ''); + + webfinger.renderUserPage(identity, function (resultDocument) { + if (resultDocument === undefined) { + httputils.fourOhFour(response, "I don't know anything about: " + identity + "\n"); + } else { + httputils.xmlResponse(response, resultDocument); + } + }); + } else if (urlpath === "/code_update") { + console.log("code updated. shutting down."); + process.exit(); + } else { + next(); + } }; exports.varDir = VAR_DIR; exports.setup = function(server) { - server.use(express.cookieParser()); + server.use(express.cookieParser()); - var cookieSessionMiddleware = sessions({ - secret: COOKIE_SECRET, - session_key: COOKIE_KEY, - path: '/' - }); + var cookieSessionMiddleware = sessions({ + secret: COOKIE_SECRET, + session_key: COOKIE_KEY, + path: '/' + }); - server.use(function(req, resp, next) { - try { - cookieSessionMiddleware(req, resp, next); - } catch(e) { - console.log("invalid cookie found: ignoring"); - delete req.cookies[COOKIE_KEY]; - cookieSessionMiddleware(req, resp, next); - } - }); + server.use(function(req, resp, next) { + try { + cookieSessionMiddleware(req, resp, next); + } catch(e) { + console.log("invalid cookie found: ignoring"); + delete req.cookies[COOKIE_KEY]; + cookieSessionMiddleware(req, resp, next); + } + }); - server.use(handler); + server.use(handler); - // a tweak to get the content type of host-meta correct - server.use(function(req, resp, next) { - if (req.url === '/.well-known/host-meta') { - resp.setHeader('content-type', 'text/xml'); - } - next(); - }); + // a tweak to get the content type of host-meta correct + server.use(function(req, resp, next) { + if (req.url === '/.well-known/host-meta') { + resp.setHeader('content-type', 'text/xml'); + } + next(); + }); } diff --git a/browserid/lib/secrets.js b/browserid/lib/secrets.js index 6923a989954b06fd4ffa1bb3ab1d9de94251e9ea..bcf03f302ee054abf58feecdd6fdefc1fefb30f6 100644 --- a/browserid/lib/secrets.js +++ b/browserid/lib/secrets.js @@ -14,11 +14,10 @@ exports.hydrateSecret = function(name, dir) { var p = path.join(dir, name + ".sekret"); var fileExists = false; var secret = undefined; - + try{ secret = fs.readFileSync(p).toString(); } catch(e) {}; if (secret === undefined) { - console.log("Generating server secret ("+name+")..."); secret = generateSecret(); fs.writeFileSync(p, secret); } diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js index aafd2d1eec96af90e3a7beca1bfc168535b32c54..ea6b5f7f6a1d4469d120acfad15a88d828959dcc 100644 --- a/browserid/lib/wsapi.js +++ b/browserid/lib/wsapi.js @@ -7,7 +7,6 @@ const db = require('./db.js'), email = require('./email.js'); function logRequest(method, args) { - console.log("WSAPI ("+method+") " + (args ? JSON.stringify(args) : "")); } function checkParams(getArgs, resp, params) { @@ -42,7 +41,7 @@ exports.have_email = function(req, resp) { // get inputs from get data! var email = url.parse(req.url, true).query['email']; logRequest("have_email", {email: email}); - db.emailKnown(email, function(known) { + db.emailKnown(email, function(known) { httputils.jsonResponse(resp, known); }); }; diff --git a/browserid/tests/forgotten-email-test.js b/browserid/tests/forgotten-email-test.js index 406a6b9fa55cac3c01b58e39c9c86cbfcfe7a44f..915c3f120ddd35012d4e8a49957b3dfb62b8f60f 100755 --- a/browserid/tests/forgotten-email-test.js +++ b/browserid/tests/forgotten-email-test.js @@ -4,7 +4,8 @@ const assert = require('assert'), vows = require('vows'), fs = require('fs'), path = require('path'), - http = require('http'); + http = require('http'), + querystring = require('querystring'); const amMain = (process.argv[1] === __filename); const varPath = path.join(path.dirname(__dirname), "var"); @@ -20,6 +21,52 @@ function removeVarDir() { } catch(e) {} } +// wsapi abstractions trivial cookie jar +var cookieJar = {}; + +// A macro for wsapi requests +var wsapi = { + get: function (path, getArgs) { + return function () { + var cb = this.callback; + if (typeof getArgs === 'object') + path += "?" + querystring.stringify(getArgs); + + var headers = {}; + if (Object.keys(cookieJar).length) { + headers['Cookie'] = ""; + for (var k in cookieJar) { + headers['Cookie'] += k + "=" + cookieJar[k]; + } + } + http.get({ + host: '127.0.0.1', + port: '62700', + path: path, + headers: headers + }, function(res) { + // see if there are any set-cookies that we should honor + if (res.headers['set-cookie']) { + res.headers['set-cookie'].forEach(function(cookie) { + var m = /^([^;]+)(?:;.*)$/.exec(cookie); + if (m) { + var x = m[1].split('='); + cookieJar[x[0]] = x[1]; + } + }); + } + var body = ''; + res.on('data', function(chunk) { body += chunk; }) + .on('end', function() { + cb({code: res.statusCode, headers: res.headers, body: body}); + }); + }).on('error', function (e) { + cb(); + }); + }; + } +}; + suite.addBatch({ "remove the user database": { topic: function() { @@ -41,26 +88,14 @@ suite.addBatch({ return true; }, "server should be running": { - topic: function() { - var cb = this.callback; - http.get({ - host: '127.0.0.1', - port: '62700', - path: '/ping.txt' - }, function(res) { - cb(true); - }).on('error', function (e) { - cb(false); - }); - }, - "server is running": function (r) { - assert.notStrictEqual(r, false); + topic: wsapi.get('/ping.txt'), + "server is running": function (r, err) { + assert.equal(r.code, 200); } } } }); - suite.addBatch({ "wait for readiness": { topic: function() { @@ -73,22 +108,120 @@ suite.addBatch({ } }); +var lastEmailBody = undefined; +// let's kludge our way into nodemailer to intercept outbound emails +const nodeMailer = require('nodemailer'); +nodeMailer.EmailMessage.prototype.send = function(callback) { + lastEmailBody = this.body; +}; - +var token = undefined; // create a new account via the api with (first address) +suite.addBatch({ + "stage first account": { + topic: wsapi.get('/wsapi/stage_user', { + email: 'first@fakeemail.com', + pass: 'firstfakepass', + pubkey: 'fakepubkey', + site:'fakesite.com' + }), + "caused an email to be sent": function (r, err) { + assert.equal(r.code, 200); + var m = /token=([a-zA-Z0-9]+)/.exec(lastEmailBody); + token = m[1]; + }, + "the token is sane": function(r, err) { + assert.strictEqual('string', typeof token); + } + } +}); + +suite.addBatch({ + "create first account": { + topic: function() { + wsapi.get('/wsapi/prove_email_ownership', { token: token }).call(this); + }, + "account created": function(r, err) { + assert.equal(r.code, 200); + } + } +}); -// manually verify the account +suite.addBatch({ + "email created": { + topic: wsapi.get('/wsapi/registration_status'), + "should exist": function(r, err) { + assert.strictEqual(r.code, 200); + assert.strictEqual(JSON.parse(r.body), "complete"); + } + } +}); // add a new email address to the account (second address) +suite.addBatch({ + "add a new email address to our account": { + topic: wsapi.get('/wsapi/add_email', { + email: 'second@fakeemail.com', + pubkey: 'fakepubkey', + site:'fakesite.com' + }), + "caused an email to be sent": function (r, err) { + assert.equal(r.code, 200); + var m = /token=([a-zA-Z0-9]+)/.exec(lastEmailBody); + token = m[1]; + }, + "the token is sane": function(r, err) { + assert.strictEqual('string', typeof token); + } + } +}); + +// confirm second email email address to the account +suite.addBatch({ + "create second account": { + topic: function() { + wsapi.get('/wsapi/prove_email_ownership', { token: token }).call(this); + }, + "account created": function(r, err) { + assert.equal(r.code, 200); + } + } +}); + +// verify now both email addresses are known +suite.addBatch({ + "first email exists": { + topic: wsapi.get('/wsapi/have_email', { email: 'first@fakeemail.com' }), + "should exist": function(r, err) { + assert.strictEqual(true, JSON.parse(r.body)); + } + }, + "second email exists": { + topic: wsapi.get('/wsapi/have_email', { email: 'second@fakeemail.com' }), + "should exist": function(r, err) { + assert.strictEqual(JSON.parse(r.body), true); + } + }, + "a random email doesn't exist": { + topic: wsapi.get('/wsapi/have_email', { email: 'third@fakeemail.com' }), + "shouldn't exist": function(r, err) { + assert.strictEqual(JSON.parse(r.body), false); + } + } +}); // run the "forgot_email" flow with first address +// XXX // try to log into the first email address with oldpassword +// XXX // try to log into the second email address with oldpassword +// XXX // try to log into the first email with newpassword +// XXX // stop the server suite.addBatch({