* 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:
Danny Coates 2014-01-17 17:31:27 -08:00
Родитель 8c1263055a
Коммит 8f3509fc7e
27 изменённых файлов: 1772 добавлений и 1564 удалений

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

@ -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()
}
)
})

64
test/mailbox.js Normal file
Просмотреть файл

@ -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()
}
)
})

175
test/remote/flow_tests.js Normal file
Просмотреть файл

@ -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()
}
)
})

402
test/remote/misc_tests.js Normal file
Просмотреть файл

@ -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