This commit is contained in:
Danny Coates 2014-01-03 18:11:33 -08:00
Родитель e973ffed85
Коммит e285cb8ac3
16 изменённых файлов: 2 добавлений и 1589 удалений

Просмотреть файл

@ -40,10 +40,6 @@ function main() {
var signer = new CC({ module: __dirname + '/signer.js' })
signer.on('error', function () {}) // don't die
// client_heklper compute-cluster
var clientHelper = new CC({ module: __dirname + '/client_helper.js' })
clientHelper.on('error', function () {}) // don't die
var Server = require('../server')
var server = null
// TODO: send to the SMTP server directly. In the future this may change
@ -58,11 +54,9 @@ function main() {
config.db.backend,
log,
Token.error,
Token.AuthToken,
Token.SessionToken,
Token.KeyFetchToken,
Token.AccountResetToken,
Token.SrpToken,
Token.PasswordForgotToken,
Token.PasswordChangeToken
)
@ -86,7 +80,7 @@ function main() {
function (backends) {
var db = backends[0]
var noncedb = backends[1]
var routes = require('../routes')(log, error, serverPublicKey, signer, clientHelper, db, mailer, Token, config)
var routes = require('../routes')(log, error, serverPublicKey, signer, db, mailer, config)
server = Server.create(log, error, config, routes, db, noncedb, i18n)
server.start(

Просмотреть файл

@ -7,11 +7,9 @@ var P = require('p-promise')
module.exports = function (
log,
error,
AuthToken,
SessionToken,
KeyFetchToken,
AccountResetToken,
SrpToken,
PasswordForgotToken,
PasswordChangeToken
) {
@ -20,8 +18,6 @@ module.exports = function (
this.sessionTokens = {}
this.keyFetchTokens = {}
this.accountResetTokens = {}
this.authTokens = {}
this.srpTokens = {}
this.passwordForgotTokens = {}
this.passwordChangeTokens = {}
this.accounts = {}
@ -106,18 +102,6 @@ module.exports = function (
.then(saveTo(this.accountResetTokens))
}
Heap.prototype.createAuthToken = function (srpToken) {
log.trace({ op: 'Heap.createAuthToken', uid: srpToken && srpToken.uid })
return AuthToken.create(srpToken)
.then(saveTo(this.authTokens))
}
Heap.prototype.createSrpToken = function (emailRecord) {
log.trace({ op: 'Heap.createSrpToken', uid: emailRecord && emailRecord.uid })
return SrpToken.create(emailRecord)
.then(saveTo(this.srpTokens))
}
Heap.prototype.createPasswordForgotToken = function (emailRecord) {
log.trace({ op: 'Heap.createPasswordForgotToken', uid: emailRecord && emailRecord.uid })
return PasswordForgotToken.create(emailRecord)
@ -198,23 +182,6 @@ module.exports = function (
return P(accountResetToken)
}
Heap.prototype.authToken = function (id) {
log.trace({ op: 'Heap.authToken', id: id })
var authToken = this.authTokens[id.toString('hex')]
if (!authToken) { return P.reject(error.invalidToken()) }
var account = this.accounts[authToken.uid.toString('hex')]
if (!account) { return P.reject(error.unknownAccount()) }
authToken.verified = account.verified
return P(authToken)
}
Heap.prototype.srpToken = function (id) {
log.trace({ op: 'Heap.srpToken', id: id })
var srpToken = this.srpTokens[id.toString('hex')]
if (!srpToken) { return P.reject(error.invalidToken()) }
return P(srpToken)
}
Heap.prototype.passwordForgotToken = function (id) {
log.trace({ op: 'Heap.passwordForgotToken', id: id })
var passwordForgotToken = this.passwordForgotTokens[id.toString('hex')]
@ -261,8 +228,6 @@ module.exports = function (
if (!account) { return P.reject(error.unknownAccount()) }
deleteUid(account.uid, this.sessionTokens)
deleteUid(account.uid, this.keyFetchTokens)
deleteUid(account.uid, this.authTokens)
deleteUid(account.uid, this.srpTokens)
deleteUid(account.uid, this.accountResetTokens)
deleteUid(account.uid, this.passwordForgotTokens)
delete this.emailRecords[account.email]
@ -309,30 +274,6 @@ module.exports = function (
return P(true)
}
Heap.prototype.deleteAuthToken = function (authToken) {
log.trace(
{
op: 'Heap.deleteAuthToken',
id: authToken && authToken.id,
uid: authToken && authToken.uid
}
)
delete this.authTokens[authToken.id]
return P(true)
}
Heap.prototype.deleteSrpToken = function (srpToken) {
log.trace(
{
op: 'Heap.deleteSrpToken',
id: srpToken && srpToken.id,
uid: srpToken && srpToken.uid
}
)
delete this.srpTokens[srpToken.id]
return P(true)
}
Heap.prototype.deletePasswordForgotToken = function (passwordForgotToken) {
log.trace(
{
@ -371,67 +312,17 @@ module.exports = function (
account.passwordForgotToken = null
deleteUid(account.uid, this.sessionTokens)
deleteUid(account.uid, this.keyFetchTokens)
deleteUid(account.uid, this.authTokens)
deleteUid(account.uid, this.srpTokens)
deleteUid(account.uid, this.accountResetTokens)
deleteUid(account.uid, this.passwordForgotTokens)
return P(true)
}
Heap.prototype.authFinish = function (srpToken) {
log.trace({ op: 'Heap.authFinish', uid: srpToken && srpToken.uid })
return this.deleteSrpToken(srpToken)
.then(this.createAuthToken.bind(this, srpToken))
}
Heap.prototype.createSession = function (authToken) {
log.trace({ op: 'Heap.createSession', uid: authToken && authToken.uid })
return this.deleteAuthToken(authToken)
.then(
function () {
return P.all([
this.createKeyFetchToken(authToken),
this.createSessionToken(authToken)
])
}.bind(this)
)
.then(
function (tokens) {
return {
keyFetchToken: tokens[0],
sessionToken: tokens[1]
}
}
)
}
Heap.prototype.verifyEmail = function (account) {
log.trace({ op: 'Heap.verifyEmail', uid: account && account.uid })
account.verified = true
return P(true)
}
Heap.prototype.createPasswordChange = function (authToken) {
log.trace({ op: 'Heap.createPasswordChange', uid: authToken && authToken.uid })
return this.deleteAuthToken(authToken)
.then(
function () {
return P.all([
this.createKeyFetchToken(authToken),
this.createAccountResetToken(authToken)
])
}.bind(this)
)
.then(
function (tokens) {
return {
keyFetchToken: tokens[0],
accountResetToken: tokens[1]
}
}
)
}
Heap.prototype.forgotPasswordVerified = function (passwordForgotToken) {
log.trace({ op: 'Heap.forgotPasswordVerified', uid: passwordForgotToken && passwordForgotToken.uid })
return this.deletePasswordForgotToken(passwordForgotToken)

Просмотреть файл

@ -6,11 +6,9 @@ module.exports = function (
backend,
log,
error,
AuthToken,
SessionToken,
KeyFetchToken,
AccountResetToken,
SrpToken,
PasswordForgotToken,
PasswordChangeToken) {
@ -18,11 +16,9 @@ module.exports = function (
return require('./mysql')(
log,
error,
AuthToken,
SessionToken,
KeyFetchToken,
AccountResetToken,
SrpToken,
PasswordForgotToken,
PasswordChangeToken
)
@ -31,11 +27,9 @@ module.exports = function (
return require('./heap')(
log,
error,
AuthToken,
SessionToken,
KeyFetchToken,
AccountResetToken,
SrpToken,
PasswordForgotToken,
PasswordChangeToken
)

Просмотреть файл

@ -9,11 +9,9 @@ var schema = require('fs').readFileSync(__dirname + '/schema.sql', { encoding: '
module.exports = function (
log,
error,
AuthToken,
SessionToken,
KeyFetchToken,
AccountResetToken,
SrpToken,
PasswordForgotToken,
PasswordChangeToken
) {
@ -235,55 +233,6 @@ module.exports = function (
}.bind(this))
}
MySql.prototype.createAuthToken = function (srpToken) {
log.trace({ op: 'MySql.createAuthToken', uid: srpToken && srpToken.uid })
var sql = 'INSERT INTO authTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)'
var con
return this.getMasterConnection()
.then(function(thisCon) {
con = thisCon
return AuthToken.create(srpToken)
})
.then(function(authToken) {
var d = P.defer()
con.query(
sql,
[authToken.tokenid, authToken.data, authToken.uid],
function (err) {
con.release()
if (err) return d.reject(err)
d.resolve(authToken)
}
)
return d.promise
}.bind(this))
}
MySql.prototype.createSrpToken = function (emailRecord) {
log.trace({ op: 'MySql.createSrpToken', uid: emailRecord && emailRecord.uid })
var sql = 'INSERT INTO srpTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)'
var con
return this.getMasterConnection()
.then(function(thisCon) {
con = thisCon
return SrpToken.create(emailRecord)
})
.then(function (srpToken) {
var d = P.defer()
con.query(
sql,
[srpToken.tokenid, srpToken.data, srpToken.uid],
function (err) {
con.release()
if (err) return d.reject(err)
d.resolve(srpToken)
}
)
return d.promise
}.bind(this))
}
MySql.prototype.createPasswordForgotToken = function (emailRecord) {
log.trace({ op: 'MySql.createPasswordForgotToken', uid: emailRecord && emailRecord.uid })
var sql = 'REPLACE INTO passwordForgotTokens (tokenid, tokendata, uid, passcode, created, tries) VALUES (?, ?, ?, ?, ?, ?)'
@ -464,64 +413,6 @@ module.exports = function (
})
}
MySql.prototype.authToken = function (id) {
log.trace({ op: 'MySql.authToken', id: id })
var sql = 'SELECT t.uid, t.tokendata, a.verified' +
' FROM authTokens t, accounts a' +
' WHERE t.tokenid = ? AND t.uid = a.uid'
return this.getSlaveConnection()
.then(function(con) {
var d = P.defer()
con.query(
sql,
[id],
function (err, results) {
con.release()
if (err) return d.reject(err)
if (!results.length) return d.reject(error.invalidToken())
var result = results[0]
AuthToken.fromHex(result.tokendata, result)
.done(
function (authToken) {
return d.resolve(authToken)
}
)
}
)
return d.promise
})
}
MySql.prototype.srpToken = function (id) {
log.trace({ op: 'MySql.srpToken', id: id })
var sql = 'SELECT t.tokendata, t.uid, a.srp, a.passwordStretching ' +
' FROM srpTokens t, accounts a ' +
' WHERE t.tokenid = ? AND t.uid = a.uid'
return this.getSlaveConnection()
.then(function(con) {
var d = P.defer()
con.query(
sql,
[id],
function (err, results) {
con.release()
if (err) return d.reject(err)
if (!results.length) return d.reject(error.invalidToken())
var result = results[0]
result.srp = JSON.parse(result.srp)
result.passwordStretching = JSON.parse(result.passwordStretching)
SrpToken.fromHex(result.tokendata, result)
.done(
function (srpToken) {
return d.resolve(srpToken)
}
)
}
)
return d.promise
})
}
MySql.prototype.passwordForgotToken = function (id) {
log.trace({ op: 'MySql.passwordForgotToken', id: id })
var sql = 'SELECT t.tokendata, t.uid, a.email, t.passcode, t.created, t.tries ' +
@ -760,56 +651,6 @@ module.exports = function (
})
}
MySql.prototype.deleteAuthToken = function (authToken) {
log.trace(
{
op: 'MySql.deleteAuthToken',
id: authToken && authToken.tokenid,
uid: authToken && authToken.uid
}
)
var sql = 'DELETE FROM authTokens WHERE tokenid = ?'
return this.getMasterConnection()
.then(function(con) {
var d = P.defer()
con.query(
sql,
[authToken.tokenid],
function (err) {
con.release()
if (err) return d.reject(err)
d.resolve(true)
}
)
return d.promise
})
}
MySql.prototype.deleteSrpToken = function (srpToken) {
log.trace(
{
op: 'MySql.deleteSrpToken',
id: srpToken && srpToken.tokenid,
uid: srpToken && srpToken.uid
}
)
var sql = 'DELETE FROM srpTokens WHERE tokenid = ?'
return this.getMasterConnection()
.then(function(con) {
var d = P.defer()
con.query(
sql,
[srpToken.tokenid],
function (err) {
con.release()
if (err) return d.reject(err)
d.resolve(true)
}
)
return d.promise
})
}
MySql.prototype.deletePasswordForgotToken = function (passwordForgotToken) {
log.trace(
{
@ -905,126 +746,6 @@ module.exports = function (
})
}
MySql.prototype.authFinish = function (srpToken) {
log.trace({ op: 'MySql.authFinish', uid: srpToken && srpToken.uid })
// Order of events:
// (1) make an AuthToken
// (2) get a connection
// (3) start a transaction
// (4) delete from srpTokens
// (5) insert into authTokens
// (6) commit transaction
// (7) release connection
// (8) resolve with the new authToken
var con
var authToken
return this.getMasterConnection()
.then(function(thisCon) {
con = thisCon
return beginTransaction(con)
})
.then(function() {
return AuthToken.create(srpToken)
})
.then(function (newAuthToken) {
authToken = newAuthToken
var d = P.defer()
con.query(
'DELETE FROM srpTokens WHERE tokenid = ?',
[srpToken.tokenid],
function(err) {
if (err) d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
var d = P.defer()
con.query(
'INSERT INTO authTokens(tokenid, tokendata, uid) VALUES (?, ?, ?)',
[authToken.tokenid, authToken.data, authToken.uid],
function(err) {
if (err) d.reject(err)
d.resolve(authToken)
}
)
return d.promise
})
.then(function() {
return commitTransaction(con).then(function() {
con.release()
})
})
.then(function() {
return P(authToken)
})
}
MySql.prototype.createSession = function (authToken) {
log.trace({ op: 'MySql.createSession', uid: authToken && authToken.uid })
var con
return P.all(
[
KeyFetchToken.create(authToken),
SessionToken.create(authToken)
]
)
.then(function (tokens) {
var keyFetchToken = tokens[0]
var sessionToken = tokens[1]
return this.getMasterConnection()
.then(function(thisCon) {
con = thisCon
return beginTransaction(con)
})
.then(function(thisCon) {
var d = P.defer()
con.query(
'DELETE FROM authTokens WHERE tokenid = ?',
[authToken.tokenid],
function(err) {
if (err) return d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
var d = P.defer()
con.query(
'INSERT INTO keyfetchTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)',
[keyFetchToken.tokenid, keyFetchToken.data, keyFetchToken.uid],
function(err) {
if (err) return d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
var d = P.defer()
con.query(
'INSERT INTO sessionTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)',
[sessionToken.tokenid, sessionToken.data, sessionToken.uid],
function(err) {
if (err) return d.reject(err)
// now commit and release
commitTransaction(con).then(function() {
con.release()
d.resolve({
keyFetchToken: keyFetchToken,
sessionToken: sessionToken
})
})
}
)
return d.promise
})
}.bind(this))
}
MySql.prototype.verifyEmail = function (account) {
log.trace({ op: 'MySql.verifyEmail', uid: account && account.uid })
var sql = 'UPDATE accounts SET verified = true WHERE uid = ?'
@ -1044,73 +765,6 @@ module.exports = function (
})
}
MySql.prototype.createPasswordChange = function (authToken) {
log.trace({ op: 'MySql.createPasswordChange', uid: authToken && authToken.uid })
var con
return P.all(
[
KeyFetchToken.create(authToken),
AccountResetToken.create(authToken)
]
)
.then(function (tokens) {
var keyFetchToken = tokens[0]
var accountResetToken = tokens[1]
return this.getMasterConnection()
.then(function(thisCon) {
con = thisCon
return beginTransaction(con)
})
.then(function(thisCon) {
var d = P.defer()
con.query(
'DELETE FROM authTokens WHERE tokenid = ?',
[authToken.tokenid],
function(err) {
if (err) return d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
var d = P.defer()
con.query(
'INSERT INTO keyfetchTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)',
[keyFetchToken.tokenid, keyFetchToken.data, keyFetchToken.uid],
function(err) {
if (err) return d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
var d = P.defer()
con.query(
'REPLACE INTO resetTokens (tokenid, tokendata, uid) VALUES (?, ?, ?)',
[accountResetToken.tokenid, accountResetToken.data, accountResetToken.uid],
function(err) {
if (err) return d.reject(err)
d.resolve()
}
)
return d.promise
})
.then(function() {
return commitTransaction(con)
})
.then(function() {
con.release()
return P({
keyFetchToken: keyFetchToken,
accountResetToken: accountResetToken
})
})
}.bind(this))
}
MySql.prototype.forgotPasswordVerified = function (passwordForgotToken) {
log.trace({ op: 'MySql.forgotPasswordVerified', uid: passwordForgotToken && passwordForgotToken.uid })

Просмотреть файл

@ -1,119 +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/. */
var HEX_STRING = require('./validators').HEX_STRING
var HEX_EMAIL = require('./validators').HEX_EMAIL
module.exports = function (log, isA, error, db, Token) {
var routes = [
{
method: 'POST',
path: '/auth/start',
config: {
description:
"Begins an SRP login for the supplied email address, " +
"returning the temporary srpToken and parameters for " +
"key stretching and the SRP protocol for the client.",
tags: ["srp", "account"],
handler: function (request) {
log.begin('Auth.start', request)
var reply = request.reply.bind(request)
db.emailRecord(Buffer(request.payload.email, 'hex').toString())
.then(
function (emailRecord) {
return db.createSrpToken(emailRecord)
}
)
.then(
function (srpToken) {
return srpToken.clientData()
}
)
.done(reply, reply)
},
validate: {
payload: {
email: isA.String().max(1024).regex(HEX_EMAIL).required()
},
response: {
schema: {
srpToken: isA.String().required(),
passwordStretching: isA.Object(),
srp: isA.Object({
type: isA.String().required(),
salt: isA.String().regex(HEX_STRING), // salt
B: isA.String().regex(HEX_STRING) // server's public key value
})
}
}
}
}
},
{
method: 'POST',
path: '/auth/finish',
config: {
description:
"Finishes the SRP dance, with the client providing " +
"proof-of-knownledge of the password and receiving " +
"the bundle encrypted with the authToken.",
tags: ["srp", "session"],
handler: function (request) {
log.begin('Auth.finish', request)
var reply = request.reply.bind(request)
var srpTokenId = Buffer(request.payload.srpToken, 'hex')
var srpToken = null;
db.srpToken(srpTokenId)
.then(
function (token) {
srpToken = token
return srpToken.finish(request.payload.A, request.payload.M)
}
)
.then(
function (srpToken) {
log.security({ event: 'login-success', uid: srpToken.uid });
return srpToken
},
function (err) {
log.security({ event: 'login-failure', err: err, uid: srpToken.uid });
throw err
}
)
.then(
function (srpToken) {
return db.authFinish(srpToken)
.then(
function (authToken) {
return srpToken.bundleAuth(authToken.data)
}
)
.then(
function (bundle) {
return {bundle: bundle}
}
)
}
)
.done(reply, reply)
},
validate: {
payload: {
srpToken: isA.String().regex(HEX_STRING).required(),
A: isA.String().regex(HEX_STRING).required(),
M: isA.String().regex(HEX_STRING).required()
},
response: {
schema: {
bundle: isA.String().regex(HEX_STRING).required()
}
}
}
}
}
]
return routes
}

Просмотреть файл

@ -14,14 +14,11 @@ module.exports = function (
error,
serverPublicKey,
signer,
clientHelper,
db,
mailer,
Token,
config
) {
var isProduction = config.env === 'prod'
var auth = require('./auth')(log, isA, error, db, Token)
var defaults = require('./defaults')(log, P, db)
var idp = require('./idp')(log, serverPublicKey)
var account = require('./account')(log, crypto, P, uuid, isA, error, db, mailer, isProduction)
@ -29,16 +26,13 @@ module.exports = function (
var session = require('./session')(log, isA, error, db)
var sign = require('./sign')(log, isA, error, signer, config.domain)
var util = require('./util')(log, crypto, isA, config)
var raw = require('./rawpassword')(log, isA, error, clientHelper, crypto, db, isProduction)
var v1Routes = [].concat(
auth,
account,
password,
session,
sign,
util,
raw
util
)
v1Routes.forEach(function(route) {
route.path = "/v1" + route.path

Просмотреть файл

@ -1,193 +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/. */
var HEX_STRING = require('./validators').HEX_STRING
var HEX_EMAIL = require('./validators').HEX_EMAIL
module.exports = function (log, isA, error, clientHelper, crypto, db, isProduction) {
function enqueueWithHelper(message, cb) {
clientHelper.enqueue(message, function(err, result) {
if (err) {
log.error({ op: 'clientHelper.enqueue', err: err, result: result })
return cb(error.serviceUnavailable())
}
if (result && result.err) {
return cb(result.err)
}
return cb(null, result)
})
}
var routes = [
{
method: 'POST',
path: '/raw_password/session/create',
config: {
description: 'Create a session from an email and password',
tags: ['raw', 'session'],
handler: function (request) {
log.begin('RawPassword.sessionCreate', request)
enqueueWithHelper(
{
action: 'session/create',
email: Buffer(request.payload.email, 'hex').toString('utf8'),
password: request.payload.password
},
function (err, result) {
if (err) {
return request.reply(error.wrap(err))
}
return request.reply(result)
}
)
},
validate: {
payload: {
email: isA.String().max(1024).regex(HEX_EMAIL).required(),
password: isA.String().required()
},
response: {
schema: {
uid: isA.String().required(),
verified: isA.Boolean().required(),
sessionToken: isA.String().regex(HEX_STRING).required()
}
}
}
}
},
{
method: 'POST',
path: '/raw_password/account/create',
config: {
description: 'Creates an account associated with an email address',
tags: ['raw', 'account'],
handler: function accountCreate(request) {
log.begin('RawPassword.accountCreate', request)
enqueueWithHelper(
{
action: 'account/create',
email: Buffer(request.payload.email, 'hex').toString('utf8'),
password: request.payload.password,
options: {
preVerified: request.payload.preVerified || false,
service: request.payload.service,
lang: request.app.preferredLang
}
},
function (err, result) {
if (err) {
return request.reply(error.wrap(err))
}
return request.reply(result)
}
)
},
validate: {
payload: {
email: isA.String().max(1024).regex(HEX_EMAIL).required(),
password: isA.String().required(),
preVerified: isA.Boolean(),
service: isA.String().max(16).alphanum().optional()
}
}
}
},
{
method: 'POST',
path: '/raw_password/password/change',
config: {
description: 'Creates an account associated with an email address',
tags: ['raw', 'account'],
handler: function passwordChange(request) {
log.begin('RawPassword.passwordChange', request)
enqueueWithHelper(
{
action: 'password/change',
email: Buffer(request.payload.email, 'hex').toString('utf8'),
oldPassword: request.payload.oldPassword,
newPassword: request.payload.newPassword
},
function (err, result) {
if (err) {
err = error.wrap(err)
log.security({ event: 'pwd-change-failure', err: err })
return request.reply(err);
}
log.security({ event: 'pwd-change-success' })
return request.reply(result)
}
)
},
validate: {
payload: {
email: isA.String().max(1024).regex(HEX_EMAIL).required(),
oldPassword: isA.String().required(),
newPassword: isA.String().required()
}
}
}
},
{
method: 'POST',
path: '/raw_password/password/reset',
config: {
description: 'Resets the password associated with an account',
auth: {
strategy: 'accountResetToken'
},
tags: ['raw', 'account'],
handler: function passwordReset(request) {
log.begin('RawPassword.passwordReset', request)
var accountResetToken = request.auth.credentials
var form = request.payload
db.account(accountResetToken.uid)
.then(
function (account) {
enqueueWithHelper(
{
action: 'password/reset',
email: account.email,
newPassword: form.newPassword,
passwordStretching: account.passwordStretching
},
function (err, result) {
if (err) {
err = error.wrap(err)
log.security({ event: 'pwd-reset-failure', err: err })
return request.reply(err)
}
log.security({ event: 'pwd-reset-success' })
result.wrapKb = crypto.randomBytes(32)
db.resetAccount(accountResetToken, result)
.done(
function () {
return request.reply({})
},
function (err) {
return request.reply(error.wrap(err))
}
)
}
)
}
)
},
validate: {
payload: {
newPassword: isA.String().required()
}
}
}
},
]
if (isProduction) {
delete routes[1].config.validate.payload.preVerified
}
return routes
}

Просмотреть файл

@ -5,42 +5,6 @@
module.exports = function (log, isA, error, db) {
var routes = [
{
method: 'POST',
path: '/session/create',
config: {
description: "Creates a new session",
tags: ["session"],
auth: {
strategy: 'authToken'
},
handler: function (request) {
log.begin('Session.create', request)
var reply = request.reply.bind(request)
var authToken = request.auth.credentials
log.security({ event: 'session-create' })
db.createSession(authToken)
.then(
function (tokens) {
return authToken.bundleSession(
tokens.keyFetchToken.data,
tokens.sessionToken.data
)
}
)
.then(
function (bundle) {
return {
uid: authToken.uid.toString('hex'),
verified: authToken.verified,
bundle: bundle
}
}
)
.done(reply, reply)
}
}
},
{
method: 'POST',
path: '/session/destroy',

Просмотреть файл

@ -71,11 +71,6 @@ module.exports = function (path, url, Hapi, toobusy) {
hawk: hawkOptions,
getCredentialsFunc: makeCredentialFn(db.accountResetToken.bind(db))
},
authToken: {
scheme: 'hawk',
hawk: hawkOptions,
getCredentialsFunc: makeCredentialFn(db.authToken.bind(db))
},
passwordForgotToken: {
scheme: 'hawk',
hawk: hawkOptions,

Просмотреть файл

@ -1,148 +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/. */
var test = require('../ptaptest')
var crypto = require('crypto')
var log = { trace: function() {} }
var tokens = require('../../tokens')(log)
var AuthToken = tokens.AuthToken
var ACCOUNT = {
uid: 'xxx'
}
test(
're-creation from tokendata works',
function (t) {
var token = null;
return AuthToken.create(ACCOUNT)
.then(
function (x) {
token = x
}
)
.then(
function () {
return AuthToken.fromHex(token.data, ACCOUNT)
}
)
.then(
function (token2) {
t.deepEqual(token.data, token2.data)
t.deepEqual(token.id, token2.id)
t.deepEqual(token.authKey, token2.authKey)
t.deepEqual(token.bundleKey, token2.bundleKey)
t.deepEqual(token.uid, token2.uid)
}
)
}
)
test(
'bundle / unbundle of session data works',
function (t) {
var token = null;
var keyFetchToken = crypto.randomBytes(32)
var sessionToken = crypto.randomBytes(32)
return AuthToken.create(ACCOUNT)
.then(
function (x) {
token = x
return x.bundleSession(keyFetchToken, sessionToken)
}
)
.then(
function (b) {
return token.unbundleSession(b)
}
)
.then(
function (ub) {
t.deepEqual(ub.keyFetchToken, keyFetchToken)
t.deepEqual(ub.sessionToken, sessionToken)
}
)
}
)
test(
'bundle / unbundle of account reset data works',
function (t) {
var token = null;
var keyFetchToken = crypto.randomBytes(32)
var resetToken = crypto.randomBytes(32)
return AuthToken.create(ACCOUNT)
.then(
function (x) {
token = x
return x.bundleAccountReset(keyFetchToken, resetToken)
}
)
.then(
function (b) {
return token.unbundleAccountReset(b)
}
)
.then(
function (ub) {
t.deepEqual(ub.keyFetchToken, keyFetchToken)
t.deepEqual(ub.accountResetToken, resetToken)
}
)
}
)
test(
'authToken key derivations are test-vector compliant',
function (t) {
var token = null;
var tokendata = '606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f'
return AuthToken.fromHex(tokendata, ACCOUNT)
.then(
function (x) {
token = x
t.equal(token.data.toString('hex'), tokendata)
t.equal(token.id.toString('hex'), '9a39818e3bbe613238c9d7ff013a18411ed2c66c3565c3c4de03feefecb7d212')
t.equal(token.authKey.toString('hex'), '4a17cbdd54ee17db426fcd7baddff587231d7eadb408c091ce19ca915b715985')
t.equal(token.bundleKey.toString('hex'), '9d93978e662bfc6e8cc203fa4628ef5a7bf1ddfd7ee54e97ec5c033257b4fca9')
}
)
.then(
function () {
var keyFetchToken = Buffer('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex')
var sessionToken = Buffer('a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf', 'hex')
return token.bundleSession(keyFetchToken, sessionToken)
}
)
.then(
function (bundle) {
t.equal(bundle,
'04a347b2c75b2f418cc37162dea57c1ee408f9109f820234' +
'7768a841cf8ad3dc324f1adf6b2f710fa4ea823f4ccb70c4' +
'bf46b4eb6b0a99b0017ecafbf95073eb7973ddbb184b601a' +
'c4df09704028ebfc754dd50e7d8eebfa52ce3fd868c69852')
}
)
.then(
function () {
var keyFetchToken = Buffer('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f', 'hex')
var accountResetToken = Buffer('c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf', 'hex')
return token.bundleAccountReset(keyFetchToken, accountResetToken)
}
)
.then(
function (bundle) {
t.equal(bundle,
'bd643fdd047f7ecd5743d91d980cad6011155fd8559fea1d' +
'438f12d2c66270f820be421ad000d69800a4a03980862f7e' +
'3fbd4eb5c0f77a94c0c2e7f2be97d21d804fc4bc30923cc0' +
'd6c07ffea954848e0076b94f7deee71fa34db5c106d91980')
}
)
}
)

Просмотреть файл

@ -12,11 +12,9 @@ var DB = require('../../db')(
config,
log,
Token.error,
Token.AuthToken,
Token.SessionToken,
Token.KeyFetchToken,
Token.AccountResetToken,
Token.SrpToken,
Token.PasswordForgotToken
)
@ -82,62 +80,6 @@ DB.connect()
}
)
test(
'srp token handling',
function (t) {
return db.emailRecord(ACCOUNT.email)
.then(function(emailRecord) {
return db.createSrpToken(emailRecord)
})
.then(function(srpToken) {
t.deepEqual(srpToken.uid, ACCOUNT.uid, 'srpToken.uid is the same as the ACCOUNT.uid')
t.equal(srpToken.v.toString('hex'), ACCOUNT.srp.verifier)
t.equal(srpToken.s, ACCOUNT.srp.salt, 'srpToken.s == ACCOUNT.srp.salt')
t.ok(srpToken.b, 'srpToken.b is true')
return srpToken
})
.then(function(srpToken) {
return db.srpToken(srpToken.tokenid)
})
.then(function(srpToken) {
t.deepEqual(srpToken.uid, ACCOUNT.uid)
t.equal(srpToken.v.toString('hex'), ACCOUNT.srp.verifier)
t.equal(srpToken.s, ACCOUNT.srp.salt)
t.ok(srpToken.b)
return srpToken
})
.then(function(srpToken) {
return db.deleteSrpToken(srpToken.tokenid)
})
}
)
test(
'auth token handling',
function (t) {
var tokenid;
return db.emailRecord(ACCOUNT.email)
.then(function(emailRecord) {
return db.createAuthToken({ uid: emailRecord.uid })
})
.then(function(authToken) {
t.deepEqual(authToken.uid, ACCOUNT.uid)
tokenid = authToken.tokenid
})
.then(function() {
return db.authToken(tokenid)
})
.then(function(authToken) {
t.deepEqual(authToken.tokenid, tokenid, 'token id matches')
t.deepEqual(authToken.uid, ACCOUNT.uid)
return authToken
})
.then(function(authToken) {
return db.deleteAuthToken(authToken)
})
}
)
test(
'session token handling',
function (t) {
@ -276,88 +218,6 @@ DB.connect()
}
)
test(
'db.authFinish',
function (t) {
return db.emailRecord(ACCOUNT.email)
.then(function(emailRecord) {
return db.createSrpToken(emailRecord)
})
.then(function(srpToken) {
return db.authFinish(srpToken)
})
.then(function(authToken) {
t.deepEqual(authToken.uid, ACCOUNT.uid)
})
}
)
/*
test(
'db.createSession',
function (t) {
var tokens1;
return db.emailRecord(ACCOUNT.email)
.then(function(emailRecord) {
return db.createAuthToken(emailRecord)
})
.then(function(authToken) {
return db.createSession(authToken)
})
.then(function(tokens) {
t.deepEqual(tokens.keyFetchToken.uid, ACCOUNT.uid, 'keyFetchToken uid and account uid should be the same')
t.deepEqual(tokens.sessionToken.uid, ACCOUNT.uid, 'sessionToken uid and account uid should be the same')
tokens1 = tokens
})
.then(function() {
return db.keyFetchToken(tokens1.keyFetchToken.tokenid)
})
.then(function(keyFetchToken) {
t.deepEqual(keyFetchToken.uid, ACCOUNT.uid, 'keyFetchToken uid and account uid should still be the same')
return db.deleteKeyFetchToken(tokens1.keyFetchToken)
})
.then(function() {
return db.sessionToken(tokens1.sessionToken.tokenid)
})
.then(function(sessionToken) {
t.deepEqual(sessionToken.uid, ACCOUNT.uid, 'sessionToken uid and account uid should still be the same')
return db.deleteSessionToken(tokens1.sessionToken)
})
}
)
test(
'db.createPasswordChange',
function (t) {
var tokens1;
return db.emailRecord(ACCOUNT.email)
.then(function(emailRecord) {
return db.createAuthToken(emailRecord)
})
.then(function(authToken) {
return db.createPasswordChange(authToken)
})
.then(function(tokens) {
t.deepEqual(tokens.keyFetchToken.uid, ACCOUNT.uid, 'keyFetchToken.uid is the same as the original ACCOUNT.uid')
t.deepEqual(tokens.accountResetToken.uid, ACCOUNT.uid, 'accountResetToken.uid is the same as the original ACCOUNT.uid')
tokens1 = tokens
})
.then(function() {
return db.keyFetchToken(tokens1.keyFetchToken.tokenid)
})
.then(function(keyFetchToken) {
t.deepEqual(keyFetchToken.uid, ACCOUNT.uid)
return db.deleteKeyFetchToken(tokens1.keyFetchToken)
})
.then(function() {
return db.accountResetToken(tokens1.accountResetToken.tokenid)
})
.then(function(accountResetToken) {
t.deepEqual(accountResetToken.uid, ACCOUNT.uid)
return db.deleteAccountResetToken(tokens1.accountResetToken)
})
}
)
*/
test(
'db.forgotPasswordVerified',
function (t) {

Просмотреть файл

@ -1,183 +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/. */
var test = require('../ptaptest')
var crypto = require('crypto')
var Client = require('../../client')
var config = require('../../config').root()
var TestServer = require('../test_server')
function uniqueID() {
return crypto.randomBytes(10).toString('hex');
}
TestServer.start(config.publicUrl)
.then(function main(server) {
// Randomly-generated account names for testing.
// This makes it easy to run the tests against an existing server
// which may already have some accounts in its db.
var email1 = uniqueID() + "@example.com"
test(
'(reduced security) Create account',
function (t) {
var clientApi = new Client.Api(config.publicUrl)
var email = Buffer(email1).toString('hex')
var password = 'allyourbasearebelongtous'
return clientApi.rawPasswordAccountCreate(email, password, {preVerified: true})
.then(
function (result) {
var client = null
t.equal(typeof(result.uid), 'string')
return Client.login(config.publicUrl, email1, password)
.then(
function (x) {
client = x
return client.keys()
}
)
.then(
function (keys) {
t.ok(Buffer.isBuffer(keys.kA), 'kA exists')
t.ok(Buffer.isBuffer(keys.wrapKb), 'wrapKb exists')
t.ok(Buffer.isBuffer(keys.kB), 'kB exists')
t.equal(client.kB.length, 32, 'kB exists, has the right length')
}
)
}
)
.then(
function () {
return server.assertLogs(t, {
'account-create-success': 1,
'session-create': 1
})
}
)
}
)
test(
'(reduced security) Login with email and password',
function (t) {
var clientApi = new Client.Api(config.publicUrl)
var email = Buffer(email1).toString('hex')
var password = 'allyourbasearebelongtous'
return clientApi.rawPasswordSessionCreate(email, password)
.then(
function (result) {
t.ok(result.uid, 'uid exists')
t.equal(result.verified, true, 'email verified')
t.equal(typeof(result.sessionToken), 'string', 'sessionToken exists')
}
)
.then(
function () {
return server.assertLogs(t, {
'account-create-success': 0,
'session-create': 1
})
}
)
}
)
test(
'(reduced security) Login with email and wrong password',
function (t) {
var clientApi = new Client.Api(config.publicUrl)
var email = Buffer(email1).toString('hex')
var password = 'xxx'
return clientApi.rawPasswordSessionCreate(email, password)
.then(
function (result) {
t.fail('login succeeded')
},
function (err) {
t.equal(err.errno, 103)
}
)
.then(
function () {
return server.assertLogs(t, {
'login-failure': 1,
'login-success': 0,
'session-create': 0
})
}
)
}
)
test(
'(reduced security) Login with unknown email',
function (t) {
var clientApi = new Client.Api(config.publicUrl)
var email = Buffer('x@y.me').toString('hex')
var password = 'allyourbasearebelongtous'
return clientApi.rawPasswordSessionCreate(email, password)
.then(
function (result) {
t.fail('login succeeded')
},
function (err) {
t.equal(err.errno, 102)
}
)
.then(
function () {
return server.assertLogs(t, {
// the error here is "account does not exist"
// which doesn't trigger security logging
'login-failure': 0,
'login-success': 0,
'session-create': 0
})
}
)
}
)
test(
'(reduced security) Change password',
function (t) {
var clientApi = new Client.Api(config.publicUrl)
var email = Buffer(email1).toString('hex')
var password = 'allyourbasearebelongtous'
var newPassword = 'wow'
return clientApi.rawPasswordPasswordChange(email, password, newPassword)
.then(
function (result) {
t.equal(JSON.stringify(result), '{}', 'password changed')
return clientApi.rawPasswordSessionCreate(email, newPassword)
}
)
.then(
function (result) {
t.ok(result.uid, 'uid exists')
t.equal(result.verified, true, 'email verified')
t.equal(typeof(result.sessionToken), 'string', 'sessionToken exists')
}
)
.then(
function () {
return server.assertLogs(t, {
'pwd-reset-success': 1,
'pwd-reset-failure': 0
})
}
)
}
)
test(
'teardown',
function (t) {
server.stop()
t.end()
}
)
})

Просмотреть файл

@ -1,128 +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/. */
var test = require('../ptaptest')
var crypto = require('crypto')
var srp = require('srp')
var log = { trace: function() {} }
var Token = require('../../tokens')(log)
var DB = require('../../db/heap')(
log,
Token.error,
Token.AuthToken,
Token.SessionToken,
Token.KeyFetchToken,
Token.AccountResetToken,
Token.SrpToken,
Token.PasswordForgotToken
)
var db = new DB()
var SrpToken = Token.SrpToken
var alice = {
uid: 'xxx',
email: Buffer('somebödy@example.com').toString('hex'),
password: 'awesomeSauce',
srp: {
verifier: null,
salt: 'BAD1'
},
kA: 'BAD3',
wrapKb: 'BAD4'
}
alice.srp.verifier = srp.computeVerifier(
srp.params[2048],
Buffer(alice.srp.salt, 'hex'),
Buffer(alice.email),
Buffer(alice.password)
).toString('hex')
db.createAccount(alice)
.then(
function (a) {
test(
'create login session works',
function (t) {
return SrpToken.create(a)
.then(
function (s) {
t.equal(s.uid, a.uid)
t.equal(s.s, a.srp.salt)
}
)
}
)
test(
'finish login session works',
function (t) {
var session = null
var K = null
return SrpToken.create(a)
.then(
function (s) {
session = s
var a = crypto.randomBytes(32)
var clientData = s.clientData()
var srpClient = new srp.Client(
srp.params[2048],
Buffer(clientData.srp.salt, 'hex'),
Buffer(alice.email),
Buffer(alice.password),
a
)
srpClient.setB(Buffer(clientData.srp.B, 'hex'))
return {
A: srpClient.computeA().toString('hex'),
M: srpClient.computeM1().toString('hex'),
K: srpClient.computeK().toString('hex'),
}
}
)
.then(
function (x) {
K = x.K
return session.finish(x.A, x.M)
}
)
.then(
function (s) {
t.equal(s.K.toString('hex'), K)
}
)
}
)
}
)
.done(
function () {
test(
'authToken encryption is test-vector compliant',
function (t) {
var srpK = 'e68fd0112bfa31dcffc8e9c96a1cbadb4c3145978ff35c73e5bf8d30bbc7499a'
var authToken = Buffer('606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f', 'hex')
var bundle = '253957f10e861c7c0a12bb0193d384d9579db544666d50bd3252d6576c768a68' +
'a98c87f5769ab4ccca3df863faeb217eb16ddc29d712b30112b446324ee806d6'
return SrpToken.create(alice)
.then(
function (token) {
token.K = Buffer(srpK, 'hex')
return token.bundleAuth(authToken)
}
)
.then(
function (b) {
t.equal(b, bundle)
}
)
}
)
}
)

Просмотреть файл

@ -1,62 +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/. */
module.exports = function (log, inherits, Token, error) {
function AuthToken(keys, details) {
Token.call(this, keys, details)
this.verified = !!details.verified
}
inherits(AuthToken, Token)
AuthToken.tokenTypeID = 'authToken'
AuthToken.create = function (details) {
log.trace({ op: 'AuthToken.create', uid: details && details.uid })
return Token.createNewToken(AuthToken, details || {})
}
AuthToken.fromHex = function (string, details) {
log.trace({ op: 'AuthToken.fromHex' })
return Token.createTokenFromHexData(AuthToken, string, details || {})
}
AuthToken.prototype.bundleSession = function (keyFetchToken, sessionToken) {
log.trace({ op: 'authToken.bundleSession', id: this.id })
return this.bundle('session/create', Buffer.concat([keyFetchToken, sessionToken]))
}
AuthToken.prototype.unbundleSession = function (bundle) {
log.trace({ op: 'authToken.unbundleSession', id: this.id })
return this.unbundle('session/create', bundle)
.then(
function (plaintext) {
return {
keyFetchToken: plaintext.slice(0, 32),
sessionToken: plaintext.slice(32, 64)
}
}
)
}
AuthToken.prototype.bundleAccountReset = function (keyFetchToken, resetToken) {
log.trace({ op: 'authToken.bundleAccountReset', id: this.id })
return this.bundle('password/change', Buffer.concat([keyFetchToken, resetToken]))
}
AuthToken.prototype.unbundleAccountReset = function (bundle) {
log.trace({ op: 'authToken.unbundleAccountReset', id: this.id })
return this.unbundle('password/change', bundle)
.then(
function (plaintext) {
return {
keyFetchToken: plaintext.slice(0, 32),
accountResetToken: plaintext.slice(32, 64)
}
}
)
}
return AuthToken
}

Просмотреть файл

@ -6,8 +6,6 @@ var crypto = require('crypto')
var inherits = require('util').inherits
var P = require('p-promise')
var srp = require('srp')
var uuid = require('uuid')
var hkdf = require('../hkdf')
var error = require('./error')
@ -25,7 +23,6 @@ module.exports = function (log) {
crypto
)
var SessionToken = require('./session_token')(log, inherits, Token)
var AuthToken = require('./auth_token')(log, inherits, Token, error)
var PasswordForgotToken = require('./password_forgot_token')(
log,
inherits,
@ -33,16 +30,6 @@ module.exports = function (log) {
Token,
crypto
)
var SrpToken = require('./srp_token')(
log,
inherits,
P,
uuid,
srp,
Bundle,
Token,
error
)
var PasswordChangeToken = require('./password_change_token')(
log,
@ -55,9 +42,7 @@ module.exports = function (log) {
Token.AccountResetToken = AccountResetToken
Token.KeyFetchToken = KeyFetchToken
Token.SessionToken = SessionToken
Token.AuthToken = AuthToken
Token.PasswordForgotToken = PasswordForgotToken
Token.SrpToken = SrpToken
Token.PasswordChangeToken = PasswordChangeToken
return Token

Просмотреть файл

@ -1,85 +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/. */
module.exports = function (log, inherits, P, uuid, srp, Bundle, Token, error) {
function SrpToken(keys, details) {
if (!details.srp) { details.srp = {} }
Token.call(this, keys, details)
this.b = this.bundleKey
this.v = details.srp.verifier ? Buffer(details.srp.verifier, 'hex') : null
this.s = details.srp.salt ? details.srp.salt : null
this.passwordStretching = details.passwordStretching || null
this.srpServer = new srp.Server(srp.params[2048], this.v, this.b)
this.K = null
}
inherits(SrpToken, Token)
SrpToken.tokenTypeID = 'srpToken'
SrpToken.create = function (details) {
log.trace({ op: 'SrpToken.create', uid: details && details.uid })
return Token.createNewToken(SrpToken, details || {})
}
SrpToken.fromHex = function (string, details) {
log.trace({ op: 'SrpToken.create', uid: details && details.uid })
return Token.createTokenFromHexData(SrpToken, string, details || {})
}
// Get the data to be sent back to the client in the first message.
//
SrpToken.prototype.clientData = function () {
return {
srpToken: this.id.toString('hex'),
passwordStretching: this.passwordStretching,
srp: {
type: 'SRP-6a/SHA256/2048/v1',
salt: this.s,
B: this.srpServer.computeB().toString('hex')
}
}
}
// Complete the SRP dance, verifying the correct credentials and
// deriving the value of the shared secret.
//
SrpToken.prototype.finish = function (A, M1) {
A = Buffer(A, 'hex')
this.srpServer.setA(A)
try {
this.srpServer.checkM1(Buffer(M1, 'hex'))
}
catch (e) {
throw error.incorrectPassword()
}
this.K = this.srpServer.computeK()
return this
}
SrpToken.prototype.bundleAuth = function (authToken) {
log.trace({ op: 'srpToken.bundleAuth', id: this.id, auth: authToken })
if (!this.K) {
return P.reject('Shared secret missing; SRP handshake was not completed')
}
return Bundle.bundle(this.K, 'auth/finish', authToken)
}
SrpToken.prototype.unbundleAuth = function (bundle) {
log.trace({ op: 'srpToken.unbundleAuth', id: this.id })
if (!this.K) {
return P.reject('Shared secret missing; SRP handshake was not completed')
}
return Bundle.unbundle(this.K, 'auth/finish', bundle)
.then(
function (plaintext) {
return {
authToken: plaintext,
}
}
)
}
return SrpToken
}