Skip to content
This repository has been archived by the owner on May 10, 2019. It is now read-only.

Commit

Permalink
Merge pull request #2709 from ozten/issue-2678-complete_transition
Browse files Browse the repository at this point in the history
First stab at updating cert_key and adding complete_transition wsapis
  • Loading branch information
ozten committed Nov 13, 2012
2 parents b7d6776 + 4ae16d9 commit fe194fd
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 26 deletions.
2 changes: 2 additions & 0 deletions lib/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ exports.onReady = function(f) {
'isStaged',
'lastStaged',
'listEmails',
'emailLastUsedAs',
'ping',
'userKnown',
'userOwnsEmail',
Expand All @@ -107,6 +108,7 @@ exports.onReady = function(f) {
'completeCreateUser',
'completeConfirmEmail',
'completePasswordReset',
'updateEmailLastUsedAs',
'removeEmail',
'cancelAccount',
'updatePassword',
Expand Down
57 changes: 45 additions & 12 deletions lib/db/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,25 @@ const ESC = JSON.stringify;
var dbPath = path.join(configuration.get('var_path'), "authdb.json");

/* The JSON database. The structure is thus:
* [
* {
* id: <numerical user id>
* password: "somepass",
* lastPasswordReset: 123456, (seconds-since-epoch, integer)
* emails: {
* "lloyd@hilaiel.com": {
* type: 'secondary'
* }
* }
* }
* ]
* {
* "users":[
* {
* "id": <numerical user id>,
* "password": <string password and salt info>,
* "lastPasswordReset": <seconds-since-epoch, integer>,
* "emails":{
* "syncer@somehost.com":{
* "type": <string secondary|primary>,
* "lastUsedAs": <string secondary|primary>,
* "verified":<boolean>
* }
* }
* }
* ],
* "stagedEmails":{},
* "staged":{},
* "idp":{}
* }
*/

function now() { return Math.floor(new Date().getTime() / 1000); }
Expand Down Expand Up @@ -472,6 +479,32 @@ exports.listEmails = function(uid, cb) {
});
};

exports.emailLastUsedAs = function(email, cb) {
sync();
var emails = jsel.match("." + ESC(email), db.users);
process.nextTick(function () {
if (!emails || emails.length !== 1) {
cb('emailLastUsedAs Expected 1 row, got ' + emails.length + ' for ' + email);
return;
}
cb(null, emails[0].lastUsedAs);
});
};

const typeEnum = ['primary', 'secondary'];

exports.updateEmailLastUsedAs = function(email, type, cb) {
if (typeEnum.indexOf(type) === -1) return cb && cb('Invalid type for updating email.lastUsedAs');
sync();
var emails = jsel.match("." + ESC(email), db.users);
process.nextTick(function () {
emails[0].lastUsedAs = type;
flush();
cb(null);
});
};


