diff --git a/browserid/lib/db_json.js b/browserid/lib/db_json.js index 716921a0eb41883abdd9a78e30c586bb073dfadb..b4518ac95714f0bf27c2239a39594bd599359fe5 100644 --- a/browserid/lib/db_json.js +++ b/browserid/lib/db_json.js @@ -179,6 +179,10 @@ exports.addKeyToEmail = function(existing_email, email, pubkey, cb) { } var m = jsel.match("object:has(.address:val(" + ESC(email) + ")) > .keys", db[userID].emails); + + if (jsel.match(".key:val(" + ESC(pubkey) + ")", m).length > 0) { + return cb("cannot set a key that is already known"); + } var kobj = { key: pubkey, diff --git a/browserid/lib/db_mysql.js b/browserid/lib/db_mysql.js index a80c337561a1e11b175ec1d8a33923a17243d186..19d14a542c73496c8fb8870ad5b5abf6eb83eb4d 100644 --- a/browserid/lib/db_mysql.js +++ b/browserid/lib/db_mysql.js @@ -282,13 +282,26 @@ exports.emailsBelongToSameAccount = function(lhs, rhs, cb) { function addKeyToEmailRecord(emailId, pubkey, cb) { client.query( - // XXX: 2 weeks is wrong, but then so is keypairs. - "INSERT INTO pubkey(email, content, expiry) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 2 WEEK))", + "SELECT COUNT(*) AS n FROM pubkey WHERE email = ? AND content = ?", [ emailId, pubkey ], - function(err, info) { - if (err) logUnexpectedError(err); - // smash null into undefined. - cb(err ? err : undefined); + function(err, rows) { + if (err) { + logUnexpectedError(err); + return cb(err); + } + if (rows[0].n > 0) { + return cb("cannot set a key that is already known"); + } + + client.query( + // XXX: 2 weeks is wrong, but then so is keypairs. + "INSERT INTO pubkey(email, content, expiry) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 2 WEEK))", + [ emailId, pubkey ], + function(err, info) { + if (err) logUnexpectedError(err); + // smash null into undefined. + cb(err ? err : undefined); + }); }); } diff --git a/browserid/lib/wsapi.js b/browserid/lib/wsapi.js index 015e4553b745b46ebb4ff3ac4b63b47ddcf3d624..9f8840e5b63cb7e98d6ea7dc63eea1347e3b7492 100644 --- a/browserid/lib/wsapi.js +++ b/browserid/lib/wsapi.js @@ -275,14 +275,14 @@ function setup(app) { app.post('/wsapi/set_key', checkAuthed, checkParams(["email", "pubkey"]), function (req, resp) { db.emailsBelongToSameAccount(req.session.authenticatedUser, req.body.email, function(sameAccount) { // not same account? big fat error - if (!sameAccount) { - httputils.badRequest(resp, "that email does not belong to you"); - } else { - // same account, we add the key - db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) { - resp.json(rv); - }); - } + if (!sameAccount) return httputils.badRequest(resp, "that email does not belong to you"); + + // same account, we add the key + db.addKeyToEmail(req.session.authenticatedUser, req.body.email, req.body.pubkey, function (rv) { + // addKeyToEmail returns errors as strings, and undefined on success. + if (rv) logger.warn("set_key WSAPI call failed to add key: " + rv.toString()); + resp.json(rv === undefined); + }); }); }); diff --git a/browserid/tests/set-key-wsapi-test.js b/browserid/tests/set-key-wsapi-test.js new file mode 100755 index 0000000000000000000000000000000000000000..b04579c6860ad7f9d129121b711c07def98245cc --- /dev/null +++ b/browserid/tests/set-key-wsapi-test.js @@ -0,0 +1,123 @@ +#!/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 ***** */ + +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'); + +var suite = vows.describe('forgotten-email'); + +// disable vows (often flakey?) async error behavior +suite.options.error = false; + +start_stop.addStartupBatches(suite); + +// ever time a new token is sent out, let's update the global +// var 'token' +var token = undefined; +email.setInterceptor(function(email, site, secret) { token = secret; }); + +// create a new account via the api with (first address) +suite.addBatch({ + "stage an account": { + topic: wsapi.post('/wsapi/stage_user', { + email: 'setkeyabuser@somehost.com', + pass: 'fakepass', + pubkey: 'fakekey', + site:'fakesite.com' + }), + "yields a sane token": function(r, err) { + assert.strictEqual(typeof token, 'string'); + } + } +}); + +suite.addBatch({ + "verifying account ownership": { + topic: function() { + wsapi.get('/wsapi/prove_email_ownership', { token: token }).call(this); + }, + "works": function(r, err) { + assert.equal(r.code, 200); + assert.strictEqual(true, JSON.parse(r.body)); + } + } +}); + +suite.addBatch({ + "calling registration_status after a registration is complete": { + topic: wsapi.get("/wsapi/registration_status"), + "yields a HTTP 200": function (r, err) { + assert.strictEqual(r.code, 200); + }, + "returns a json encoded string - `complete`": function (r, err) { + assert.strictEqual(JSON.parse(r.body), "complete"); + } + } +}); + +suite.addBatch({ + "setting a key that is already set": { + topic: wsapi.post('/wsapi/set_key', { + email: 'setkeyabuser@somehost.com', + pubkey: 'fakekey' + }), + "fails with a false return0" : function(r, err) { + assert.strictEqual(r.code, 200); + assert.strictEqual(r.body, 'false'); + } + }, + "setting a key that belongs to an email you do not own": { + topic: wsapi.post('/wsapi/set_key', { + email: 'unowned@somehost.com', + pubkey: 'newfakekey' + }), + "fails with a HTTP 400 code" : function(r, err) { + assert.strictEqual(r.code, 400); + } + } +}); + +start_stop.addShutdownBatches(suite); + +// run or export the suite. +if (process.argv[1] === __filename) suite.run(); +else suite.export(module);