restructure tests
* created test/local for tests that can only be run locally * created test/remote for tests that can run remotely or locally * moved most api level tests to test/remote * much more of the test suite can run remotely now :)
This commit is contained in:
Родитель
8c1263055a
Коммит
8f3509fc7e
|
@ -88,6 +88,25 @@ Client.changePassword = function (origin, email, oldPassword, newPassword) {
|
|||
)
|
||||
}
|
||||
|
||||
Client.createAndVerify = function (origin, email, password, mailbox, options) {
|
||||
return Client.create(origin, email, password, options)
|
||||
.then(
|
||||
function (client) {
|
||||
return mailbox.waitForCode(email)
|
||||
.then(
|
||||
function (code) {
|
||||
return client.verifyEmail(code)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Client.prototype.create = function () {
|
||||
return this.api.accountCreate(
|
||||
this.email,
|
||||
|
|
|
@ -9,11 +9,12 @@
|
|||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./scripts/tap-coverage.js ./test/run",
|
||||
"test": "./scripts/tap-coverage.js test/local test/remote",
|
||||
"start": "scripts/start-local.sh",
|
||||
"test-mysql": "DB_BACKEND=mysql npm test",
|
||||
"test-all": "npm test && npm run test-mysql",
|
||||
"test-remote": "MAILER_HOST=restmail.net MAILER_PORT=80 tap --timeout=180 --tap test/run/verification_tests.js"
|
||||
"test-all": "npm run test-quick && npm run test-mysql",
|
||||
"test-quick": "tap test/local test/remote",
|
||||
"test-remote": "MAILER_HOST=restmail.net MAILER_PORT=80 tap --timeout=300 --tap test/remote"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var path = require('path')
|
||||
var Client = require('../../client')
|
||||
var request = require('request')
|
||||
|
||||
process.env.CONFIG_FILES = path.join(__dirname, '../config/api_error.json')
|
||||
var config = require('../../config').root()
|
||||
|
||||
function fail() { throw new Error() }
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'token expiry',
|
||||
function (t) {
|
||||
// FYI config.tokenLifetimes.passwordChangeToken = -1
|
||||
var email = Math.random() + "@example.com"
|
||||
var password = 'ok'
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (c) {
|
||||
return c.changePassword('hello')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.errno, 110, 'invalid token')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,64 @@
|
|||
/* 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 P = require('p-promise')
|
||||
var request = require('request')
|
||||
|
||||
module.exports = function (host, port) {
|
||||
|
||||
host = host || '127.0.0.1'
|
||||
port = port || 9001
|
||||
|
||||
function waitForCode(email) {
|
||||
return waitForEmail(email)
|
||||
.then(
|
||||
function (emailData) {
|
||||
return emailData.headers['x-verify-code'] || emailData.headers['x-recovery-code']
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function loop(name, tries, cb) {
|
||||
var url = 'http://' + host + ':' + port + '/mail/' + name
|
||||
console.log('checking mail', url)
|
||||
request({ url: url, method: 'GET' },
|
||||
function (err, res, body) {
|
||||
console.log('mail status', res && res.statusCode, 'tries', tries)
|
||||
var json = null
|
||||
try {
|
||||
json = JSON.parse(body)[0]
|
||||
}
|
||||
catch (e) {
|
||||
return cb(e)
|
||||
}
|
||||
|
||||
if(!json) {
|
||||
if (tries === 0) {
|
||||
return cb(new Error('could not get mail for ' + url))
|
||||
}
|
||||
return setTimeout(loop.bind(null, name, --tries, cb), 1000)
|
||||
}
|
||||
console.log('deleting mail', url)
|
||||
request({ url: url, method: 'DELETE' },
|
||||
function (err, res, body) {
|
||||
cb(err, json)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function waitForEmail(email) {
|
||||
var d = P.defer()
|
||||
loop(email.split('@')[0], 20, function (err, json) {
|
||||
return err ? d.reject(err) : d.resolve(json)
|
||||
})
|
||||
return d.promise
|
||||
}
|
||||
|
||||
return {
|
||||
waitForEmail: waitForEmail,
|
||||
waitForCode: waitForCode
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var Client = require('../../client')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'create account',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var verifyCode = null
|
||||
var keyFetchToken = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail('got keys before verifying email')
|
||||
},
|
||||
function (err) {
|
||||
keyFetchToken = client.keyFetchToken
|
||||
t.ok(client.keyFetchToken, 'retained keyFetchToken')
|
||||
t.equal(err.message, 'Unverified account', 'account is unverified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
verifyCode = code
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
t.equal(code, verifyCode, 'verify codes are the same')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.verifyEmail(verifyCode)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, true)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.equal(keyFetchToken, client.keyFetchToken, 'reusing keyFetchToken')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-request': 1,
|
||||
'account-verify-success': 1,
|
||||
'account-verify-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create account with service identifier',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var options = { service: 'abcdef' }
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], 'abcdef')
|
||||
client.options.service = '123456'
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], '123456')
|
||||
client.options.service = null
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], undefined)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-request': 2,
|
||||
'account-verify-success': 0,
|
||||
'account-verify-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create account allows localization of emails',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.assert(emailData.text.indexOf('Welcome') !== -1, 'is en')
|
||||
t.assert(emailData.text.indexOf('GDay') === -1, 'not en-AU')
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return Client.create(config.publicUrl, email, password, { lang: 'en-AU' })
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.assert(emailData.text.indexOf('Welcome') === -1, 'not en')
|
||||
t.assert(emailData.text.indexOf('GDay') !== -1, 'is en-AU')
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Unknown account should not exist',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
client.email = server.uniqueEmail()
|
||||
client.authPW = crypto.randomBytes(32)
|
||||
return client.auth()
|
||||
.then(
|
||||
function () {
|
||||
t.fail('account should not exist')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 102, 'account does not exist')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/account/create works with proper data',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'ilikepancakes'
|
||||
var client
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
t.ok(client.uid, 'account created')
|
||||
}
|
||||
).then(
|
||||
function () {
|
||||
return client.login()
|
||||
}
|
||||
).then(
|
||||
function () {
|
||||
t.ok(client.sessionToken, "client can login")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/account/create with malformed email address',
|
||||
function (t) {
|
||||
var email = 'notAnEmailAddress'
|
||||
var password = '123456'
|
||||
return Client.create(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'malformed email is rejected')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'signup with same email, different case',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var email2 = email.toUpperCase()
|
||||
var password = 'abcdef'
|
||||
return Client.create(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
return Client.create(config.publicUrl, email2, password, server.mailbox)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400)
|
||||
t.equal(err.errno, 101, 'Account already exists')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'invalid redirectTo',
|
||||
function (t) {
|
||||
var api = new Client.Api(config.publicUrl)
|
||||
var email = server.uniqueEmail()
|
||||
var options = {
|
||||
redirectTo: 'http://accounts.firefox.com.evil.us'
|
||||
}
|
||||
return api.accountCreate(email, '123456', options)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'bad redirectTo rejected')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return api.passwordForgotSendCode(email, options)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'bad redirectTo rejected')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,84 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var Client = require('../../client')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'account destroy',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (devices) {
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail('account not destroyed')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message, 'Unknown account', 'account destroyed')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'account-destroy': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'invalid authPW on account destroy',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'ok'
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
c.authPW = Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
|
||||
return c.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.errno, 103)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,63 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var Client = require('../../client')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'the rawEmail is returned in the error on Incorrect Password errors',
|
||||
function (t) {
|
||||
var signupEmail = server.uniqueEmail()
|
||||
var loginEmail = signupEmail.toUpperCase()
|
||||
var password = 'abcdef'
|
||||
return Client.createAndVerify(config.publicUrl, signupEmail, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
return Client.login(config.publicUrl, loginEmail, password)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400)
|
||||
t.equal(err.errno, 103)
|
||||
t.equal(err.email, signupEmail)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Unknown account should not exist',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
client.email = server.uniqueEmail()
|
||||
client.authPW = crypto.randomBytes(32)
|
||||
return client.login()
|
||||
.then(
|
||||
function () {
|
||||
t.fail('account should not exist')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 102, 'account does not exist')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,121 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var Client = require('../../client')
|
||||
var jwcrypto = require('jwcrypto')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'certificate sign',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var publicKey = {
|
||||
"algorithm":"RS",
|
||||
"n":"4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123",
|
||||
"e":"65537"
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
return client.sign(publicKey, duration)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (cert) {
|
||||
t.equal(typeof(cert), 'string', 'cert exists')
|
||||
var payload = jwcrypto.extractComponents(cert).payload
|
||||
t.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'login-success': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/certificate/sign inputs',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = '123456'
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
// string as publicKey
|
||||
return client.sign("tada", 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'string as publicKey')
|
||||
// empty object as publicKey
|
||||
return client.sign({}, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'empty object as publicKey')
|
||||
// invalid publicKey argument
|
||||
return client.sign({ algorithm: 'RS', n: 'x', e: 'y', invalid: true }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'invalid publicKey argument')
|
||||
// undefined duration
|
||||
return client.sign({ algorithm: 'RS', n: 'x', e: 'y' }, undefined)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'undefined duration')
|
||||
// missing publicKey arguments (e)
|
||||
return client.sign({ algorithm: 'RS', n: 'x' }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'missing publicKey arguments (e)')
|
||||
// invalid algorithm
|
||||
return client.sign({ algorithm: 'NSA' }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
t.fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'invalid algorithm')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,175 @@
|
|||
/* 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 Client = require('../../client')
|
||||
var TestServer = require('../test_server')
|
||||
var config = require('../../config').root()
|
||||
var jwcrypto = require('jwcrypto')
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
var email1 = server.uniqueEmail()
|
||||
|
||||
test(
|
||||
'Create account flow',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var publicKey = {
|
||||
"algorithm":"RS",
|
||||
"n":"4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123",
|
||||
"e":"65537"
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.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 client.sign(publicKey, duration)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (cert) {
|
||||
t.equal(typeof(cert), 'string', 'cert exists')
|
||||
var payload = jwcrypto.extractComponents(cert).payload
|
||||
t.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'login-success': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Login flow',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var publicKey = {
|
||||
"algorithm":"RS",
|
||||
"n":"4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123",
|
||||
"e":"65537"
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
t.ok(client.uid, 'got a uid')
|
||||
t.equal(client.emailVerified, true, 'email is verified')
|
||||
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 client.sign(publicKey, duration)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (cert) {
|
||||
t.equal(typeof(cert), 'string', 'cert exists')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Change password flow',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var newPassword = 'foobar'
|
||||
var kB = null
|
||||
var client = null
|
||||
var firstAuthPW
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
firstAuthPW = x.authPW.toString('hex')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
kB = keys.kB
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.changePassword(newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.deepEqual(keys.kB, kB, 'kB is preserved')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 2,
|
||||
'pwd-change-request': 1,
|
||||
'pwd-reset-success': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,402 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var Client = require('../../client')
|
||||
var P = require('../../promise')
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'unsupported api version',
|
||||
function (t) {
|
||||
request(config.publicUrl + '/v0/account/create', function (err, res) {
|
||||
t.equal(res.statusCode, 410, 'http gone')
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/ returns version',
|
||||
function (t) {
|
||||
request(config.publicUrl + '/', function (err, res, body) {
|
||||
var json = JSON.parse(body)
|
||||
t.equal(json.version, require('../../package.json').version, 'package version')
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/verify_email redirects',
|
||||
function (t) {
|
||||
var path = '/v1/verify_email?code=0000&uid=0000'
|
||||
request(
|
||||
{
|
||||
url: config.publicUrl + path,
|
||||
followRedirect: false
|
||||
},
|
||||
function (err, res, body) {
|
||||
t.equal(res.statusCode, 302, 'redirected')
|
||||
t.equal(res.headers.location, config.contentServer.url + path)
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/complete_reset_password redirects',
|
||||
function (t) {
|
||||
var path = '/v1/complete_reset_password?code=0000&email=a@b.c&token=0000'
|
||||
request(
|
||||
{
|
||||
url: config.publicUrl + path,
|
||||
followRedirect: false
|
||||
},
|
||||
function (err, res, body) {
|
||||
t.equal(res.statusCode, 302, 'redirected')
|
||||
t.equal(res.headers.location, config.contentServer.url + path)
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'HAWK timestamp',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
return client.login()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
url = client.api.baseURL + '/account/keys'
|
||||
return client.api.Token.KeyFetchToken.fromHex(client.keyFetchToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
timestamp: Math.floor(Date.now() / 1000) - 61
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(body.errno, 111, 'invalid auth timestamp')
|
||||
var now = +new Date() / 1000
|
||||
t.ok(body.serverTime > now - 5, 'includes current time')
|
||||
t.ok(body.serverTime < now + 5, 'includes current time')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'auth-failure': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'HAWK nonce re-use',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
return client.login()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
url = client.api.baseURL + '/account/devices'
|
||||
return client.api.Token.SessionToken.fromHex(client.sessionToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
nonce: 'abcdef'
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(res.statusCode, 200, 'fresh nonce is accepted')
|
||||
d.resolve(token)
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
nonce: 'abcdef'
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(res.statusCode, 401, 'duplicate nonce is rejected')
|
||||
t.equal(body.errno, 115, 'duplicate nonce is rejected')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'auth-failure': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'timestamp header',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
return client.login()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
url = client.api.baseURL + '/account/keys'
|
||||
return client.api.Token.KeyFetchToken.fromHex(client.keyFetchToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
var now = +new Date() / 1000
|
||||
t.ok(res.headers.timestamp > now - 5, 'has timestamp header')
|
||||
t.ok(res.headers.timestamp < now + 5, 'has timestamp header')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'client adjusts to time skew',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
return client.login()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
client.api.timeOffset = 61000
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail("client should have invalid timestamp")
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 111, 'invalid timestamp')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(keys, 'client readjust to timestamp')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'oversized payload',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
return client.api.doRequest(
|
||||
'POST',
|
||||
client.api.baseURL + '/get_random_bytes',
|
||||
null,
|
||||
{ big: Buffer(1024 * 512 * 2).toString('hex')}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('request should have failed')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 113, 'payload too large')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'random bytes',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
return client.api.getRandomBytes()
|
||||
.then(
|
||||
function (x) {
|
||||
t.equal(x.data.length, 64)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'fetch /.well-known/browserid support document',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
function fetch(url) {
|
||||
return client.api.doRequest('GET', config.publicUrl + url)
|
||||
}
|
||||
return fetch('/.well-known/browserid')
|
||||
.then(
|
||||
function (doc) {
|
||||
t.ok(doc.hasOwnProperty('public-key'), 'doc has public key')
|
||||
t.ok(doc.hasOwnProperty('authentication'), 'doc has auth page')
|
||||
t.ok(doc.hasOwnProperty('provisioning'), 'doc has provisioning page')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (doc) {
|
||||
return fetch(doc['authentication'])
|
||||
.then(
|
||||
function (authPage) {
|
||||
t.ok(authPage, 'auth page can be fetched')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (doc) {
|
||||
return fetch(doc['provisioning'])
|
||||
.then(
|
||||
function (provPage) {
|
||||
t.ok(provPage, 'provisioning page can be fetched')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,265 @@
|
|||
/* 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 url = require('url')
|
||||
var Client = require('../../client')
|
||||
var TestServer = require('../test_server')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'forgot password',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var newPassword = 'ez'
|
||||
var wrapKb = null
|
||||
var kA = null
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function () {
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
wrapKb = keys.wrapKb
|
||||
kA = keys.kA
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
t.throws(function() { client.resetPassword(newPassword); })
|
||||
return resetPassword(client, code, newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(Buffer.isBuffer(keys.wrapKb), 'yep, wrapKb')
|
||||
t.notDeepEqual(wrapKb, keys.wrapKb, 'wrapKb was reset')
|
||||
t.deepEqual(kA, keys.kA, 'kA was not reset')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then( // make sure we can still login after password reset
|
||||
function () {
|
||||
return Client.login(config.publicUrl, email, newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(Buffer.isBuffer(keys.kA), 'kA exists, login after password reset')
|
||||
t.ok(Buffer.isBuffer(keys.wrapKb), 'wrapKb exists, login after password reset')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'session-create': 3,
|
||||
'pwd-reset-request': 1,
|
||||
'pwd-reset-verify-success': 1,
|
||||
'pwd-reset-verify-failure': 0,
|
||||
'pwd-reset-success': 1,
|
||||
'pwd-reset-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'forgot password limits verify attempts',
|
||||
function (t) {
|
||||
var code = null
|
||||
var email = server.uniqueEmail()
|
||||
var password = "hothamburger"
|
||||
var client = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function () {
|
||||
client = new Client(config.publicUrl)
|
||||
client.email = email
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (c) {
|
||||
code = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.reforgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (resp) {
|
||||
return server.mailbox.waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (c) {
|
||||
t.equal(code, c, 'same code as before')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 2, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 1')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 1, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 2')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 0, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 3')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with invalid token')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message, 'Invalid authentication token in request signature', 'token is now invalid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'pwd-reset-verify-failure': 3,
|
||||
'pwd-reset-success': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'recovery email link',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'something'
|
||||
var client = null
|
||||
var options = {
|
||||
redirectTo: 'https://sync.firefox.com',
|
||||
service: 'sync'
|
||||
}
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
var link = emailData.headers['x-link']
|
||||
var query = url.parse(link, true).query
|
||||
t.ok(query.token, 'uid is in link')
|
||||
t.ok(query.code, 'code is in link')
|
||||
t.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link')
|
||||
t.equal(query.service, options.service, 'service is in link')
|
||||
t.equal(query.email, email, 'email is in link')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
function resetPassword(client, code, newPassword) {
|
||||
return client.verifyPasswordResetCode(code)
|
||||
.then(function() {
|
||||
return client.resetPassword(newPassword)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/* 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 url = require('url')
|
||||
var Client = require('../../client')
|
||||
var TestServer = require('../test_server')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'create account verify with incorrect code',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.verifyEmail('00000000000000000000000000000000')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('verified email with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message.toString(), 'Invalid verification code', 'bad attempt')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false, 'account not verified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-failure': 1,
|
||||
'account-verify-request': 0,
|
||||
'account-verify-success': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'verifcation email link',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'something'
|
||||
var client = null
|
||||
var options = {
|
||||
redirectTo: 'https://sync.firefox.com',
|
||||
service: 'sync'
|
||||
}
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.mailbox.waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
var link = emailData.headers['x-link']
|
||||
var query = url.parse(link, true).query
|
||||
t.ok(/Report it: (\S+)/.exec(emailData.text)[1], 'report link exists')
|
||||
t.ok(query.uid, 'uid is in link')
|
||||
t.ok(query.code, 'code is in link')
|
||||
t.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link')
|
||||
t.equal(query.service, options.service, 'service is in link')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -0,0 +1,67 @@
|
|||
/* 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 TestServer = require('../test_server')
|
||||
var Client = require('../../client')
|
||||
var config = require('../../config').root()
|
||||
|
||||
TestServer.start(config)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'session destroy',
|
||||
function (t) {
|
||||
var email = server.uniqueEmail()
|
||||
var password = 'foobar'
|
||||
var client = null
|
||||
var sessionToken = null
|
||||
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
sessionToken = client.sessionToken
|
||||
return client.destroySession()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.equal(client.sessionToken, null, 'session token deleted')
|
||||
client.sessionToken = sessionToken
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (devices) {
|
||||
t.fail('got devices with destroyed session')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 110, 'session is invalid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'session-create': 1,
|
||||
'session-destroy': 1,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -1,231 +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 TestServer = require('../test_server')
|
||||
var crypto = require('crypto')
|
||||
var path = require('path')
|
||||
var Client = require('../../client')
|
||||
var request = require('request')
|
||||
|
||||
process.env.CONFIG_FILES = path.join(__dirname, '../config/api_error.json')
|
||||
var config = require('../../config').root()
|
||||
|
||||
function fail() { throw new Error() }
|
||||
|
||||
TestServer.start(config.publicUrl)
|
||||
.then(function main(server) {
|
||||
|
||||
test(
|
||||
'/certificate/sign inputs',
|
||||
function (t) {
|
||||
var email = crypto.randomBytes(10).toString('hex') + '@example.com'
|
||||
var password = '123456'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password, {preVerified: true})
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
// string as publicKey
|
||||
return client.sign("tada", 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'string as publicKey')
|
||||
// empty object as publicKey
|
||||
return client.sign({}, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'empty object as publicKey')
|
||||
// invalid publicKey argument
|
||||
return client.sign({ algorithm: 'RS', n: 'x', e: 'y', invalid: true }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'invalid publicKey argument')
|
||||
// undefined duration
|
||||
return client.sign({ algorithm: 'RS', n: 'x', e: 'y' }, undefined)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'undefined duration')
|
||||
// missing publicKey arguments (e)
|
||||
return client.sign({ algorithm: 'RS', n: 'x' }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'missing publicKey arguments (e)')
|
||||
// invalid algorithm
|
||||
return client.sign({ algorithm: 'NSA' }, 1000)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'invalid algorithm')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'/account/create with malformed email address',
|
||||
function (t) {
|
||||
var email = 'notAnEmailAddress'
|
||||
var password = '123456'
|
||||
return Client.create('http://127.0.0.1:9000', email, password, {preVerified: true})
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'malformed email is rejected')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'invalid redirectTo',
|
||||
function (t) {
|
||||
var api = new Client.Api(config.publicUrl)
|
||||
var email = crypto.randomBytes(10).toString('hex') + '@example.com'
|
||||
var options = {
|
||||
redirectTo: 'http://accounts.firefox.com.evil.us'
|
||||
}
|
||||
return api.accountCreate(email, '123456', options)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'bad redirectTo rejected')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return api.passwordForgotSendCode(email, options)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400, 'bad redirectTo rejected')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'signup with same email, different case',
|
||||
function (t) {
|
||||
var email = Math.random() + 'TEST@EXAMPLE.COM'
|
||||
var email2 = email.toLowerCase()
|
||||
var password = 'abcdef'
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (c) {
|
||||
return Client.create(config.publicUrl, email2, password, { preVerified: true })
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400)
|
||||
t.equal(err.errno, 101, 'Account already exists')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'the rawEmail is returned in the error on Incorrect Password errors',
|
||||
function (t) {
|
||||
var signupEmail = Math.random() + 'Test@example.com'
|
||||
var loginEmail = signupEmail.toLowerCase()
|
||||
var password = 'abcdef'
|
||||
return Client.create(config.publicUrl, signupEmail, password, { preVerified: true})
|
||||
.then(
|
||||
function (c) {
|
||||
return Client.login(config.publicUrl, loginEmail, password)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.code, 400)
|
||||
t.equal(err.errno, 103)
|
||||
t.equal(err.email, signupEmail)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'unsupported api version',
|
||||
function (t) {
|
||||
request(config.publicUrl + '/v0/account/create', function (err, res) {
|
||||
t.equal(res.statusCode, 410, 'http gone')
|
||||
t.end()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'token expiry',
|
||||
function (t) {
|
||||
// FYI config.tokenLifetimes.passwordChangeToken = -1
|
||||
var email = Math.random() + "@example.com"
|
||||
var password = 'ok'
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (c) {
|
||||
return c.changePassword('hello')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.errno, 110, 'invalid token')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'invalid authPW on account destroy',
|
||||
function (t) {
|
||||
var email = Math.random() + "@example.com"
|
||||
var password = 'ok'
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (c) {
|
||||
c.authPW = Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex')
|
||||
return c.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
fail,
|
||||
function (err) {
|
||||
t.equal(err.errno, 103)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -1,97 +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/. */
|
||||
// jshint -W069
|
||||
|
||||
var test = require('../ptaptest')
|
||||
var cp = require('child_process')
|
||||
var Client = require('../../client')
|
||||
|
||||
var config = require('../../config').root()
|
||||
|
||||
function main() {
|
||||
|
||||
test(
|
||||
'fetch /.well-known/browserid support document',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
function fetch(url) {
|
||||
return client.api.doRequest('GET', config.publicUrl + url)
|
||||
}
|
||||
return fetch('/.well-known/browserid')
|
||||
.then(
|
||||
function (doc) {
|
||||
t.ok(doc.hasOwnProperty('public-key'), 'doc has public key')
|
||||
t.ok(doc.hasOwnProperty('authentication'), 'doc has auth page')
|
||||
t.ok(doc.hasOwnProperty('provisioning'), 'doc has provisioning page')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (doc) {
|
||||
return fetch(doc['authentication'])
|
||||
.then(
|
||||
function (authPage) {
|
||||
t.ok(authPage, 'auth page can be fetched')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (doc) {
|
||||
return fetch(doc['provisioning'])
|
||||
.then(
|
||||
function (provPage) {
|
||||
t.ok(provPage, 'provisioning page can be fetched')
|
||||
return doc
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
if (server) server.kill('SIGINT')
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var server = null
|
||||
|
||||
function startServer() {
|
||||
var server = cp.spawn(
|
||||
'node',
|
||||
['../../bin/key_server.js'],
|
||||
{
|
||||
cwd: __dirname
|
||||
}
|
||||
)
|
||||
|
||||
server.stdout.on('data', process.stdout.write.bind(process.stdout))
|
||||
server.stderr.on('data', process.stderr.write.bind(process.stderr))
|
||||
return server
|
||||
}
|
||||
|
||||
function waitLoop() {
|
||||
Client.Api.heartbeat(config.publicUrl)
|
||||
.done(
|
||||
main,
|
||||
function () {
|
||||
if (!server) {
|
||||
server = startServer()
|
||||
}
|
||||
console.log('waiting...')
|
||||
setTimeout(waitLoop, 100)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
waitLoop()
|
|
@ -1,584 +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 TestServer = require('../test_server')
|
||||
var P = require('../../promise')
|
||||
var config = require('../../config').root()
|
||||
var jwcrypto = require('jwcrypto')
|
||||
|
||||
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"
|
||||
var email2 = uniqueID() + "@example.com"
|
||||
var email3 = uniqueID() + "@example.com"
|
||||
var email4 = uniqueID() + "@example.com"
|
||||
var email5 = uniqueID() + "@example.com"
|
||||
var email6 = uniqueID() + "@example.com"
|
||||
|
||||
test(
|
||||
'Create account flow',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var publicKey = {
|
||||
"algorithm":"RS",
|
||||
"n":"4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123",
|
||||
"e":"65537"
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.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 client.sign(publicKey, duration)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (cert) {
|
||||
t.equal(typeof(cert), 'string', 'cert exists')
|
||||
var payload = jwcrypto.extractComponents(cert).payload
|
||||
t.equal(payload.principal.email.split('@')[0], client.uid, 'cert has correct uid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'login-success': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Change password flow',
|
||||
function (t) {
|
||||
var email = email2
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var newPassword = 'foobar'
|
||||
var kB = null
|
||||
var client = null
|
||||
var firstAuthPW
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
firstAuthPW = x.authPW.toString('hex')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
kB = keys.kB
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.changePassword(newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.notEqual(client.authPW.toString('hex'), firstAuthPW, 'password has changed')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.deepEqual(keys.kB, kB, 'kB is preserved')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 2,
|
||||
'pwd-change-request': 1,
|
||||
'pwd-reset-success': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Login flow',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var publicKey = {
|
||||
"algorithm":"RS",
|
||||
"n":"4759385967235610503571494339196749614544606692567785790953934768202714280652973091341316862993582789079872007974809511698859885077002492642203267408776123",
|
||||
"e":"65537"
|
||||
}
|
||||
var duration = 1000 * 60 * 60 * 24
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
t.ok(client.uid, 'got a uid')
|
||||
t.equal(client.emailVerified, true, 'email is verified')
|
||||
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 client.sign(publicKey, duration)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (cert) {
|
||||
t.equal(typeof(cert), 'string', 'cert exists')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'account destroy',
|
||||
function (t) {
|
||||
var email = email3
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (devices) {
|
||||
t.equal(devices.length, 1, 'we have an account')
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail('account not destroyed')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message, 'Unknown account', 'account destroyed')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'account-destroy': 1,
|
||||
'auth-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'session destroy',
|
||||
function (t) {
|
||||
var email = email4
|
||||
var password = 'foobar'
|
||||
var client = null
|
||||
var sessionToken = null
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
sessionToken = client.sessionToken
|
||||
return client.destroySession()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.equal(client.sessionToken, null, 'session token deleted')
|
||||
client.sessionToken = sessionToken
|
||||
return client.devices()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (devices) {
|
||||
t.fail('got devices with destroyed session')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 110, 'session is invalid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'login-success': 1,
|
||||
'session-create': 1,
|
||||
'session-destroy': 1,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Unknown account should not exist',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
client.email = email5
|
||||
client.authPW = crypto.randomBytes(32)
|
||||
return client.auth()
|
||||
.then(
|
||||
function () {
|
||||
t.fail('account should not exist')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 102, 'account does not exist')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Known account should exist',
|
||||
function (t) {
|
||||
var email = email6
|
||||
var password = 'ilikepancakes'
|
||||
var client
|
||||
return Client.create(config.publicUrl, email, password, { preVerified: true })
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
t.ok(client.uid, 'account created')
|
||||
}
|
||||
).then(
|
||||
function () {
|
||||
return client.login()
|
||||
}
|
||||
).then(
|
||||
function () {
|
||||
t.ok(client.sessionToken, "client can login")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'random bytes',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
return client.api.getRandomBytes()
|
||||
.then(
|
||||
function (x) {
|
||||
t.equal(x.data.length, 64)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'oversized payload',
|
||||
function (t) {
|
||||
var client = new Client(config.publicUrl)
|
||||
return client.api.doRequest(
|
||||
'POST',
|
||||
client.api.baseURL + '/get_random_bytes',
|
||||
null,
|
||||
{ big: Buffer(1024 * 512).toString('hex')}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('request should have failed')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 113, 'payload too large')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'HAWK timestamp',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
url = c.api.baseURL + '/account/keys'
|
||||
return c.api.Token.KeyFetchToken.fromHex(c.keyFetchToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
timestamp: Math.floor(Date.now() / 1000) - 61
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(body.errno, 111, 'invalid auth timestamp')
|
||||
var now = +new Date() / 1000
|
||||
t.ok(body.serverTime > now - 5, 'includes current time')
|
||||
t.ok(body.serverTime < now + 5, 'includes current time')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'auth-failure': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'HAWK nonce re-use',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
url = c.api.baseURL + '/account/devices'
|
||||
return c.api.Token.SessionToken.fromHex(c.sessionToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
nonce: 'abcdef'
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(res.statusCode, 200, 'fresh nonce is accepted')
|
||||
d.resolve(token)
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
nonce: 'abcdef'
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
t.equal(res.statusCode, 401, 'duplicate nonce is rejected')
|
||||
t.equal(body.errno, 115, 'duplicate nonce is rejected')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'auth-failure': 1
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'timestamp header',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var url = null
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
url = c.api.baseURL + '/account/keys'
|
||||
return c.api.Token.KeyFetchToken.fromHex(c.keyFetchToken)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (token) {
|
||||
var d = P.defer()
|
||||
var hawk = require('hawk')
|
||||
var request = require('request')
|
||||
var method = 'GET'
|
||||
var verify = {
|
||||
credentials: token,
|
||||
timestamp: Math.floor(Date.now() / 1000)
|
||||
}
|
||||
var headers = {
|
||||
Authorization: hawk.client.header(url, method, verify).field
|
||||
}
|
||||
request(
|
||||
{
|
||||
method: method,
|
||||
url: url,
|
||||
headers: headers,
|
||||
json: true
|
||||
},
|
||||
function (err, res, body) {
|
||||
if (err) {
|
||||
d.reject(err)
|
||||
} else {
|
||||
var now = +new Date() / 1000
|
||||
t.ok(res.headers.timestamp > now - 5, 'has timestamp header')
|
||||
t.ok(res.headers.timestamp < now + 5, 'has timestamp header')
|
||||
d.resolve()
|
||||
}
|
||||
}
|
||||
)
|
||||
return d.promise
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'client adjusts to time skew',
|
||||
function (t) {
|
||||
var email = email1
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
c.api.timeOffset = 61000;
|
||||
return c.keys();
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail("client should have invalid timestamp")
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.errno, 111, 'invalid timestamp')
|
||||
return client.keys();
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(keys, 'client readjust to timestamp')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
server.stop()
|
||||
t.end()
|
||||
}
|
||||
)
|
||||
})
|
|
@ -1,643 +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 TestServer = require('../test_server')
|
||||
var path = require('path')
|
||||
var P = require('../../promise')
|
||||
var Client = require('../../client')
|
||||
var crypto = require('crypto')
|
||||
var url = require('url')
|
||||
|
||||
process.env.CONFIG_FILES = path.join(__dirname, '../config/verification.json')
|
||||
var config = require('../../config').root()
|
||||
|
||||
function uniqueID() {
|
||||
return crypto.randomBytes(10).toString('hex');
|
||||
}
|
||||
|
||||
TestServer.start(config.publicUrl)
|
||||
.done(function main(server) {
|
||||
|
||||
test(
|
||||
'create account',
|
||||
function (t) {
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var verifyCode = null
|
||||
var keyFetchToken = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.fail('got keys before verifying email')
|
||||
},
|
||||
function (err) {
|
||||
keyFetchToken = client.keyFetchToken
|
||||
t.ok(client.keyFetchToken, 'retained keyFetchToken')
|
||||
t.equal(err.message, 'Unverified account', 'account is unverified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
verifyCode = code
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
t.equal(code, verifyCode, 'verify codes are the same')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.verifyEmail(verifyCode)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, true)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.equal(keyFetchToken, client.keyFetchToken, 'reusing keyFetchToken')
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-request': 1,
|
||||
'account-verify-success': 1,
|
||||
'account-verify-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create account verify with incorrect code',
|
||||
function (t) {
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.verifyEmail('00000000000000000000000000000000')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('verified email with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message.toString(), 'Invalid verification code', 'bad attempt')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.emailStatus()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (status) {
|
||||
t.equal(status.verified, false, 'account not verified')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-failure': 1,
|
||||
'account-verify-request': 0,
|
||||
'account-verify-success': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create account with service identifier',
|
||||
function (t) {
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
var options = { service: 'abcdef' }
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], 'abcdef')
|
||||
client.options.service = '123456'
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], '123456')
|
||||
client.options.service = null
|
||||
return client.requestVerifyEmail()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.equal(emailData.headers['x-service-id'], undefined)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'account-verify-request': 2,
|
||||
'account-verify-success': 0,
|
||||
'account-verify-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'forgot password',
|
||||
function (t) {
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var newPassword = 'ez'
|
||||
var wrapKb = null
|
||||
var kA = null
|
||||
var client = null
|
||||
return createFreshAccount(email, password)
|
||||
.then(
|
||||
function () {
|
||||
return Client.login(config.publicUrl, email, password)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
wrapKb = keys.wrapKb
|
||||
kA = keys.kA
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
t.throws(function() { client.resetPassword(newPassword); })
|
||||
return resetPassword(client, code, newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(Buffer.isBuffer(keys.wrapKb), 'yep, wrapKb')
|
||||
t.notDeepEqual(wrapKb, keys.wrapKb, 'wrapKb was reset')
|
||||
t.deepEqual(kA, keys.kA, 'kA was not reset')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then( // make sure we can still login after password reset
|
||||
function () {
|
||||
return Client.login(config.publicUrl, email, newPassword)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
return client.keys()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (keys) {
|
||||
t.ok(Buffer.isBuffer(keys.kA), 'kA exists, login after password reset')
|
||||
t.ok(Buffer.isBuffer(keys.wrapKb), 'wrapKb exists, login after password reset')
|
||||
t.equal(client.kB.length, 32, 'kB exists, has the right length')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'account-create-success': 1,
|
||||
'session-create': 3,
|
||||
'pwd-reset-request': 1,
|
||||
'pwd-reset-verify-success': 1,
|
||||
'pwd-reset-verify-failure': 0,
|
||||
'pwd-reset-success': 1,
|
||||
'pwd-reset-failure': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'forgot password limits verify attempts',
|
||||
function (t) {
|
||||
var code = null
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = "hothamburger"
|
||||
var client = null
|
||||
return createFreshAccount(email, password)
|
||||
.then(
|
||||
function () {
|
||||
client = new Client(config.publicUrl)
|
||||
client.email = email
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (c) {
|
||||
code = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.reforgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (resp) {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (c) {
|
||||
t.equal(code, c, 'same code as before')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 2, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 1')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 1, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 2')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with bad code')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.tries, 0, 'used a try')
|
||||
t.equal(err.message, 'Invalid verification code', 'bad attempt 3')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return resetPassword(client, '00000000000000000000000000000000', 'password')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
t.fail('reset password with invalid token')
|
||||
},
|
||||
function (err) {
|
||||
t.equal(err.message, 'Invalid authentication token in request signature', 'token is now invalid')
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return server.assertLogs(t, {
|
||||
'pwd-reset-verify-failure': 3,
|
||||
'pwd-reset-success': 0
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create account allows localization of emails',
|
||||
function (t) {
|
||||
var email = uniqueID() +'@restmail.net'
|
||||
var password = 'allyourbasearebelongtous'
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.assert(emailData.text.indexOf('Welcome') !== -1, 'is en')
|
||||
t.assert(emailData.text.indexOf('GDay') === -1, 'not en-AU')
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return Client.create(config.publicUrl, email, password, { lang: 'en-AU' })
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
t.assert(emailData.text.indexOf('Welcome') === -1, 'not en')
|
||||
t.assert(emailData.text.indexOf('GDay') !== -1, 'is en-AU')
|
||||
return client.destroyAccount()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'verifcation email link',
|
||||
function (t) {
|
||||
var email = uniqueID() + '@restmail.net'
|
||||
var password = 'something'
|
||||
var client = null
|
||||
var options = {
|
||||
redirectTo: 'https://sync.firefox.com',
|
||||
service: 'sync'
|
||||
}
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
var link = emailData.headers['x-link']
|
||||
var query = url.parse(link, true).query
|
||||
t.ok(/Report it: (\S+)/.exec(emailData.text)[1], 'report link exists')
|
||||
t.ok(query.uid, 'uid is in link')
|
||||
t.ok(query.code, 'code is in link')
|
||||
t.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link')
|
||||
t.equal(query.service, options.service, 'service is in link')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'recovery email link',
|
||||
function (t) {
|
||||
var email = uniqueID() + '@restmail.net'
|
||||
var password = 'something'
|
||||
var client = null
|
||||
var options = {
|
||||
redirectTo: 'https://sync.firefox.com',
|
||||
service: 'sync'
|
||||
}
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then(
|
||||
function (c) {
|
||||
client = c
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return client.forgotPassword()
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForEmail(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (emailData) {
|
||||
var link = emailData.headers['x-link']
|
||||
var query = url.parse(link, true).query
|
||||
t.ok(query.token, 'uid is in link')
|
||||
t.ok(query.code, 'code is in link')
|
||||
t.equal(query.redirectTo, options.redirectTo, 'redirectTo is in link')
|
||||
t.equal(query.service, options.service, 'service is in link')
|
||||
t.equal(query.email, email, 'email is in link')
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'teardown',
|
||||
function (t) {
|
||||
t.end()
|
||||
server.stop()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var request = require('request')
|
||||
|
||||
// This test helper creates fresh account for the given email and password.
|
||||
function createFreshAccount(email, password) {
|
||||
var client = null
|
||||
return Client.create(config.publicUrl, email, password)
|
||||
.then(
|
||||
function (x) {
|
||||
client = x
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function () {
|
||||
return waitForCode(email)
|
||||
}
|
||||
)
|
||||
.then(
|
||||
function (code) {
|
||||
return client.verifyEmail(code)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function waitForCode(email) {
|
||||
return waitForEmail(email)
|
||||
.then(
|
||||
function (emailData) {
|
||||
return emailData.headers['x-verify-code'] || emailData.headers['x-recovery-code']
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function loop(name, tries, cb) {
|
||||
var url = 'http://' + config.smtp.api.host + ':' + config.smtp.api.port + '/mail/' + name
|
||||
console.log('checking mail', url)
|
||||
request({ url: url, method: 'GET' },
|
||||
function (err, res, body) {
|
||||
console.log('mail status', res && res.statusCode, 'tries', tries)
|
||||
var json = null
|
||||
try {
|
||||
json = JSON.parse(body)[0]
|
||||
}
|
||||
catch (e) {
|
||||
return cb(e)
|
||||
}
|
||||
|
||||
if(!json) {
|
||||
if (tries === 0) {
|
||||
return cb(new Error('could not get mail for ' + url))
|
||||
}
|
||||
return setTimeout(loop.bind(null, name, --tries, cb), 1000)
|
||||
}
|
||||
console.log('deleting mail', url)
|
||||
request({ url: url, method: 'DELETE' },
|
||||
function (err, res, body) {
|
||||
cb(err, json)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function waitForEmail(email) {
|
||||
var d = P.defer()
|
||||
loop(email.split('@')[0], 20, function (err, json) {
|
||||
return err ? d.reject(err) : d.resolve(json)
|
||||
})
|
||||
return d.promise
|
||||
}
|
||||
|
||||
|
||||
function resetPassword(client, code, newPassword) {
|
||||
return client.verifyPasswordResetCode(code)
|
||||
.then(function() {
|
||||
return client.resetPassword(newPassword)
|
||||
})
|
||||
}
|
|
@ -3,12 +3,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var cp = require('child_process')
|
||||
var crypto = require('crypto')
|
||||
var P = require('../promise')
|
||||
var request = require('request')
|
||||
var split = require('binary-split')
|
||||
var through = require('through')
|
||||
var mailbox = require('./mailbox')
|
||||
|
||||
function TestServer() {
|
||||
function TestServer(config) {
|
||||
this.server = cp.spawn(
|
||||
'node',
|
||||
['./key_server_stub.js'],
|
||||
|
@ -16,6 +18,7 @@ function TestServer() {
|
|||
cwd: __dirname
|
||||
}
|
||||
)
|
||||
this.mailbox = mailbox(config.smtp.api.host, config.smtp.api.port)
|
||||
this.logs = {}
|
||||
this.server.stderr
|
||||
.pipe(split())
|
||||
|
@ -70,7 +73,8 @@ TestServer.prototype.assertLogs = function (t, spec) {
|
|||
return P()
|
||||
}
|
||||
|
||||
function waitLoop(url, cb) {
|
||||
function waitLoop(config, cb) {
|
||||
var url = config.publicUrl
|
||||
request(
|
||||
url + '/__heartbeat__',
|
||||
function (err, res, body) {
|
||||
|
@ -82,19 +86,19 @@ function waitLoop(url, cb) {
|
|||
}
|
||||
if (TestServer.server.fake) {
|
||||
console.log('starting...')
|
||||
TestServer.server = new TestServer()
|
||||
TestServer.server = new TestServer(config)
|
||||
}
|
||||
console.log('waiting...')
|
||||
return setTimeout(waitLoop.bind(null, url, cb), 100)
|
||||
return setTimeout(waitLoop.bind(null, config, cb), 100)
|
||||
}
|
||||
cb()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
TestServer.start = function (url) {
|
||||
TestServer.start = function (config) {
|
||||
var d = P.defer()
|
||||
waitLoop(url, function (err) {
|
||||
waitLoop(config, function (err) {
|
||||
return err ? d.reject(err) : d.resolve(TestServer.server)
|
||||
})
|
||||
return d.promise
|
||||
|
@ -105,4 +109,8 @@ TestServer.prototype.stop = function () {
|
|||
this.mail.kill()
|
||||
}
|
||||
|
||||
TestServer.prototype.uniqueEmail = function () {
|
||||
return crypto.randomBytes(10).toString('hex') + '@restmail.net'
|
||||
}
|
||||
|
||||
module.exports = TestServer
|
||||
|
|
Загрузка…
Ссылка в новой задаче