exports.removeEmail = function(authenticated_user, email, cb) {
sync();
var m = jsel.match(":has(.id:expr(x=" + ESC(authenticated_user) + ")) .emails:has(."+ESC(email)+")", db.users);
Expand Down
38 changes: 31 additions & 7 deletions lib/db/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

/*
* The Schema:
* +--- email -------+
* +--- user --------------------+ |*int id |
* |*int id |<-----|*int user |
* | string passwd | |*string address |
* | timestamp lastPasswordReset | | enum type |
* +-----------------------------+ | bool verified |
* +-----------------+
* +--- email ---------+
* +--- user --------------------+ |*int id |
* |*int id |<-----|*int user |
* | string passwd | |*string address |
* | timestamp lastPasswordReset | | enum lastUsedAs |
* +-----------------------------+ | bool verified |
* +-------------------+
*
*
* +------ staged ----------+
Expand Down Expand Up @@ -591,6 +591,30 @@ exports.listEmails = function(uid, cb) {
});
};

exports.emailLastUsedAs = function(email, cb) {
client.query('SELECT lastUsedAs FROM email WHERE address = ?', [email], function(err, rows) {
if (err) {
cb(err);
} else if (rows.length !== 1) {
cb('emailLastUsedAs Expected 1 row, got ' + rows.length + ' for ' + email);
} else {
cb(null, rows[0].lastUsedAs);
}
});
};

const typeEnum = ['primary', 'secondary'];

exports.updateEmailLastUsedAs = function(email, type, cb) {
if (typeEnum.indexOf(type) === -1) {
process.nextTick(function () {
cb ('Invalid type for updating email.lastUsedAs');
});
} else {
client.query('UPDATE email SET lastUsedAs = ? WHERE address = ?', [type, email], cb);
}
};

exports.removeEmail = function(authenticated_user, email, cb) {
exports.userOwnsEmail(authenticated_user, email, function(err, ok) {
if (err) return cb(err);
Expand Down
1 change: 1 addition & 0 deletions lib/primary.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ exports.checkSupport = function(principalDomain, cb) {
});
};

exports.emailRegex = /\@(.*)$/;

exports.getPublicKey = function(domain, cb) {
exports.checkSupport(domain, function(err, r) {
Expand Down
6 changes: 1 addition & 5 deletions lib/wsapi/address_info.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,8 @@ exports.args = {
};
exports.i18n = false;

const emailRegex = /\@(.*)$/;

exports.process = function(req, res) {
// parse out the domain from the email
var m = emailRegex.exec(req.params.email);

var m = primary.emailRegex.exec(req.params.email);
primary.checkSupport(m[1], function(err, r) {
if (err) {
logger.info('"' + m[1] + '" primary support is misconfigured, falling back to secondary: ' + err);
Expand Down
4 changes: 3 additions & 1 deletion lib/wsapi/cert_key.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ exports.process = function(req, res) {
// 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);
httputils.serverError(res, "can't contact keysigner");
return;
}
db.updateEmailLastUsedAs(req.params.email, 'secondary', function (err) {
if (err) logger.error("cert_key unable to update email.lastUsedAs in the database");
});
});
});
});
Expand Down
52 changes: 52 additions & 0 deletions lib/wsapi/complete_transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* 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/. */

const
db = require('../db.js'),
httputils = require('../httputils'),
logger = require('../logging.js').logger,
primary = require('../primary.js'),
wsapi = require('../wsapi.js');

// Updates DB state regarding how the user has last used an email
// Needed for when an email address goes from Secondary to Primary.
// If an email address goes from Primary to Secondary, the update
// to the db is made in the cert_key wsapi call.
// complete_transition wsapi exists, only because there is no good
// API call to piggyback on. If we ever add another POST, we can
// push this behavior into that new wsapi.

exports.method = 'post';
exports.writes_db = true;
exports.authed = 'assertion';
exports.args = {
'email': 'email'
};
exports.i18n = false;

exports.process = function (req, res) {
var email = req.params.email;
db.userOwnsEmail(req.session.userid, email, function(err, owned) {
if (err) return wsapi.databaseDown(res, err);
// not same account? big fat error
if (!owned) return httputils.badRequest(res, "that email does not belong to you");
var domain = primary.emailRegex.exec(email)[1];

primary.checkSupport(domain, function (err, r) {
var notPrimary = false;
if (err) {
logger.error('"' + domain + '" primary support is misconfigured, falling back to secondary: ' + err);
notPrimary = true;
} else if (r && r.urls) {
db.updateEmailLastUsedAs(email, 'primary', function (err) {
if (err) return httputils.serverError(res, "Unable to update database");
return res.json({success: true});
});
} else {
notPrimary = true;
}
if (notPrimary) return httputils.badRequest(res, "email is not a primary");
});
});
};
19 changes: 18 additions & 1 deletion tests/cert-emails-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ vows = require('vows'),
start_stop = require('./lib/start-stop.js'),
wsapi = require('./lib/wsapi.js'),
ca = require('../lib/keysigner/ca.js'),
db = require('../lib/db.js'),
jwcrypto = require("jwcrypto");

var suite = vows.describe('cert-emails');
Expand Down Expand Up @@ -139,6 +140,23 @@ suite.addBatch({
assert.equal(certs_and_assertion.certificates[0].split(".").length, 3);
assert.equal(certs_and_assertion.assertion.split(".").length, 3);
},
},
"after a short wait": {
// In practise, db.emailLastUsedAs is sometimes called before
// db.updateEmailLastUsedAs has been called by cert_key wsapi...
topic: function (err, r) {
setTimeout(this.callback, 500);
},
"email table lastUsedAs updated": {
topic: function(err, certs_and_assertion) {
// TODO used listEmails here, once that work is done
db.emailLastUsedAs('syncer@somehost.com', this.callback);
},
"cert_key records a secondary": function (err, lastUsedAs) {
assert.isNull(err);
assert.equal(lastUsedAs, 'secondary');
}
}
}
},
"cert key invoked proper arguments but incorrect email address": {
Expand All @@ -155,7 +173,6 @@ suite.addBatch({
}
},
});

start_stop.addShutdownBatches(suite);

// run or export the suite.
Expand Down
102 changes: 102 additions & 0 deletions tests/complete-transition-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/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'),
db = require('../lib/db.js'),
email = require('../lib/email.js'),
jwcrypto = require('jwcrypto'),
primary = require('./lib/primary.js'),
start_stop = require('./lib/start-stop.js'),
vows = require('vows'),
wsapi = require('./lib/wsapi.js');

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;

const TEST_DOMAIN = 'example.domain',
TEST_EMAIL = 'testuser@' + TEST_DOMAIN,
TEST_ORIGIN = 'http://127.0.0.1:10002';

var primaryUser = new primary({
email: TEST_EMAIL,
domain: TEST_DOMAIN
});

suite.addBatch({
"set things up": {
topic: function() {
primaryUser.setup(this.callback);
},
"works": function() {
// nothing to do here
}
}
});

// now let's generate an assertion using this user
suite.addBatch({
"generating an assertion": {
topic: function() {
primaryUser.getAssertion(TEST_ORIGIN, this.callback);
},
"succeeds": function(err, r) {
assert.isString(r);
},
"and logging in with the assertion succeeds": {
topic: function(err, assertion) {
wsapi.post('/wsapi/auth_with_assertion', {
assertion: assertion,
ephemeral: true
}).call(this);
},
"works": function(err, r) {
var resp = JSON.parse(r.body);
assert.isObject(resp);
assert.isTrue(resp.success);
}
}
}
});


suite.addBatch({
"Mark a transition as having been seen": {
topic: wsapi.post('/wsapi/complete_transition', {
email: TEST_EMAIL
}),
"request successful": function(err, r) {
assert.equal(r.code, 200);
assert.strictEqual(true, JSON.parse(r.body).success);
token = undefined;
},
"db updated": {
topic: function () {
db.emailLastUsedAs(TEST_EMAIL, this.callback);
},
"lastUseAs updated": function (err, lastUsedAs) {
assert.isNull(err);
assert.equal(lastUsedAs, 'primary');
}
}
}
});

start_stop.addShutdownBatches(suite);

// run or export the suite.
if (process.argv[1] === __filename) suite.run();
else suite.export(module);

0 comments on commit fe194fd

Please sign in to comment.