diff --git a/bin/keysigner b/bin/keysigner index 1d981b0c5bc2203a46673e15eef3f423920402de..8e1f2a733bc38ec6e6b35c5c56b473664f8759fb 100755 --- a/bin/keysigner +++ b/bin/keysigner @@ -74,13 +74,15 @@ try { process.exit(1); } + + // and our single function -app.post('/wsapi/cert_key', validate(["email", "pubkey", "ephemeral"]), function(req, resp) { +app.post('/wsapi/cert_key', validate({ 'email': 'email', 'pubkey': 'pubkey', 'ephemeral': 'boolean' }), function(req, resp) { var startTime = new Date(); cc.enqueue({ - pubkey: req.body.pubkey, - email: req.body.email, - validityPeriod: (req.body.ephemeral ? config.get('ephemeral_session_duration_ms') : config.get('certificate_validity_ms')), + pubkey: req.params.pubkey, + email: req.params.email, + validityPeriod: (req.params.ephemeral ? config.get('ephemeral_session_duration_ms') : config.get('certificate_validity_ms')), hostname: HOSTNAME }, function (err, r) { var reqTime = new Date - startTime; diff --git a/lib/sanitize.js b/lib/sanitize.js deleted file mode 100644 index cd63a569f19352ca238ca5e03266db6ad010a8c0..0000000000000000000000000000000000000000 --- a/lib/sanitize.js +++ /dev/null @@ -1,44 +0,0 @@ -/* 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/. */ - -// a teensy tinsy module to do parameter sanitization. A good candiate for future -// librification. -// -// usage: -// -// const sanitize = require('sanitize'); -// -// sanitize(value).isEmail(); -// sanitize(value).isDomain(); - -// XXX - should review these simple regexps - -var logger = require('./logging.js').logger; - -module.exports = function (value) { - var isEmail = function() { - - if (!value.toLowerCase().match(/^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z\d_-]+(\.[a-z\d_-]+)+$/i)) - throw "not a valid email"; - }; - - var isDomain = function() { - if (!value.match(/^[a-z\d_-]+(\.[a-z\d-]+)+$/i)) { - throw "not a valid domain"; - } - }; - - var isOrigin = function() { - // allow single hostnames, e.g. localhost - if (!value.match(/^https?:\/\/[a-z\d_-]+(\.[a-z\d_-]+)*(:\d+)?$/i)) { - throw "not a valid origin"; - } - }; - - return { - isEmail: isEmail, - isDomain: isDomain, - isOrigin: isOrigin - }; -}; diff --git a/lib/validate.js b/lib/validate.js index 83b32ac1b3cea754a61f38b06f193d39e63cbafd..dd2e55b6fefb0a8c36668f404d93958dcbc9cd99 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -14,29 +14,104 @@ const logger = require('./logging.js').logger, -httputils = require('./httputils.js'); +httputils = require('./httputils.js'), +check = require('validator').check; + +var types = { + email: function(x) { + check(x).isEmail(); + }, + password: function(x) { + check(x).len(8,80); + }, + boolean: function(x) { + if (typeof x !== 'boolean') throw "boolean required"; + }, + token: function(x) { + check(x).len(48,48).isAlphanumeric(); + }, + assertion: function(x) { + check(x).len(50,10240).regex(/[0-9a-zA-Z~_-]+/); + }, + pubkey: function(x) { + check(x).len(50,10240); + JSON.parse(x); + }, + origin: function(x) { + // allow single hostnames, e.g. localhost + if (typeof x !== 'string' || !x.match(/^https?:\/\/[a-z\d_-]+(\.[a-z\d_-]+)*(:\d+)?$/i)) { + throw "not a valid origin"; + } + } +}; module.exports = function (params) { + // normalize the parameters description, verify all specified types are present + if (Array.isArray(params) || typeof params !== 'object' || typeof params === null) { + throw "argument to validate must be an object, not a " + (typeof params); + } + + Object.keys(params).forEach(function(p) { + var v = params[p]; + if (typeof v === 'string') { + v = { type: v }; + } + if (typeof v.required === "undefined") v.required = true; + + if (!types[v.type]) throw "unknown type specified in WSAPI:" + v.type; + params[p] = v; + }); + return function(req, resp, next) { - var params_in_request=null; + var reqParams = null; if (req.method === "POST") { - params_in_request = req.body; + reqParams = req.body; } else { - params_in_request = req.query; + reqParams = req.query; } + // clear body and query to prevent wsapi handlers from accessing + // un-validated input parameters + req.body = {}; + req.query = {}; + req.params = {}; + + // now validate try { - params.forEach(function(k) { - if (!params_in_request || !params_in_request.hasOwnProperty(k)) { - throw k; + // allow csrf through + if (reqParams.csrf) { + req.params.csrf = reqParams.csrf; + delete reqParams.csrf; + } + + Object.keys(params).forEach(function(p) { + if (params[p].required && !reqParams.hasOwnProperty(p)) throw "missing required parameter: '" + p + "'"; + if (reqParams[p] === undefined) return; + + // validate + try { + types[params[p].type](reqParams[p]); + } catch (e) { + throw p + ": " + e.toString(); } + req.params[p] = reqParams[p]; + delete reqParams[p]; }); + + // if there are any keys left in reqParams, they're not allowable! + var extra = Object.keys(reqParams); + if (extra.length) throw "extra parameters are not allowed: " + extra.join(', '); } catch(e) { - var msg = "missing '" + e + "' argument"; - logger.warn("bad request received: " + msg); - return httputils.badRequest(resp, msg); + var msg = { + success: false, + reason: e.toString() + }; + logger.warn("bad request received: " + msg.reason); + resp.statusCode = 400; + return resp.json(msg); } + // this is called outside the try/catch because errors // in the handling of the request should be caught separately next(); diff --git a/lib/wsapi.js b/lib/wsapi.js index b9c5b50f7f384ec1f2fe5336cc165b27c4441585..cabedf038c27df25dc4dcb4f6b0eb765b8f8e8a7 100644 --- a/lib/wsapi.js +++ b/lib/wsapi.js @@ -97,12 +97,6 @@ function authenticateSession(session, uid, level, duration_ms) { } } -function checkPassword(pass) { - if (!pass || pass.length < 8 || pass.length > 80) { - return "valid passwords are between 8 and 80 chars"; - } -} - function langContext(req) { return { lang: req.lang, @@ -147,7 +141,6 @@ exports.clearAuthenticatedUser = clearAuthenticatedUser; exports.isAuthed = isAuthed; exports.bcryptPassword = bcryptPassword; exports.authenticateSession = authenticateSession; -exports.checkPassword = checkPassword; exports.forwardWritesTo = undefined; exports.langContext = langContext; exports.databaseDown = databaseDown; @@ -248,7 +241,8 @@ exports.setup = function(options, app) { str += op.method.toUpperCase() + " - "; str += (op.authed ? "" : "not ") + "authed"; if (op.args) { - str += " - " + op.args.join(", "); + var keys = Array.isArray(op.args) ? op.args : Object.keys(op.args); + str += " - " + keys.join(", "); } if (op.internal) str += ' - internal'; str += ")"; @@ -271,7 +265,6 @@ exports.setup = function(options, app) { // set up the argument validator if (api.args) { - if (!Array.isArray(api.args)) throw "exports.args must be an array of strings"; wsapis[operation].validate = validate(api.args); } else { wsapis[operation].validate = function(req,res,next) { next(); }; diff --git a/lib/wsapi/add_email_with_assertion.js b/lib/wsapi/add_email_with_assertion.js index e8649ceb64911cfc659eefc2ae44c5da639d9980..344c9d50d1e0a2680543e015ffccef4b243eff3f 100644 --- a/lib/wsapi/add_email_with_assertion.js +++ b/lib/wsapi/add_email_with_assertion.js @@ -15,7 +15,9 @@ https = require('https'); exports.method = 'post'; exports.writes_db = true; exports.authed = 'assertion'; -exports.args = ['assertion']; +exports.args = { + 'assertion': 'assertion' +}; exports.i18n = false; // This WSAPI will be invoked when a user attempts to add a primary @@ -23,7 +25,7 @@ exports.i18n = false; // authenticated. exports.process = function(req, res) { // first let's verify that the assertion is valid - primary.verifyAssertion(req.body.assertion, function(err, email) { + primary.verifyAssertion(req.params.assertion, function(err, email) { if (err) { return res.json({ success: false, diff --git a/lib/wsapi/address_info.js b/lib/wsapi/address_info.js index 16943822da8268103daff7b3235f324a278e3cbb..a9ae6733af46cbdbc1754019303459d3f7219198 100644 --- a/lib/wsapi/address_info.js +++ b/lib/wsapi/address_info.js @@ -21,19 +21,19 @@ logger = require('../logging.js').logger; exports.method = 'get'; exports.writes_db = false; exports.authed = false; -exports.args = ['email']; +exports.args = { + 'email': 'email' +}; exports.i18n = false; const emailRegex = /\@(.*)$/; exports.process = function(req, res) { // parse out the domain from the email - var email = url.parse(req.url, true).query['email']; - var m = emailRegex.exec(email); - if (!m) { - return httputils.badRequest(res, "invalid email address"); - } + var m = emailRegex.exec(req.params.email); + // Saftey value for production branch only + // (lth) ^^ what does this mean? ^^ var done = false; primary.checkSupport(m[1], function(err, urls, publicKey, delegates) { if (done) { @@ -49,7 +49,7 @@ exports.process = function(req, res) { urls.type = 'primary'; res.json(urls); } else { - db.emailKnown(email, function(err, known) { + db.emailKnown(req.params.email, function(err, known) { if (err) { return wsapi.databaseDown(res, err); } else { diff --git a/lib/wsapi/auth_with_assertion.js b/lib/wsapi/auth_with_assertion.js index 37127d6edab08d8c01cfafc8edc7e2f8dd76a620..3d76fa43a00e40f437838afa14af4403c3d81965 100644 --- a/lib/wsapi/auth_with_assertion.js +++ b/lib/wsapi/auth_with_assertion.js @@ -16,7 +16,10 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = false; exports.authed = false; -exports.args = ['assertion', 'ephemeral']; +exports.args = { + 'assertion': 'assertion', + 'ephemeral': 'boolean' +}; exports.i18n = false; exports.process = function(req, res) { @@ -25,7 +28,7 @@ exports.process = function(req, res) { // create a user account if that's needed // 1. first let's verify that the assertion is valid - primary.verifyAssertion(req.body.assertion, function(err, email) { + primary.verifyAssertion(req.params.assertion, function(err, email) { if (err) { return res.json({ success: false, @@ -43,7 +46,7 @@ exports.process = function(req, res) { if (err) return wsapi.databaseDown(res, err); if (!uid) return res.json({ success: false, reason: "internal error" }); wsapi.authenticateSession(req.session, uid, 'assertion', - req.body.ephemeral ? config.get('ephemeral_session_duration_ms') + req.params.ephemeral ? config.get('ephemeral_session_duration_ms') : config.get('authentication_duration_ms')); return res.json({ success: true, userid: uid }); }); @@ -61,8 +64,8 @@ exports.process = function(req, res) { var m = u.scheme === 'http' ? http : https; var post_body = querystring.stringify({ - assertion: req.body.assertion, - csrf: req.body.csrf + assertion: req.params.assertion, + csrf: req.params.csrf }); var preq = m.request({ @@ -94,7 +97,7 @@ exports.process = function(req, res) { logger.info("successfully created primary acct for " + email + " (" + r.userid + ")"); wsapi.authenticateSession(req.session, r.userid, 'assertion', - req.body.ephemeral ? config.get('ephemeral_session_duration_ms') + req.params.ephemeral ? config.get('ephemeral_session_duration_ms') : config.get('authentication_duration_ms')); res.json({ success: true, userid: r.userid }); }); diff --git a/lib/wsapi/authenticate_user.js b/lib/wsapi/authenticate_user.js index 938b38aa152b275b0ad489b08d1bb81edf24bb6d..6c0d21db8c3e99a115d6874d1c9d2871a6c8c727 100644 --- a/lib/wsapi/authenticate_user.js +++ b/lib/wsapi/authenticate_user.js @@ -17,18 +17,22 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = false; exports.authed = false; -exports.args = ['email','pass', 'ephemeral']; exports.i18n = false; +exports.args = { + 'email': 'email', + 'pass': 'password', + 'ephemeral': 'boolean' +}; exports.process = function(req, res) { function fail(reason) { var r = { success: false }; if (reason) r.reason = reason; - logger.debug('authentication fails for user: ' + req.body.email + (reason ? (' - ' + reason) : "")); + logger.debug('authentication fails for user: ' + req.params.email + (reason ? (' - ' + reason) : "")); return res.json(r); } - db.emailToUID(req.body.email, function(err, uid) { + db.emailToUID(req.params.email, function(err, uid) { if (err) return wsapi.databaseDown(res, err); if (typeof uid !== 'number') { @@ -43,7 +47,7 @@ exports.process = function(req, res) { } var startTime = new Date(); - bcrypt.compare(req.body.pass, hash, function (err, success) { + bcrypt.compare(req.params.pass, hash, function (err, success) { var reqTime = new Date - startTime; statsd.timing('bcrypt.compare_time', reqTime); @@ -56,13 +60,13 @@ exports.process = function(req, res) { logger.error("error comparing passwords with bcrypt: " + err); return fail("internal password check error"); } else if (!success) { - return fail("password mismatch for user: " + req.body.email); + return fail("password mismatch for user: " + req.params.email); } else { if (!req.session) req.session = {}; wsapi.authenticateSession(req.session, uid, 'password', - req.body.ephemeral ? config.get('ephemeral_session_duration_ms') - : config.get('authentication_duration_ms')); + req.params.ephemeral ? config.get('ephemeral_session_duration_ms') + : config.get('authentication_duration_ms')); res.json({ success: true, userid: uid }); @@ -78,9 +82,9 @@ exports.process = function(req, res) { var m = u.scheme === 'http' ? http : https; var post_body = querystring.stringify({ - oldpass: req.body.pass, - newpass: req.body.pass, - csrf: req.body.csrf + oldpass: req.params.pass, + newpass: req.params.pass, + csrf: req.params.csrf }); var preq = m.request({ host: u.host, diff --git a/lib/wsapi/cert_key.js b/lib/wsapi/cert_key.js index 1f373b92a0a5377351cf2d56619d8eef1ed4e694..71be5c629cfb815dc2f6df14a9462d902f983792 100644 --- a/lib/wsapi/cert_key.js +++ b/lib/wsapi/cert_key.js @@ -14,11 +14,15 @@ wsapi = require('../wsapi.js'); exports.method = 'post'; exports.writes_db = false; exports.authed = 'password'; -exports.args = ['email','pubkey','ephemeral']; +exports.args = { + 'email': 'email', + 'pubkey': 'pubkey', + 'ephemeral': 'boolean' +}; exports.i18n = false; exports.process = function(req, res) { - db.userOwnsEmail(req.session.userid, req.body.email, function(err, owned) { + db.userOwnsEmail(req.session.userid, req.params.email, function(err, owned) { if (err) return wsapi.databaseDown(res, err); // not same account? big fat error @@ -27,12 +31,22 @@ exports.process = function(req, res) { // secondary addresses in the database may be "unverified". this occurs when // a user forgets their password. We will not issue certs for unverified email // addresses - db.emailIsVerified(req.body.email, function(err, verified) { + db.emailIsVerified(req.params.email, function(err, verified) { if (!verified) return httputils.forbidden(res, "that email requires (re)verification"); // forward to the keysigner! var keysigner = urlparse(config.get('keysigner_url')); keysigner.path = '/wsapi/cert_key'; + + // parameter validation moves arguments from req.body to req.params, + // and removes them from req.body. This feature makes it impossible + // to use unvalidated params in your wsapi "process" function. + // + // http_forward, however, will only forward params in req.body + // or req.query. so we explicitly copy req.params to req.body + // to cause them to be forwarded. + req.body = req.params; + forward(keysigner, req, res, function(err) { if (err) { logger.error("error forwarding request to keysigner: " + err); diff --git a/lib/wsapi/complete_email_confirmation.js b/lib/wsapi/complete_email_confirmation.js index fefdd1fc31d6fd9da4f76aadf908f6cfa6441165..f3ba86e791dadd98de37b3ef7e2ccba68ff205ed 100644 --- a/lib/wsapi/complete_email_confirmation.js +++ b/lib/wsapi/complete_email_confirmation.js @@ -18,9 +18,15 @@ httputils = require('../httputils.js'); exports.method = 'post'; exports.writes_db = true; exports.authed = false; -// NOTE: this API also takes a 'pass' parameter which is required -// when a user has a null password (only primaries on their acct) -exports.args = ['token']; +exports.args = { + 'token': 'token', + // NOTE: 'pass' is required when a user has a null password + // (only primaries on their acct) + 'pass': { + type: 'password', + required: false + } +}; exports.i18n = false; exports.process = function(req, res) { @@ -28,7 +34,8 @@ exports.process = function(req, res) { // // 1. you must already be authenticated as the user who initiated the verification // 2. you must provide the password of the initiator. - db.authForVerificationSecret(req.body.token, function(err, initiator_hash, initiator_uid) { + + db.authForVerificationSecret(req.params.token, function(err, initiator_hash, initiator_uid) { if (err) { logger.info("unknown verification secret: " + err); return wsapi.databaseDown(res, err); @@ -36,8 +43,8 @@ exports.process = function(req, res) { if (req.session.userid === initiator_uid) { postAuthentication(); - } else if (typeof req.body.pass === 'string') { - bcrypt.compare(req.body.pass, initiator_hash, function (err, success) { + } else if (typeof req.params.pass === 'string') { + bcrypt.compare(req.params.pass, initiator_hash, function (err, success) { if (err) { logger.warn("max load hit, failing on auth request with 503: " + err); return httputils.serviceUnavailable(res, "server is too busy"); @@ -52,7 +59,7 @@ exports.process = function(req, res) { } function postAuthentication() { - db.completeConfirmEmail(req.body.token, function(e, email, uid) { + db.completeConfirmEmail(req.params.token, function(e, email, uid) { if (e) { logger.warn("couldn't complete email verification: " + e); wsapi.databaseDown(res, e); diff --git a/lib/wsapi/complete_reset.js b/lib/wsapi/complete_reset.js index b48f6582e94814fa91c730f4e68c760a6c1b4ae2..4d3bcfec2b762c39d535d8938f9b36e37e2fecba 100644 --- a/lib/wsapi/complete_reset.js +++ b/lib/wsapi/complete_reset.js @@ -15,7 +15,13 @@ exports.writes_db = true; exports.authed = false; // NOTE: this API also takes a 'pass' parameter which is required // when a user has a null password (only primaries on their acct) -exports.args = ['token']; +exports.args = { + 'token': 'token', + 'pass': { + type: 'password', + required: false + } +}; exports.i18n = true; exports.process = function(req, res) { @@ -28,18 +34,18 @@ exports.process = function(req, res) { // is this the same browser? if (typeof req.session.pendingReset === 'string' && - req.body.token === req.session.pendingReset) { + req.params.token === req.session.pendingReset) { return postAuthentication(); } // is a password provided? - else if (typeof req.body.pass === 'string') { - return db.authForVerificationSecret(req.body.token, function(err, hash) { + else if (typeof req.params.pass === 'string') { + return db.authForVerificationSecret(req.params.token, function(err, hash) { if (err) { logger.warn("couldn't get password for verification secret: " + err); return wsapi.databaseDown(res, err); } - bcrypt.compare(req.body.pass, hash, function (err, success) { + bcrypt.compare(req.params.pass, hash, function (err, success) { if (err) { logger.warn("max load hit, failing on auth request with 503: " + err); return httputils.serviceUnavailable(res, "server is too busy"); @@ -55,7 +61,7 @@ exports.process = function(req, res) { } function postAuthentication() { - db.haveVerificationSecret(req.body.token, function(err, known) { + db.haveVerificationSecret(req.params.token, function(err, known) { if (err) return wsapi.databaseDown(res, err); if (!known) { @@ -65,7 +71,7 @@ exports.process = function(req, res) { return res.json({ success: false} ); } - db.completePasswordReset(req.body.token, function(err, email, uid) { + db.completePasswordReset(req.params.token, function(err, email, uid) { if (err) { logger.warn("couldn't complete email verification: " + err); wsapi.databaseDown(res, err); diff --git a/lib/wsapi/complete_user_creation.js b/lib/wsapi/complete_user_creation.js index 5e253d38a5f7ca5b766796c6dcdab5936e07adf3..f737e3b8f297bc8873070dcc4b2a2d905b6e9fd4 100644 --- a/lib/wsapi/complete_user_creation.js +++ b/lib/wsapi/complete_user_creation.js @@ -13,7 +13,15 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = true; exports.authed = false; -exports.args = ['token']; +exports.args = { + 'token': 'token', + // NOTE: 'pass' is required when a user completes on a different device + // than they initiate + 'pass': { + type: 'password', + required: false + } +}; exports.i18n = false; exports.process = function(req, res) { @@ -31,18 +39,18 @@ exports.process = function(req, res) { // is this the same browser? if (typeof req.session.pendingCreation === 'string' && - req.body.token === req.session.pendingCreation) { + req.params.token === req.session.pendingCreation) { return postAuthentication(); } // is a password provided? - else if (typeof req.body.pass === 'string') { - return db.authForVerificationSecret(req.body.token, function(err, hash) { + else if (typeof req.params.pass === 'string') { + return db.authForVerificationSecret(req.params.token, function(err, hash) { if (err) { logger.warn("couldn't get password for verification secret: " + err); return wsapi.databaseDown(res, err); } - bcrypt.compare(req.body.pass, hash, function (err, success) { + bcrypt.compare(req.params.pass, hash, function (err, success) { if (err) { logger.warn("max load hit, failing on auth request with 503: " + err); return httputils.serviceUnavailable(res, "server is too busy"); @@ -58,7 +66,7 @@ exports.process = function(req, res) { } function postAuthentication() { - db.haveVerificationSecret(req.body.token, function(err, known) { + db.haveVerificationSecret(req.params.token, function(err, known) { if (err) return wsapi.databaseDown(res, err); if (!known) { @@ -68,7 +76,7 @@ exports.process = function(req, res) { return res.json({ success: false} ); } - db.completeCreateUser(req.body.token, function(err, email, uid) { + db.completeCreateUser(req.params.token, function(err, email, uid) { if (err) { logger.warn("couldn't complete email verification: " + err); wsapi.databaseDown(res, err); diff --git a/lib/wsapi/create_account_with_assertion.js b/lib/wsapi/create_account_with_assertion.js index 3fdd3f3608037056ab8326767946d58a91ef182e..96beb7d9b225f76641d0547e99a3473757d64586 100644 --- a/lib/wsapi/create_account_with_assertion.js +++ b/lib/wsapi/create_account_with_assertion.js @@ -13,16 +13,20 @@ exports.method = 'post'; exports.writes_db = true; exports.authed = false; exports.internal = true; -exports.args = ['assertion']; +exports.args = { + assertion: 'assertion' +}; exports.i18n = false; exports.process = function(req, res) { // let's (re)verify that the assertion is valid - primary.verifyAssertion(req.body.assertion, function(err, email) { + primary.verifyAssertion(req.params.assertion, function(err, email) { if (err) { // this should not be an error, the assertion should have already been // tested on the webhead - logger.error('verfication of primary assertion failed unexpectedly dbwriter (' + err + '): ' + req.body.assertion); + logger.error('verfication of primary assertion failed unexpectedly dbwriter (' + err + '): ' + + req.params.assertion); + return httputils.serverError(res); } diff --git a/lib/wsapi/email_addition_status.js b/lib/wsapi/email_addition_status.js index 5a7a3017d536a095cb2072137e94a6c6fa436021..d3eb3e569215db59e6b1f7fd8f0ace984780f19a 100644 --- a/lib/wsapi/email_addition_status.js +++ b/lib/wsapi/email_addition_status.js @@ -15,11 +15,11 @@ wsapi = require('../wsapi.js'); exports.method = 'get'; exports.writes_db = false; exports.authed = 'assertion'; -exports.args = ['email']; +exports.args = { email: 'email' }; exports.i18n = false; exports.process = function(req, res) { - var email = req.query.email; + var email = req.params.email; // check if the currently authenticated user has the email stored under pendingAddition // in their acct. diff --git a/lib/wsapi/email_for_token.js b/lib/wsapi/email_for_token.js index 41a3c40272fc8c9139433c775373ce1097927d03..3aed92174cf1c5b138638959cffea8a223355803 100644 --- a/lib/wsapi/email_for_token.js +++ b/lib/wsapi/email_for_token.js @@ -21,12 +21,14 @@ logger = require('../logging.js').logger; exports.method = 'get'; exports.writes_db = false; exports.authed = false; -exports.args = ['token']; +exports.args = { + 'token': 'token' +}; exports.i18n = false; exports.process = function(req, res) { - db.emailForVerificationSecret(req.query.token, function(err, email, uid, hash) { + db.emailForVerificationSecret(req.params.token, function(err, email, uid, hash) { if (err) { if (err === 'database unavailable') { return httputils.serviceUnavailable(res, err); @@ -48,11 +50,11 @@ exports.process = function(req, res) { must_auth = false; } else if (!uid && typeof req.session.pendingCreation === 'string' && - req.query.token === req.session.pendingCreation) { + req.params.token === req.session.pendingCreation) { must_auth = false; } else if (typeof req.session.pendingReset === 'string' && - req.query.token === req.session.pendingReset) + req.params.token === req.session.pendingReset) { must_auth = false; } diff --git a/lib/wsapi/email_reverify_status.js b/lib/wsapi/email_reverify_status.js index 5068459c3312e7f0fc3baf2e48b5a6a9319f2b1c..c46d887517d99e1622d233a547714d812c8d33df 100644 --- a/lib/wsapi/email_reverify_status.js +++ b/lib/wsapi/email_reverify_status.js @@ -13,21 +13,17 @@ wsapi = require('../wsapi.js'); exports.method = 'get'; exports.writes_db = false; exports.authed = 'assertion'; -exports.args = ['email']; +exports.args = { email: 'email' }; exports.i18n = false; exports.process = function(req, res) { - var email = req.query.email; - + // For simplicity, all we check is if an email is verified. We do not check that // the email is owned by the currently authenticated user, nor that the verification // secret still exists. These checks would require more database interactions, and // other calls will fail in such circumstances. - - // is the address verified? - db.emailIsVerified(email, function(err, verified) { + db.emailIsVerified(req.params.email, function(err, verified) { if (err) return wsapi.databaseDown(res, err); - res.json({ status: verified ? 'complete' : 'pending' }); }); }; diff --git a/lib/wsapi/have_email.js b/lib/wsapi/have_email.js index 2ff22f0306e81331435dcff93cf9107d18322c05..931bbb40b72ef9f0eac0e7458adb04abcf2eebf8 100644 --- a/lib/wsapi/have_email.js +++ b/lib/wsapi/have_email.js @@ -12,12 +12,13 @@ url = require('url'); exports.method = 'get'; exports.writes_db = false; exports.authed = false; -exports.args = ['email']; exports.i18n = false; +exports.args = { + 'email': 'email' +}; exports.process = function(req, res) { - var email = url.parse(req.url, true).query['email']; - db.emailKnown(email, function(err, known) { + db.emailKnown(req.params.email, function(err, known) { if (err) return wsapi.databaseDown(res, err); res.json({ email_known: known }); }); diff --git a/lib/wsapi/password_reset_status.js b/lib/wsapi/password_reset_status.js index 6059eac063238f9507b52c893ea01fc663c19428..e82b2f1dfb6d59dec8e88fe3b9b7412ab95ca6a3 100644 --- a/lib/wsapi/password_reset_status.js +++ b/lib/wsapi/password_reset_status.js @@ -6,25 +6,16 @@ const db = require('../db.js'), wsapi = require('../wsapi.js'), logger = require('../logging.js').logger, -httputils = require('../httputils.js'), -sanitize = require('../sanitize.js'); +httputils = require('../httputils.js'); exports.method = 'get'; exports.writes_db = false; exports.authed = false; -exports.args = ['email']; +exports.args = { email: 'email' }; exports.i18n = false; exports.process = function(req, res) { - var email = req.query.email; - - try { - sanitize(email).isEmail(); - } catch(e) { - var msg = "invalid arguments: " + e; - logger.warn("bad request received: " + msg); - return httputils.badRequest(res, msg); - } + var email = req.params.email; // if the email is in the staged table, we are not complete yet. // if the email is not in the staged table - diff --git a/lib/wsapi/remove_email.js b/lib/wsapi/remove_email.js index 145adf03deab484899eb96fffacfb55b1d3b0d90..87ae5a7c44ba0e40d2d708ba086e23922558e531 100644 --- a/lib/wsapi/remove_email.js +++ b/lib/wsapi/remove_email.js @@ -11,11 +11,13 @@ logger = require('../logging.js').logger; exports.method = 'post'; exports.writes_db = true; exports.authed = 'assertion'; -exports.args = ['email']; +exports.args = { + 'email': 'email' +}; exports.i18n = false; exports.process = function(req, res) { - var email = req.body.email; + var email = req.params.email; db.removeEmail(req.session.userid, email, function(error) { if (error) { diff --git a/lib/wsapi/stage_email.js b/lib/wsapi/stage_email.js index 1e9d8cdb4f7cdad18008714885ff85100b1fc512..859f1323deb18ceb02df4180a4c1504ca9bd1304 100644 --- a/lib/wsapi/stage_email.js +++ b/lib/wsapi/stage_email.js @@ -8,7 +8,6 @@ wsapi = require('../wsapi.js'), httputils = require('../httputils'), logger = require('../logging.js').logger, email = require('../email.js'), -sanitize = require('../sanitize'), config = require('../configuration'); /* Stage an email for addition to a user's account. Causes email to be sent. */ @@ -16,7 +15,14 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = true; exports.authed = 'assertion'; -exports.args = ['email','site']; +exports.args = { + email: 'email', + site: 'origin', + pass: { + type: 'password', + required: false + } +}; exports.i18n = true; exports.process = function(req, res) { @@ -24,21 +30,11 @@ exports.process = function(req, res) { // is currently NULL - this would occur in the case where this is the // first secondary address to be added to an account - // validate - try { - sanitize(req.body.email).isEmail(); - sanitize(req.body.site).isOrigin(); - } catch(e) { - var msg = "invalid arguments: " + e; - logger.warn("bad request received: " + msg); - return httputils.badRequest(res, msg); - } - - db.lastStaged(req.body.email, function (err, last) { + db.lastStaged(req.params.email, function (err, last) { if (err) return wsapi.databaseDown(res, err); if (last && (new Date() - last) < config.get('min_time_between_emails_ms')) { - logger.warn('throttling request to stage email address ' + req.body.email + ', only ' + + logger.warn('throttling request to stage email address ' + req.params.email + ', only ' + ((new Date() - last) / 1000.0) + "s elapsed"); return httputils.throttled(res, "Too many emails sent to that address, try again later."); } @@ -46,13 +42,12 @@ exports.process = function(req, res) { db.checkAuth(req.session.userid, function(err, hash) { var needs_password = !hash; - if (!err && needs_password && !req.body.pass) { + if (!err && needs_password && !req.params.pass) { err = "user must choose a password"; } - if (!err && !needs_password && req.body.pass) { + if (!err && !needs_password && req.params.pass) { err = "a password may not be set at this time"; } - if (!err && needs_password) err = wsapi.checkPassword(req.body.pass); if (err) { logger.info("stage of email fails: " + err); @@ -63,7 +58,7 @@ exports.process = function(req, res) { } if (needs_password) { - wsapi.bcryptPassword(req.body.pass, function(err, hash) { + wsapi.bcryptPassword(req.params.pass, function(err, hash) { if (err) { logger.warn("couldn't bcrypt password during email verification: " + err); return res.json({ success: false }); @@ -78,7 +73,7 @@ exports.process = function(req, res) { function completeStage(hash) { try { // on failure stageEmail may throw - db.stageEmail(req.session.userid, req.body.email, hash, function(err, secret) { + db.stageEmail(req.session.userid, req.params.email, hash, function(err, secret) { if (err) return wsapi.databaseDown(res, err); var langContext = wsapi.langContext(req); @@ -88,7 +83,7 @@ exports.process = function(req, res) { res.json({ success: true }); // let's now kick out a verification email! - email.sendConfirmationEmail(req.body.email, req.body.site, secret, langContext); + email.sendConfirmationEmail(req.params.email, req.params.site, secret, langContext); }); } catch(e) { // we should differentiate tween' 400 and 500 here. diff --git a/lib/wsapi/stage_reset.js b/lib/wsapi/stage_reset.js index a8aefbbcbdba61397c637ad9f41761ac0bb340ec..fda8ee6ba866b0333a4545f93365fd98b3b11e92 100644 --- a/lib/wsapi/stage_reset.js +++ b/lib/wsapi/stage_reset.js @@ -8,7 +8,6 @@ wsapi = require('../wsapi.js'), httputils = require('../httputils'), logger = require('../logging.js').logger, email = require('../email.js'), -sanitize = require('../sanitize'), config = require('../configuration'); /* First half of account creation. Stages a user account for creation. @@ -20,40 +19,24 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = true; exports.authed = false; -exports.args = ['email','site','pass']; +exports.args = { + email: 'email', + site: 'origin', + pass: 'password' +}; exports.i18n = true; exports.process = function(req, res) { - // a password *must* be supplied to this call iff the user's password - // is currently NULL - this would occur in the case where this is the - // first secondary address to be added to an account - - // validate - try { - sanitize(req.body.email).isEmail(); - sanitize(req.body.site).isOrigin(); - } catch(e) { - var msg = "invalid arguments: " + e; - logger.warn("bad request received: " + msg); - return httputils.badRequest(res, msg); - } - - var err = wsapi.checkPassword(req.body.pass); - if (err) { - logger.warn("invalid password received: " + err); - return httputils.badRequest(res, err); - } - - db.lastStaged(req.body.email, function (err, last) { + db.lastStaged(req.params.email, function (err, last) { if (err) return wsapi.databaseDown(res, err); if (last && (new Date() - last) < config.get('min_time_between_emails_ms')) { - logger.warn('throttling request to stage email address ' + req.body.email + ', only ' + + logger.warn('throttling request to stage email address ' + req.params.email + ', only ' + ((new Date() - last) / 1000.0) + "s elapsed"); return httputils.throttled(res, "Too many emails sent to that address, try again later."); } - db.emailToUID(req.body.email, function(err, uid) { + db.emailToUID(req.params.email, function(err, uid) { if (err) { logger.info("reset password fails: " + err); return res.json({ success: false }); @@ -70,7 +53,7 @@ exports.process = function(req, res) { wsapi.clearAuthenticatedUser(req.session); // now bcrypt the password - wsapi.bcryptPassword(req.body.pass, function (err, hash) { + wsapi.bcryptPassword(req.params.pass, function (err, hash) { if (err) { if (err.indexOf('exceeded') != -1) { logger.warn("max load hit, failing on auth request with 503: " + err); @@ -82,7 +65,7 @@ exports.process = function(req, res) { // on failure stageEmail may throw try { - db.stageEmail(uid, req.body.email, hash, function(err, secret) { + db.stageEmail(uid, req.params.email, hash, function(err, secret) { if (err) return wsapi.databaseDown(res, err); var langContext = wsapi.langContext(req); @@ -93,7 +76,7 @@ exports.process = function(req, res) { res.json({ success: true }); // let's now kick out a verification email! - email.sendForgotPasswordEmail(req.body.email, req.body.site, secret, langContext); + email.sendForgotPasswordEmail(req.params.email, req.params.site, secret, langContext); }); } catch(e) { // we should differentiate tween' 400 and 500 here. diff --git a/lib/wsapi/stage_reverify.js b/lib/wsapi/stage_reverify.js index 885fcb57ce7c05081dc3c8c06d70ecb16ce0110a..37036c43814b8c788e09a6e50f945db5b3ddc8d0 100644 --- a/lib/wsapi/stage_reverify.js +++ b/lib/wsapi/stage_reverify.js @@ -8,7 +8,6 @@ wsapi = require('../wsapi.js'), httputils = require('../httputils'), logger = require('../logging.js').logger, email = require('../email.js'), -sanitize = require('../sanitize'), config = require('../configuration'); /* Stage an email for re-verification (i.e. after account password reset). @@ -17,37 +16,30 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = true; exports.authed = 'assertion'; -exports.args = ['email','site']; +exports.args = { + email: 'email', + site: 'origin' +}; exports.i18n = true; exports.process = function(req, res) { - // validate - try { - sanitize(req.body.email).isEmail(); - sanitize(req.body.site).isOrigin(); - } catch(e) { - var msg = "invalid arguments: " + e; - logger.warn("bad request received: " + msg); - return httputils.badRequest(res, msg); - } - // Note, we do no throttling of emails in this case. Because this call requires // authentication, protect a user from themselves could cause more harm than good, // specifically we would be removing a user available workaround (i.e. a cosmic ray // hits our email delivery, user doesn't get an email in 30s. User tries again.) // one may only reverify an email that is owned and unverified - db.userOwnsEmail(req.session.userid, req.body.email, function(err, owned) { + db.userOwnsEmail(req.session.userid, req.params.email, function(err, owned) { if (err) return res.json({ success: false, reason: err }); if (!owned) return res.json({ success: false, reason: 'you don\'t control that email address' }); - db.emailIsVerified(req.body.email, function(err, verified) { + db.emailIsVerified(req.params.email, function(err, verified) { if (err) return res.json({ success: false, reason: err }); if (verified) return res.json({ success: false, reason: 'email is already verified' }); try { // on failure stageEmail may throw - db.stageEmail(req.session.userid, req.body.email, undefined, function(err, secret) { + db.stageEmail(req.session.userid, req.params.email, undefined, function(err, secret) { if (err) return wsapi.databaseDown(res, err); var langContext = wsapi.langContext(req); @@ -57,7 +49,7 @@ exports.process = function(req, res) { res.json({ success: true }); // let's now kick out a verification email! - email.sendConfirmationEmail(req.body.email, req.body.site, secret, langContext); + email.sendConfirmationEmail(req.params.email, req.params.site, secret, langContext); }); } catch(e) { // we should differentiate tween' 400 and 500 here. diff --git a/lib/wsapi/stage_user.js b/lib/wsapi/stage_user.js index 8498194ff1b395190c62c32bd281a4b9e620294e..9d3b6c0df1fb9b08ebbf81cb0ce717f488c10f3f 100644 --- a/lib/wsapi/stage_user.js +++ b/lib/wsapi/stage_user.js @@ -8,7 +8,6 @@ wsapi = require('../wsapi.js'), httputils = require('../httputils'), logger = require('../logging.js').logger, email = require('../email.js'), -sanitize = require('../sanitize'), config = require('../configuration'); /* First half of account creation. Stages a user account for creation. @@ -20,34 +19,21 @@ config = require('../configuration'); exports.method = 'post'; exports.writes_db = true; exports.authed = false; -exports.args = ['email','pass','site']; +exports.args = { + 'email': 'email', + 'pass': 'password', + 'site': 'origin' +}; exports.i18n = true; exports.process = function(req, res) { var langContext = wsapi.langContext(req); - // validate - try { - sanitize(req.body.email).isEmail(); - sanitize(req.body.site).isOrigin(); - if(!req.body.pass) throw "missing pass"; - } catch(e) { - var msg = "invalid arguments: " + e; - logger.warn("bad request received: " + msg); - return httputils.badRequest(res, msg); - } - - var err = wsapi.checkPassword(req.body.pass); - if (err) { - logger.warn("invalid password received: " + err); - return httputils.badRequest(res, err); - } - - db.lastStaged(req.body.email, function (err, last) { + db.lastStaged(req.params.email, function (err, last) { if (err) return wsapi.databaseDown(res, err); if (last && (new Date() - last) < config.get('min_time_between_emails_ms')) { - logger.warn('throttling request to stage email address ' + req.body.email + ', only ' + + logger.warn('throttling request to stage email address ' + req.params.email + ', only ' + ((new Date() - last) / 1000.0) + "s elapsed"); return httputils.throttled(res, "Too many emails sent to that address, try again later."); } @@ -56,7 +42,7 @@ exports.process = function(req, res) { wsapi.clearAuthenticatedUser(req.session); // now bcrypt the password - wsapi.bcryptPassword(req.body.pass, function (err, hash) { + wsapi.bcryptPassword(req.params.pass, function (err, hash) { if (err) { if (err.indexOf('exceeded') != -1) { logger.warn("max load hit, failing on auth request with 503: " + err); @@ -69,7 +55,7 @@ exports.process = function(req, res) { try { // upon success, stage_user returns a secret (that'll get baked into a url // and given to the user), on failure it throws - db.stageUser(req.body.email, hash, function(err, secret) { + db.stageUser(req.params.email, hash, function(err, secret) { if (err) return wsapi.databaseDown(res, err); // store the email being registered in the session data @@ -83,7 +69,7 @@ exports.process = function(req, res) { res.json({ success: true }); // let's now kick out a verification email! - email.sendNewUserEmail(req.body.email, req.body.site, secret, langContext); + email.sendNewUserEmail(req.params.email, req.params.site, secret, langContext); }); } catch(e) { // we should differentiate tween' 400 and 500 here. diff --git a/lib/wsapi/update_password.js b/lib/wsapi/update_password.js index d7a395c3a49a7cf4d8330bf30b703443ce0fe3de..52471aa05c45d18c5baa67ef2dde104855213395 100644 --- a/lib/wsapi/update_password.js +++ b/lib/wsapi/update_password.js @@ -12,27 +12,22 @@ bcrypt = require('../bcrypt'); exports.method = 'post'; exports.writes_db = true; exports.authed = 'password'; -exports.args = ['oldpass','newpass']; +exports.args = { + oldpass: 'password', + newpass: 'password' +}; exports.i18n = false; exports.process = function(req, res) { - var err = wsapi.checkPassword(req.body.newpass); - if (err) { - return res.json({ - success:false, - reason: err - }); - } - db.checkAuth(req.session.userid, function(err, hash) { if (err) return wsapi.databaseDown(res, err); - if (typeof hash !== 'string' || typeof req.body.oldpass !== 'string') + if (typeof hash !== 'string' || typeof req.params.oldpass !== 'string') { return res.json({ success: false }); } - bcrypt.compare(req.body.oldpass, hash, function (err, success) { + bcrypt.compare(req.params.oldpass, hash, function (err, success) { if (err) { if (err.indexOf('exceeded') != -1) { logger.warn("max load hit, failing on auth request with 503: " + err); @@ -48,22 +43,22 @@ exports.process = function(req, res) { return res.json({ success: false }); } - logger.info("updating password for email " + req.session.userid); - wsapi.bcryptPassword(req.body.newpass, function(err, hash) { + logger.info("updating password for user " + req.session.userid); + wsapi.bcryptPassword(req.params.newpass, function(err, hash) { if (err) { if (err.indexOf('exceeded') != -1) { logger.warn("max load hit, failing on auth request with 503: " + err); res.status(503); return res.json({ success: false, reason: "server is too busy" }); } - logger.error("error bcrypting password for password update for " + req.body.email, err); + logger.error("error bcrypting password for password update for user " + req.session.userid, err); return res.json({ success: false }); } db.updatePassword(req.session.userid, hash, function(err) { var success = true; if (err) { - logger.error("error updating bcrypted password for email " + req.body.email, err); + logger.error("error updating bcrypted password for user " + req.session.userid, err); wsapi.databaseDown(res, err); } else { res.json({ success: success }); diff --git a/lib/wsapi/user_creation_status.js b/lib/wsapi/user_creation_status.js index 41852b7278743be2cea352787e48fd2aaa471b72..9997ec01c8ba4d565d335b15c33077861d160632 100644 --- a/lib/wsapi/user_creation_status.js +++ b/lib/wsapi/user_creation_status.js @@ -9,11 +9,13 @@ wsapi = require('../wsapi.js'); exports.method = 'get'; exports.writes_db = false; exports.authed = false; -exports.args = ['email']; +exports.args = { + 'email': 'email' +}; exports.i18n = false; exports.process = function(req, res) { - var email = req.query.email; + var email = req.params.email; // if the user is authenticated as the user in question, we're done if (wsapi.isAuthed(req, 'assertion')) { @@ -23,7 +25,7 @@ exports.process = function(req, res) { else notAuthed(); }); } else { - notAuthed() + notAuthed(); } function notAuthed() { diff --git a/package.json b/package.json index 24a3bdf1e613fdced528e5c488f7be92f699ae34..b919e5a2f6a0adb118ac84b9552e51e44987d9aa 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "uglifycss": "0.0.5", "underscore": "1.3.1", "urlparse": "0.0.1", + "validator": "0.4.9", "winston": "0.5.6" }, "devDependencies": { diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js index 99116c22ffabfc7240808018866584c1226a2b7e..1a23e87b0644c0b73c6e8140268008886d19a011 100644 --- a/resources/static/test/mocks/xhr.js +++ b/resources/static/test/mocks/xhr.js @@ -238,7 +238,6 @@ BrowserID.Mocks.xhr = (function() { } }; - return xhr; }()); diff --git a/tests/internal-wsapi-test.js b/tests/internal-wsapi-test.js old mode 100644 new mode 100755 diff --git a/tests/password-length-test.js b/tests/password-length-test.js index dc7a2b02230b9dfac5438c093f1ab6a4369537a1..9dc384501fa94893592d5b44a6bb54f6fa7ea9a4 100755 --- a/tests/password-length-test.js +++ b/tests/password-length-test.js @@ -44,7 +44,7 @@ suite.addBatch({ }), "causes a HTTP error response": function(err, r) { assert.equal(r.code, 400); - assert.equal(r.body, "Bad Request: missing 'pass' argument"); + assert.strictEqual(JSON.parse(r.body).success, false); } }, "a password that is too short": { @@ -55,7 +55,7 @@ suite.addBatch({ }), "causes a HTTP error response": function(err, r) { assert.equal(r.code, 400); - assert.equal(r.body, "Bad Request: valid passwords are between 8 and 80 chars"); + assert.equal(JSON.parse(r.body).success, false); } }, "a password that is too long": { @@ -66,7 +66,7 @@ suite.addBatch({ }), "causes a HTTP error response": function(err, r) { assert.equal(r.code, 400); - assert.equal(r.body, "Bad Request: valid passwords are between 8 and 80 chars"); + assert.equal(JSON.parse(r.body).success, false); } }, "but a password that is just right": { diff --git a/tests/registration-status-wsapi-test.js b/tests/registration-status-wsapi-test.js index 10944b1ae2388549ddf83fad688ca8fff0996228..0a1b42a8b01aaf8deddf57248cd8f817618f3ffa 100755 --- a/tests/registration-status-wsapi-test.js +++ b/tests/registration-status-wsapi-test.js @@ -91,7 +91,7 @@ suite.addBatch({ assert.strictEqual(r.code, 400); }, "returns an error string": function (err, r) { - assert.strictEqual(r.body, "Bad Request: missing 'email' argument"); + assert.strictEqual(JSON.parse(r.body).success, false); } } }); diff --git a/tests/remove-email-test.js b/tests/remove-email-test.js new file mode 100755 index 0000000000000000000000000000000000000000..76a3ca088d9b3ce23efe3481a0776eb2af0e60fd --- /dev/null +++ b/tests/remove-email-test.js @@ -0,0 +1,181 @@ +#!/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/. */ + +require('./lib/test_env.js'); + +const assert = require('assert'), +vows = require('vows'), +start_stop = require('./lib/start-stop.js'), +wsapi = require('./lib/wsapi.js'), +email = require('../lib/email.js'), +jwcrypto = require('jwcrypto'); + +var suite = vows.describe('forgotten-email'); + +// algs +require("jwcrypto/lib/algs/ds"); +require("jwcrypto/lib/algs/rs"); + +start_stop.addStartupBatches(suite); + +// every time a new token is sent out, let's update the global +// var 'token' +var token = undefined; + +// create a new account via the api with (first address) +suite.addBatch({ + "staging an account": { + topic: wsapi.post('/wsapi/stage_user', { + email: 'first@fakeemail.com', + pass: 'firstfakepass', + site:'http://localhost:123' + }), + "works": function(err, r) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + start_stop.waitForToken(this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; + } + } +}); + +suite.addBatch({ + "create first account": { + topic: function() { + wsapi.post('/wsapi/complete_user_creation', { token: token }).call(this); + }, + "account created": function(err, r) { + assert.equal(r.code, 200); + assert.strictEqual(true, JSON.parse(r.body).success); + token = undefined; + } + } +}); + +suite.addBatch({ + "email created": { + topic: wsapi.get('/wsapi/user_creation_status', { email: 'first@fakeemail.com' } ), + "should exist": function(err, r) { + assert.strictEqual(r.code, 200); + assert.strictEqual(JSON.parse(r.body).status, "complete"); + } + } +}); + +// add a new email address to the account (second address) +suite.addBatch({ + "add a new email address to our account": { + topic: wsapi.post('/wsapi/stage_email', { + email: 'second@fakeemail.com', + site:'https://fakesite.foobar.bizbaz.uk' + }), + "works": function(err, r) { + assert.strictEqual(r.code, 200); + } + } +}); + +// wait for the token +suite.addBatch({ + "a token": { + topic: function() { + start_stop.waitForToken(this.callback); + }, + "is obtained": function (t) { + assert.strictEqual(typeof t, 'string'); + token = t; + } + } +}); + +// confirm second email email address to the account +suite.addBatch({ + "create second account": { + topic: function() { + wsapi.post('/wsapi/complete_email_confirmation', { token: token }).call(this); + }, + "account created": function(err, r) { + assert.equal(r.code, 200); + assert.strictEqual(JSON.parse(r.body).success, true); + token = undefined; + } + } +}); + +// 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(err, r) { + assert.strictEqual(JSON.parse(r.body).email_known, true); + } + }, + "second email exists": { + topic: wsapi.get('/wsapi/have_email', { email: 'second@fakeemail.com' }), + "should exist": function(err, r) { + assert.strictEqual(JSON.parse(r.body).email_known, true); + } + }, + "a random email doesn't exist": { + topic: wsapi.get('/wsapi/have_email', { email: 'third@fakeemail.com' }), + "shouldn't exist": function(err, r) { + assert.strictEqual(JSON.parse(r.body).email_known, false); + } + } +}); + +suite.addBatch({ + "list emails API": { + topic: wsapi.get('/wsapi/list_emails', {}), + "succeeds with HTTP 200" : function(err, r) { + assert.strictEqual(r.code, 200); + }, + "returns two emails": function(err, r) { + r = Object.keys(JSON.parse(r.body)); + assert.ok(r.indexOf('first@fakeemail.com') != -1); + assert.ok(r.indexOf('second@fakeemail.com') != -1); + } + } +}); + +suite.addBatch({ + "remove email": { + topic: wsapi.post('/wsapi/remove_email', { email: 'second@fakeemail.com'}), + "succeeds with HTTP 200" : function(err, r) { + assert.strictEqual(r.code, 200); + } + } +}); + +suite.addBatch({ + "list emails API": { + topic: wsapi.get('/wsapi/list_emails', {}), + "succeeds with HTTP 200" : function(err, r) { + assert.strictEqual(r.code, 200); + }, + "returns one emails": function(err, r) { + r = Object.keys(JSON.parse(r.body)); + assert.ok(r.indexOf('first@fakeemail.com') !== -1); + assert.ok(r.indexOf('second@fakeemail.com') === -1); + } + } +}); + +start_stop.addShutdownBatches(suite); + +// run or export the suite. +if (process.argv[1] === __filename) suite.run(); +else suite.export(module); diff --git a/tests/stalled-mysql-test.js b/tests/stalled-mysql-test.js index 5337ad2950c1cc19285fec08f8547ecc73e9a548..889dad843334ea4895a75cf3b40b63c81760827b 100755 --- a/tests/stalled-mysql-test.js +++ b/tests/stalled-mysql-test.js @@ -121,7 +121,7 @@ suite.addBatch({ }, "complete_email_confirmation": { topic: wsapi.post('/wsapi/complete_email_confirmation', { - token: 'bogus' + token: 'bogusbogusbogusbogusbogusbogusbogusbogusbogusbog' }), "fails with 503": function(err, r) { assert.strictEqual(r.code, 503); @@ -129,7 +129,7 @@ suite.addBatch({ }, "complete_user_creation": { topic: wsapi.post('/wsapi/complete_user_creation', { - token: 'bogus', + token: 'bogusbogusbogusbogusbogusbogusbogusbogusbogusbog', pass: 'alsobogus' }), "fails with 503": function(err, r) { @@ -138,7 +138,7 @@ suite.addBatch({ }, "email_for_token": { topic: wsapi.get('/wsapi/email_for_token', { - token: 'bogus' + token: 'bogusbogusbogusbogusbogusbogusbogusbogusbogusbog' }), "fails with 503": function(err, r) { assert.strictEqual(r.code, 503); @@ -229,7 +229,7 @@ suite.addBatch({ "cert_key": { topic: wsapi.post('/wsapi/cert_key', { email: "test@whatev.er", - pubkey: "bogus", + pubkey: JSON.stringify("bogusbogusbogusbogusbogusbogusbogusbogusbogusbogusbogus"), ephemeral: false }), "fails with 503": function(err, r) {