2013-05-16 00:42:25 +04:00
|
|
|
/* 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/. */
|
|
|
|
|
2013-05-15 03:23:36 +04:00
|
|
|
const Hapi = require('hapi');
|
2013-05-17 04:13:01 +04:00
|
|
|
const fs = require('fs');
|
2013-05-16 01:08:03 +04:00
|
|
|
const CC = require('compute-cluster');
|
2013-06-18 21:38:19 +04:00
|
|
|
const config = require('../lib/config').root();
|
2013-05-15 03:23:36 +04:00
|
|
|
|
2013-05-16 01:08:03 +04:00
|
|
|
const hour = 1000 * 60 * 60;
|
2013-07-02 22:44:28 +04:00
|
|
|
const T = Hapi.types;
|
2013-05-15 03:23:36 +04:00
|
|
|
|
2013-05-16 01:08:03 +04:00
|
|
|
var cc = new CC({ module: __dirname + '/sign.js' });
|
2013-05-16 03:52:28 +04:00
|
|
|
|
|
|
|
var account = require('../lib/account');
|
2013-05-15 03:23:36 +04:00
|
|
|
|
|
|
|
var routes = [
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
path: '/.well-known/browserid',
|
|
|
|
config: {
|
|
|
|
handler: wellKnown
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
path: '/sign_in.html',
|
|
|
|
config: {
|
|
|
|
handler: {
|
|
|
|
file: './sign_in.html'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
method: 'GET',
|
|
|
|
path: '/provision.html',
|
|
|
|
config: {
|
|
|
|
handler: {
|
|
|
|
file: './provision.html'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2013-05-16 00:42:25 +04:00
|
|
|
{
|
|
|
|
method: 'POST',
|
|
|
|
path: '/create',
|
|
|
|
config: {
|
2013-07-02 22:44:28 +04:00
|
|
|
description:
|
|
|
|
"Creates an account associated with an email address, " +
|
|
|
|
"passing along SRP information (salt and verifier) " +
|
|
|
|
"and a wrapped key (used for class B data storage).",
|
|
|
|
tags: ["srp", "account"],
|
2013-05-16 00:42:25 +04:00
|
|
|
handler: create,
|
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-02 22:44:28 +04:00
|
|
|
email: T.String().email().required(),
|
|
|
|
verifier: T.String().required(),
|
|
|
|
salt: T.String().required(),
|
|
|
|
params: T.Object(), // TODO: what are these?
|
|
|
|
wrapKb: T.String() // TODO: required?
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2013-05-15 03:23:36 +04:00
|
|
|
{
|
|
|
|
method: 'POST',
|
|
|
|
path: '/sign',
|
|
|
|
config: {
|
|
|
|
handler: sign,
|
2013-07-03 01:16:40 +04:00
|
|
|
auth: {
|
|
|
|
strategy: 'hawk',
|
|
|
|
payload: 'required'
|
|
|
|
},
|
2013-07-02 22:44:28 +04:00
|
|
|
tags: ["account"],
|
2013-05-15 03:23:36 +04:00
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-03 01:16:40 +04:00
|
|
|
publicKey: Hapi.types.String().required(),
|
|
|
|
duration: Hapi.types.Number().integer().min(0).max(24 * hour).required()
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
method: 'POST',
|
2013-05-16 03:52:28 +04:00
|
|
|
path: '/startLogin',
|
2013-05-15 03:23:36 +04:00
|
|
|
config: {
|
2013-07-02 22:44:28 +04:00
|
|
|
description:
|
|
|
|
"Begins an SRP login for the supplied email address, " +
|
|
|
|
"returning the temporary sessionId and parameters for " +
|
|
|
|
"key stretching and the SRP protocol for the client.",
|
|
|
|
tags: ["srp", "account"],
|
2013-05-16 03:52:28 +04:00
|
|
|
handler: startLogin,
|
2013-05-15 03:23:36 +04:00
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-02 22:44:28 +04:00
|
|
|
email: T.String().email().required()
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
schema: {
|
|
|
|
sessionId: T.String(),
|
|
|
|
stretch: T.Object({
|
|
|
|
salt: T.String()
|
|
|
|
}),
|
|
|
|
srp: T.Object({
|
|
|
|
N_bits: T.Number(), // number of bits for prime
|
|
|
|
alg: T.String(), // hash algorithm (sha256)
|
|
|
|
s: T.String(), // salt
|
|
|
|
B: T.String() // server's public key value
|
|
|
|
})
|
|
|
|
}
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
method: 'POST',
|
|
|
|
path: '/finishLogin',
|
|
|
|
config: {
|
2013-07-02 22:44:28 +04:00
|
|
|
description:
|
|
|
|
"Finishes the SRP dance, with the client providing " +
|
|
|
|
"proof-of-knownledge of the password and receiving " +
|
|
|
|
"the bundle encrypted with the shared key.",
|
|
|
|
tags: ["srp", "account"],
|
2013-05-16 00:42:25 +04:00
|
|
|
handler: finishLogin,
|
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-02 22:44:28 +04:00
|
|
|
sessionId: T.String().required(),
|
|
|
|
password: T.String().without('A'),
|
|
|
|
A: T.String().without('password').with('M'),
|
|
|
|
M: T.String().with('A')
|
|
|
|
},
|
|
|
|
response: {
|
|
|
|
schema: {
|
|
|
|
bundle: T.String().without('kA').without('wrapKb'),
|
|
|
|
accountToken: T.String(),
|
|
|
|
kA: T.String().without('bundle'),
|
|
|
|
wrapKb: T.String().without('bundle')
|
|
|
|
}
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-05-17 04:13:01 +04:00
|
|
|
},
|
2013-05-25 02:58:08 +04:00
|
|
|
{
|
|
|
|
method: 'POST',
|
|
|
|
path: '/resetToken',
|
|
|
|
config: {
|
2013-07-02 22:44:28 +04:00
|
|
|
tags: ["account"],
|
2013-05-25 02:58:08 +04:00
|
|
|
handler: getResetToken,
|
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-02 22:44:28 +04:00
|
|
|
accountToken: T.String().required()
|
2013-05-25 02:58:08 +04:00
|
|
|
},
|
|
|
|
response: {
|
|
|
|
schema: {
|
2013-07-02 22:44:28 +04:00
|
|
|
resetToken: T.String().required()
|
2013-05-25 02:58:08 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
method: 'POST',
|
|
|
|
path: '/resetPassword',
|
|
|
|
config: {
|
2013-07-02 22:44:28 +04:00
|
|
|
tags: ["account"],
|
2013-05-25 02:58:08 +04:00
|
|
|
handler: resetPassword,
|
|
|
|
validate: {
|
|
|
|
payload: {
|
2013-07-02 22:44:28 +04:00
|
|
|
resetToken: T.String().required(),
|
|
|
|
verifier: T.String().required(),
|
|
|
|
params: T.Object(),
|
|
|
|
wrapKb: T.String()
|
2013-05-25 02:58:08 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2013-05-15 03:23:36 +04:00
|
|
|
];
|
|
|
|
|
|
|
|
function wellKnown(request) {
|
|
|
|
request.reply({
|
2013-06-18 03:36:52 +04:00
|
|
|
'public-key': fs.readFileSync(config.publicKeyFile),
|
2013-05-15 03:23:36 +04:00
|
|
|
'authentication': '/sign_in.html',
|
|
|
|
'provisioning': '/provision.html'
|
2013-05-16 01:08:03 +04:00
|
|
|
});
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
|
2013-05-16 00:42:25 +04:00
|
|
|
function create(request) {
|
2013-05-16 03:52:28 +04:00
|
|
|
account.create(
|
|
|
|
request.payload,
|
2013-07-02 22:44:28 +04:00
|
|
|
function (err) {
|
2013-05-16 00:42:25 +04:00
|
|
|
if (err) {
|
2013-05-16 03:52:28 +04:00
|
|
|
request.reply(err);
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
//TODO do stuff
|
2013-05-16 01:08:03 +04:00
|
|
|
request.reply('ok');
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-16 01:08:03 +04:00
|
|
|
);
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
|
2013-05-15 03:23:36 +04:00
|
|
|
function sign(request) {
|
2013-07-03 01:16:40 +04:00
|
|
|
account.getUser(
|
|
|
|
request.auth.credentials.uid,
|
|
|
|
function (err, user) {
|
|
|
|
if (err) { return request.reply(Hapi.error.internal('Unable to sign certificate', err)); }
|
|
|
|
cc.enqueue(
|
|
|
|
{
|
|
|
|
email: user.email,
|
|
|
|
publicKey: request.payload.publicKey,
|
|
|
|
duration: request.payload.duration
|
|
|
|
},
|
|
|
|
function (err, result) {
|
|
|
|
if (err || result.err) {
|
|
|
|
request.reply(Hapi.error.internal('Unable to sign certificate', err || result.err));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
request.reply(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
2013-05-16 01:08:03 +04:00
|
|
|
);
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
|
2013-05-16 03:52:28 +04:00
|
|
|
function startLogin(request) {
|
|
|
|
|
|
|
|
account.startLogin(
|
2013-05-15 03:23:36 +04:00
|
|
|
request.payload.email,
|
2013-05-16 03:52:28 +04:00
|
|
|
function (err, result) {
|
2013-05-15 03:23:36 +04:00
|
|
|
if (err) {
|
2013-05-16 03:52:28 +04:00
|
|
|
request.reply(err);
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
else {
|
2013-05-16 03:52:28 +04:00
|
|
|
request.reply(result);
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-16 01:08:03 +04:00
|
|
|
);
|
2013-05-16 03:52:28 +04:00
|
|
|
|
2013-05-15 03:23:36 +04:00
|
|
|
}
|
|
|
|
|
2013-05-16 00:42:25 +04:00
|
|
|
function finishLogin(request) {
|
2013-05-16 03:52:28 +04:00
|
|
|
|
2013-06-25 04:21:02 +04:00
|
|
|
function respond(err, result) {
|
|
|
|
if (err) {
|
|
|
|
request.reply(err);
|
2013-05-16 03:52:28 +04:00
|
|
|
}
|
2013-06-25 04:21:02 +04:00
|
|
|
else {
|
|
|
|
request.reply(result);
|
|
|
|
}
|
|
|
|
}
|
2013-05-16 03:52:28 +04:00
|
|
|
|
2013-06-25 04:21:02 +04:00
|
|
|
if (request.payload.password) {
|
|
|
|
account.finishLoginWithPassword(
|
|
|
|
request.payload.sessionId,
|
|
|
|
request.payload.password,
|
|
|
|
respond
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
account.finishLoginWithSRP(
|
|
|
|
request.payload.sessionId,
|
|
|
|
request.payload.A,
|
|
|
|
request.payload.M,
|
|
|
|
respond
|
|
|
|
);
|
|
|
|
}
|
2013-05-16 00:42:25 +04:00
|
|
|
}
|
|
|
|
|
2013-05-25 02:58:08 +04:00
|
|
|
function getResetToken(request) {
|
|
|
|
|
|
|
|
account.getResetToken(
|
|
|
|
request.payload.accountToken,
|
|
|
|
function (err, result) {
|
|
|
|
if (err) {
|
|
|
|
request.reply(err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
request.reply(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function resetPassword(request) {
|
|
|
|
account.resetPassword(
|
|
|
|
request.payload.resetToken,
|
|
|
|
request.payload,
|
|
|
|
function (err) {
|
|
|
|
if (err) {
|
|
|
|
request.reply(err);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
request.reply('ok');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-05-15 03:23:36 +04:00
|
|
|
module.exports = {
|
|
|
|
routes: routes
|
2013-05-16 01:08:03 +04:00
|
|
|
};
|