feat(recovery): update delete recovery key and get recovery key endpoints (#2518), r=@rfk

This commit is contained in:
Vijay Budhram 2018-07-17 15:20:40 -04:00 коммит произвёл GitHub
Родитель b6908b9fb0
Коммит 4d109a05a7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 657 добавлений и 208 удалений

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

@ -58,9 +58,11 @@ see [`mozilla/fxa-js-client`](https://github.com/mozilla/fxa-js-client).
* [Recovery codes](#recovery-codes)
* [GET /recoveryCodes (:lock: sessionToken)](#get-recoverycodes)
* [POST /session/verify/recoveryCode (:lock: sessionToken)](#post-sessionverifyrecoverycode)
* [Recovery keys](#recovery-keys)
* [POST /recoveryKeys (:lock: sessionToken)](#post-recoverykeys)
* [GET /recoveryKeys/{recoveryKeyId} (:lock: accountResetToken)](#get-recoverykeysrecoverykeyid)
* [Recovery key](#recovery-key)
* [POST /recoveryKey (:lock: sessionToken)](#post-recoverykey)
* [GET /recoveryKey/:recoveryKeyId (:lock: accountResetToken)](#get-recoverykey)
* [POST /recoveryKey/exists (:lock: sessionToken)](#post-recoverykeyexists)
* [DELETE /recoveryKey (:lock: sessionToken)](#delete-recoverykey)
* [Session](#session)
* [POST /session/destroy (:lock: sessionToken)](#post-sessiondestroy)
* [POST /session/reauth (:lock: sessionToken)](#post-sessionreauth)
@ -285,6 +287,10 @@ for `code` and `errno` are:
Recovery code not found.
* `code: 400, errno: 157`:
Unavailable device command.
* `code: 400, errno: 158`:
Recovery key not found.
* `code: 400, errno: 159`:
Recovery key is not valid.
* `code: 503, errno: 201`:
Service unavailable
* `code: 503, errno: 202`:
@ -2346,12 +2352,12 @@ Verify a session using a recovery code.
<!--end-response-body-post-sessionverifyrecoverycode-remaining-->
### Recovery keys
### Recovery key
#### POST /recoveryKeys
#### POST /recoveryKey
:lock: HAWK-authenticated with session token
<!--begin-route-post-recoverykeys-->
<!--begin-route-post-recoverykey-->
Creates a new recovery key for a user.
Recovery keys are one-time-use tokens
@ -2359,30 +2365,68 @@ that can be used to recover the user's kB
if they forget their password.
For more details, see the
[recovery keys](recovery_keys.md) docs.
<!--end-route-post-recoverykeys-->
<!--end-route-post-recoverykey-->
##### Request body
* `recoveryKeyId`: *validators.recoveryKeyId*
<!--begin-request-body-post-recoverykeys-recoveryKeyId-->
<!--begin-request-body-post-recoverykey-recoveryKeyId-->
A unique identifier for this recovery key, derived from the key via HKDF.
<!--end-request-body-post-recoverykeys-recoveryKeyId-->
<!--end-request-body-post-recoverykey-recoveryKeyId-->
* `recoveryData`: *validators.recoveryData*
<!--begin-request-body-post-recoverykeys-recoveryData-->
<!--begin-request-body-post-recoverykey-recoveryData-->
An encrypted bundle containing the user's kB.
<!--end-request-body-post-recoverykeys-recoveryData-->
<!--end-request-body-post-recoverykey-recoveryData-->
#### GET /recoveryKeys/{recoveryKeyId}
#### GET /recoveryKey/:recoveryKeyId
:lock: HAWK-authenticated with account reset token
<!--begin-route-get-recoverykeysrecoverykeyid-->
<!--begin-route-get-recoverykeyrecoverykeyid-->
Retrieve the account recovery data associated with the given recovery key.
<!--end-route-get-recoverykeysrecoverykeyid-->
<!--end-route-get-recoverykeyrecoverykeyid-->
##### Response body
* `recoveryData`: *string*
<!--begin-response-body-post-recoverykeyrecoverykeyid-recoverydata-->
<!--end-response-body-post-recoverykeyrecoverykeyid-recoverydata-->
#### POST /recoveryKey/exists
:lock: HAWK-authenticated with session token
<!--begin-route-post-recoverykeyexists-->
This route checks to see if given user has setup an account recovery key.
When used during the password reset flow, an email can be provided (instead
of a sessionToken) to check for the status. However, when
using an email, the request is rate limited.
<!--end-route-post-recoverykeyexists-->
##### Request body
* `email`: *validators.email.required*
<!--begin-request-body-post-recoverykeyexists-email-->
<!--end-request-body-post-recoverykeyexists-email-->
##### Response body
* `status`: *boolean, required*
<!--begin-response-body-post-recoverykeyexists-email-->
<!--end-response-body-post-recoverykeyexists-email-->
#### DELETE /recoveryKey
<!--begin-route-delete-recoverykey-->
This route remove an account's recovery key. When the key is
removed, it can no longer be used to restore an account's kB.
<!--end-route-delete-recoverykey-->
### Session

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

@ -21,7 +21,7 @@ Creating a new recovery key involves the following steps:
* recover-data = JWE(recover-enc, {"alg": "dir", "enc": "A256GCM", "kid": recover-kid}, kB)
* FxA web-content submits recovery data to FxA server for storage,
associating it with the fingerprint (recover-kid)
* `POST /recoveryKeys`, providing `recoveryKeyId` and `recoveryData` in the request body.
* `POST /recoveryKey`, providing `recoveryKeyId` and `recoveryData` in the request body.
This scheme ensures someone in posession of the recovery key,
can request the encrypted recovery data
@ -42,7 +42,7 @@ as follows:
* FxA web-content uses the recovery code to derive the fingerprint
and encryption key (recover-kid and recover-enc as defined above).
* FxA web-content requests recover-data from FxA server, providing recover-kid.
* `GET /recoveryKeys/:recoveryKeyId`, authenticated with `accountResetToken`.
* `GET /recoveryKey/:recoveryKeyId`, authenticated with `accountResetToken`.
* Providing the `:recoveryKeyId` here proves that the user posesses the recovery key,
while the `accountResetToken` proves that they control the email address
of the account.

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

@ -1288,7 +1288,7 @@ module.exports = (
}
SAFE_URLS.createRecoveryKey = new SafeUrl(
'/account/:uid/recoveryKeys',
'/account/:uid/recoveryKey',
'db.createRecoveryKey'
)
DB.prototype.createRecoveryKey = function (uid, recoveryKeyId, recoveryData) {
@ -1298,23 +1298,29 @@ module.exports = (
}
SAFE_URLS.getRecoveryKey = new SafeUrl(
'/account/:uid/recoveryKeys/:recoveryKeyId',
'/account/:uid/recoveryKey',
'db.getRecoveryKey'
)
DB.prototype.getRecoveryKey = function (uid, recoveryKeyId) {
DB.prototype.getRecoveryKey = function (uid) {
log.trace({op: 'DB.getRecoveryKey', uid})
return this.pool.get(SAFE_URLS.getRecoveryKey, { uid, recoveryKeyId })
return this.pool.get(SAFE_URLS.getRecoveryKey, {uid})
.catch(err => {
if (isNotFoundError(err)) {
throw error.recoveryKeyNotFound()
}
throw err
})
}
SAFE_URLS.deleteRecoveryKey = new SafeUrl(
'/account/:uid/recoveryKeys/:recoveryKeyId',
'db.createRecoveryKey'
'/account/:uid/recoveryKey',
'db.deleteRecoveryKey'
)
DB.prototype.deleteRecoveryKey = function (uid, recoveryKeyId) {
DB.prototype.deleteRecoveryKey = function (uid) {
log.trace({op: 'DB.deleteRecoveryKey', uid})
return this.pool.del(SAFE_URLS.deleteRecoveryKey, { uid, recoveryKeyId })
return this.pool.del(SAFE_URLS.deleteRecoveryKey, { uid })
}

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

@ -70,6 +70,9 @@ var ERRNO = {
RECOVERY_CODE_NOT_FOUND: 156,
DEVICE_COMMAND_UNAVAILABLE: 157,
RECOVERY_KEY_NOT_FOUND: 158,
RECOVERY_KEY_INVALID: 159,
SERVER_BUSY: 201,
FEATURE_NOT_ENABLED: 202,
BACKEND_SERVICE_FAILURE: 203,
@ -794,6 +797,24 @@ AppError.unavailableDeviceCommand = () => {
})
}
AppError.recoveryKeyNotFound = () => {
return new AppError({
code: 400,
error: 'Bad Request',
errno: ERRNO.RECOVERY_KEY_NOT_FOUND,
message: 'Recovery key not found.'
})
}
AppError.recoveryKeyInvalid = () => {
return new AppError({
code: 400,
error: 'Bad Request',
errno: ERRNO.RECOVERY_KEY_INVALID,
message: 'Recovery key is not valid.'
})
}
AppError.backendServiceFailure = (service, operation) => {
return new AppError({
code: 500,

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

@ -1058,7 +1058,7 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push)
function deleteRecoveryKey() {
if (recoveryKeyId) {
return db.deleteRecoveryKey(account.uid, recoveryKeyId)
return db.deleteRecoveryKey(account.uid)
}
return P.resolve()

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

@ -55,7 +55,7 @@ module.exports = function (
const unblockCodes = require('./unblock-codes')(log, db, mailer, config.signinUnblock, customs)
const totp = require('./totp')(log, db, mailer, customs, config.totp)
const recoveryCodes = require('./recovery-codes')(log, db, config.totp, customs, mailer)
const recoveryKeys = require('./recovery-keys')(log, db, Password, config.verifierVersion, customs)
const recoveryKey = require('./recovery-key')(log, db, Password, config.verifierVersion, customs)
const util = require('./util')(
log,
config,
@ -79,7 +79,7 @@ module.exports = function (
totp,
unblockCodes,
util,
recoveryKeys
recoveryKey
)
v1Routes.forEach(r => { r.path = basePath + '/v1' + r.path })
defaults.forEach(r => { r.path = basePath + r.path })

190
lib/routes/recovery-key.js Normal file
Просмотреть файл

@ -0,0 +1,190 @@
/* 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/. */
'use strict'
const errors = require('../error')
const validators = require('./validators')
const isA = require('joi')
const butil = require('../crypto/butil')
module.exports = (log, db, Password, verifierVersion, customs) => {
return [
{
method: 'POST',
path: '/recoveryKey',
options: {
auth: {
strategy: 'sessionToken'
},
validate: {
payload: {
recoveryKeyId: validators.recoveryKeyId,
recoveryData: validators.recoveryData
}
}
},
handler: async function (request) {
log.begin('createRecoveryKey', request)
const uid = request.auth.credentials.uid
const sessionToken = request.auth.credentials
const {recoveryKeyId, recoveryData} = request.payload
return createRecoveryKey()
.then(emitMetrics)
.then(() => { return {} })
function createRecoveryKey() {
if (sessionToken.tokenVerificationId) {
throw errors.unverifiedSession()
}
return db.createRecoveryKey(uid, recoveryKeyId, recoveryData)
}
function emitMetrics() {
log.info({
op: 'account.recoveryKey.created',
uid
})
return request.emitMetricsEvent('recoveryKey.created', {uid})
}
}
},
{
method: 'GET',
path: '/recoveryKey/{recoveryKeyId}',
options: {
auth: {
strategy: 'accountResetToken'
},
validate: {
params: {
recoveryKeyId: validators.recoveryKeyId
}
}
},
handler: async function (request) {
log.begin('getRecoveryKey', request)
const uid = request.auth.credentials.uid
const ip = request.app.clientAddress
const recoveryKeyId = request.params.recoveryKeyId
let recoveryData
return customs.checkAuthenticated('getRecoveryKey', ip, uid)
.then(getRecoveryKey)
.then(() => { return {recoveryData} })
function getRecoveryKey() {
return db.getRecoveryKey(uid)
.then((res) => {
// `db.getRecoveryKey` doesn't require recoveryKeyId to retrieve
// the recovery bundle, however, we should perform a security
// check to ensure that the returned bundle contains the recoveryKeyId.
if (! butil.buffersAreEqual(res.recoveryKeyId, recoveryKeyId)) {
throw errors.recoveryKeyInvalid()
}
recoveryData = res.recoveryData
})
}
}
},
{
method: 'POST',
path: '/recoveryKey/exists',
options: {
auth: {
mode: 'optional',
strategy: 'sessionToken'
},
validate: {
payload: {
email: validators.email().optional()
}
},
response: {
schema: {
exists: isA.boolean().required()
}
}
},
handler(request) {
log.begin('recoveryKeyExists', request)
const email = request.payload.email
let exists = false, uid
if (request.auth.credentials) {
uid = request.auth.credentials.uid
}
return Promise.resolve()
.then(() => {
if (! uid) {
// If not using a sessionToken, an email is required to check
// for a recovery key. This occurs when checking from the
// password reset page and allows us to redirect the user to either
// the regular password reset or account recovery password reset.
if (! email) {
throw errors.missingRequestParameter('email')
}
return customs.check(request, email, 'recoveryKeyExists')
.then(() => db.accountRecord(email))
.then((result) => uid = result.uid)
}
// When checking from `/settings` a sessionToken is required and the
// request is not rate limited.
})
.then(() => {
return db.getRecoveryKey(uid)
.then((recoveryKey) => {
if (recoveryKey) {
exists = true
}
}, (err) => {
if (err.errno === errors.ERRNO.RECOVERY_KEY_NOT_FOUND) {
exists = false
return
}
throw err
})
})
.then(() => {
return {exists}
})
}
},
{
method: 'DELETE',
path: '/recoveryKey',
options: {
auth: {
strategy: 'sessionToken'
}
},
handler(request) {
log.begin('recoveryKeyDelete', request)
return Promise.resolve()
.then(() => {
const sessionToken = request.auth.credentials
if (sessionToken.tokenVerificationId) {
throw errors.unverifiedSession()
}
return db.deleteRecoveryKey(sessionToken.uid)
.then(() => {
return {}
})
})
}
}
]
}

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

@ -1,87 +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/. */
'use strict'
const errors = require('../error')
const validators = require('./validators')
module.exports = (log, db, Password, verifierVersion, customs) => {
return [
{
method: 'POST',
path: '/recoveryKeys',
options: {
auth: {
strategy: 'sessionToken'
},
validate: {
payload: {
recoveryKeyId: validators.recoveryKeyId,
recoveryData: validators.recoveryData
}
}
},
handler: async function (request) {
log.begin('createRecoveryKey', request)
const uid = request.auth.credentials.uid
const sessionToken = request.auth.credentials
const {recoveryKeyId, recoveryData} = request.payload
return createRecoveryKey()
.then(emitMetrics)
.then(() => { return {} })
function createRecoveryKey() {
if (sessionToken.tokenVerificationId) {
throw errors.unverifiedSession()
}
return db.createRecoveryKey(uid, recoveryKeyId, recoveryData)
}
function emitMetrics() {
log.info({
op: 'account.recoveryKey.created',
uid
})
return request.emitMetricsEvent('recoveryKey.created', {uid})
}
}
},
{
method: 'GET',
path: '/recoveryKeys/{recoveryKeyId}',
options: {
auth: {
strategy: 'accountResetToken'
},
validate: {
params: {
recoveryKeyId: validators.recoveryKeyId
}
}
},
handler: async function (request) {
log.begin('getRecoveryKey', request)
const uid = request.auth.credentials.uid
const ip = request.app.clientAddress
const recoveryKeyId = request.params.recoveryKeyId
let recoveryData
return customs.checkAuthenticated('getRecoveryKey', ip, uid)
.then(getRecoveryKey)
.then(() => { return {recoveryData} })
function getRecoveryKey() {
return db.getRecoveryKey(uid, recoveryKeyId)
.then((res) => recoveryData = res.recoveryData)
}
}
}
]
}

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

@ -610,6 +610,7 @@ module.exports = function (log, config) {
Mailer.prototype.recoveryEmail = function (message) {
var templateName = 'recoveryEmail'
var query = {
uid: message.uid,
token: message.token,
code: message.code,
email: message.email

180
npm-shrinkwrap.json сгенерированный
Просмотреть файл

@ -2727,7 +2727,7 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"fxa-auth-db-mysql": {
"version": "git+https://github.com/mozilla/fxa-auth-db-mysql.git#b5c0f0e34b41c9464528f01ddd4bc8a28ed44571",
"version": "git+https://github.com/mozilla/fxa-auth-db-mysql.git#29b9b4bc65784659ff103d77a390dc043e41992c",
"from": "git+https://github.com/mozilla/fxa-auth-db-mysql.git#master",
"dev": true,
"requires": {
@ -2772,9 +2772,9 @@
"dev": true
},
"JSONStream": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz",
"integrity": "sha1-wQI3G27Dp887hHygDCC7D85Mbeo=",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz",
"integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==",
"requires": {
"jsonparse": "^1.2.0",
"through": ">=2.2.7 <3"
@ -2786,9 +2786,9 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"acorn": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz",
"integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ=="
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz",
"integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ=="
},
"acorn-jsx": {
"version": "3.0.1",
@ -2811,9 +2811,9 @@
"integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo="
},
"agent-base": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz",
"integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
"dev": true,
"requires": {
"es6-promisify": "^5.0.0"
@ -2840,6 +2840,7 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@ -2978,9 +2979,9 @@
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"optional": true,
"requires": {
@ -3036,9 +3037,9 @@
"integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8="
},
"buffer-from": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
"integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA=="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
"integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ=="
},
"builtin-modules": {
"version": "1.1.1",
@ -3539,12 +3540,12 @@
}
},
"dtrace-provider": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.6.tgz",
"integrity": "sha1-QooiOv4DQl0s1tY0f99AxmkDVj0=",
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz",
"integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=",
"optional": true,
"requires": {
"nan": "^2.3.3"
"nan": "^2.10.0"
}
},
"ecc-jsbn": {
@ -3567,17 +3568,17 @@
}
},
"error-ex": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz",
"integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"requires": {
"is-arrayish": "^0.2.1"
}
},
"es5-ext": {
"version": "0.10.42",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz",
"integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==",
"version": "0.10.45",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz",
"integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.1",
@ -3741,7 +3742,7 @@
},
"eslint-plugin-fxa": {
"version": "git+https://github.com/mozilla/eslint-plugin-fxa.git#e082927b4c6dc17d21414e35f4c94312adbaba92",
"from": "eslint-plugin-fxa@git+https://github.com/mozilla/eslint-plugin-fxa.git#e082927b4c6dc17d21414e35f4c94312adbaba92"
"from": "git+https://github.com/mozilla/eslint-plugin-fxa.git#master"
},
"espree": {
"version": "3.5.4",
@ -3886,12 +3887,14 @@
}
},
"find-my-way": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-1.12.0.tgz",
"integrity": "sha512-d7wZ0IeijAZDA/gvHjCNxxRTDCn5j9hnugcgEbNzYhofbDfogGhyRu93mtcJoAxeB1zemWTz9JB2JzNOar/qbA==",
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-1.15.1.tgz",
"integrity": "sha512-cwR1IxkB1JIIGxWpX3TQC1U/51htT4dps536rno7fkszeSSevvZGkl1dpIANRNq+X6/VDSF/S4JAuDPSTepHBA==",
"dev": true,
"requires": {
"fast-decode-uri-component": "^1.0.0"
"fast-decode-uri-component": "^1.0.0",
"safe-regex": "^1.1.0",
"semver-store": "^0.3.0"
}
},
"find-up": {
@ -4476,9 +4479,9 @@
"integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk="
},
"hosted-git-info": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
"integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw=="
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
},
"hpack.js": {
"version": "2.1.6",
@ -4528,9 +4531,9 @@
}
},
"ignore": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz",
"integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg=="
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
"integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug=="
},
"imurmurhash": {
"version": "0.1.4",
@ -4633,7 +4636,8 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"optional": true
},
"is-builtin-module": {
"version": "1.0.0",
@ -4862,6 +4866,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
@ -5126,7 +5131,8 @@
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"optional": true
},
"loud-rejection": {
"version": "1.6.0",
@ -5435,12 +5441,12 @@
},
"dependencies": {
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
"integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"dev": true,
"requires": {
"lodash": "^4.14.0"
"lodash": "^4.17.10"
}
}
}
@ -7029,9 +7035,9 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"p-limit": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz",
"integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
"requires": {
"p-try": "^1.0.0"
}
@ -7292,7 +7298,8 @@
"repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
"optional": true
},
"repeating": {
"version": "2.0.1",
@ -7333,9 +7340,9 @@
},
"dependencies": {
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
}
}
@ -7405,9 +7412,9 @@
},
"dependencies": {
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"dev": true
}
}
@ -7439,9 +7446,9 @@
"integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg=="
},
"restify-errors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-6.0.0.tgz",
"integrity": "sha512-Ytrpbf0KQ2h7TSrcCqmtA8dybaLv/+H9GHfayBiewwpNuzTIcomvLOfRzR0e4u2VdUsqggbWBrNogwKum0uQIQ==",
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-6.1.1.tgz",
"integrity": "sha512-QSwjp1b0pHB8QQQwqaPJu+VroGHAGX+HeHqz50awIb8334SAENCKeCI1VAhN099n4h0UVNupJ99ozx0pkHdqew==",
"requires": {
"assert-plus": "^1.0.0",
"lodash": "^4.17.4",
@ -7450,9 +7457,9 @@
}
},
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
}
}
},
@ -7477,6 +7484,12 @@
"onetime": "^1.0.0"
}
},
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"dev": true
},
"right-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
@ -7527,11 +7540,20 @@
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"safe-json-stringify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.1.0.tgz",
"integrity": "sha512-EzBtUaFH9bHYPc69wqjp0efJI/DPNHdFbGE3uIMn4sVbO0zx8vZ8cG4WKxQfOpUOKsQyGBiT2mTqnCw+6nLswA==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
"optional": true
},
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
"ret": "~0.1.10"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -7571,6 +7593,12 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"semver-store": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz",
"integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==",
"dev": true
},
"shelljs": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz",
@ -7721,9 +7749,9 @@
"dev": true
},
"sshpk": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
"integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"dev": true,
"requires": {
"asn1": "~0.2.3",
@ -7733,6 +7761,7 @@
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
@ -7773,9 +7802,9 @@
}
},
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=",
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
"integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
"dev": true
},
"strip-ansi": {
@ -8012,18 +8041,11 @@
"dev": true
},
"util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.0.tgz",
"integrity": "sha512-5n12uMzKCjvB2HPFHnbQSjaqAa98L5iIXmHrZCLavuZVe0qe/SJGbDGWlpaHk5lnBkWRDO+dRu1/PgmUYKPPTw==",
"requires": {
"inherits": "2.0.1"
},
"dependencies": {
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
}
"inherits": "2.0.3"
}
},
"util-deprecate": {

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

@ -866,7 +866,7 @@ module.exports = config => {
.then((token) => {
return this.doRequest(
'POST',
this.baseURL + '/recoveryKeys',
this.baseURL + '/recoveryKey',
token,
{
recoveryKeyId,
@ -881,7 +881,39 @@ module.exports = config => {
.then((token) => {
return this.doRequest(
'GET',
`${this.baseURL}/recoveryKeys/${recoveryKeyId}`,
`${this.baseURL}/recoveryKey/${recoveryKeyId}`,
token
)
})
}
ClientApi.prototype.getRecoveryKeyExistsWithSession = function (sessionTokenHex) {
return tokens.SessionToken.fromHex(sessionTokenHex)
.then((token) => {
return this.doRequest(
'POST',
`${this.baseURL}/recoveryKey/exists`,
token,
{}
)
})
}
ClientApi.prototype.getRecoveryKeyExistsWithEmail = function (email) {
return this.doRequest(
'POST',
`${this.baseURL}/recoveryKey/exists`,
undefined,
{email}
)
}
ClientApi.prototype.deleteRecoveryKey = function (sessionTokenHex) {
return tokens.SessionToken.fromHex(sessionTokenHex)
.then((token) => {
return this.doRequest(
'DELETE',
`${this.baseURL}/recoveryKey`,
token
)
})

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

@ -558,6 +558,18 @@ module.exports = config => {
return this.api.getRecoveryKey(this.accountResetToken, recoveryKeyId)
}
Client.prototype.getRecoveryKeyExists = function (email) {
if (! email) {
return this.api.getRecoveryKeyExistsWithSession(this.sessionToken)
} else {
return this.api.getRecoveryKeyExistsWithEmail(email)
}
}
Client.prototype.deleteRecoveryKey = function () {
return this.api.deleteRecoveryKey(this.sessionToken)
}
Client.prototype.resetAccountWithRecoveryKey = function (newPassword, kB, recoveryKeyId, headers, options = {}) {
if (! this.accountResetToken) {
throw new Error('call verifyPasswordResetCode before calling resetAccountWithRecoveryKey')

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

@ -165,9 +165,8 @@ describe('/account/reset', function () {
it('should have deleted recovery key', () => {
assert.equal(mockDB.deleteRecoveryKey.callCount, 1)
const args = mockDB.deleteRecoveryKey.args[0]
assert.equal(args.length, 2, 'db.deleteRecoveryKey passed correct number of args')
assert.equal(args.length, 1, 'db.deleteRecoveryKey passed correct number of args')
assert.equal(args[0], uid, 'uid passed')
assert.equal(args[1], mockRequest.payload.recoveryKeyId, 'recoveryKeyId passed')
})
it('should have reset custom server', () => {

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

@ -9,6 +9,7 @@ const getRoute = require('../../routes_helpers').getRoute
const mocks = require('../../mocks')
const P = require('../../../lib/promise')
const sinon = require('sinon')
const errors = require('../../../lib/error')
let log, db, customs, routes, route, request, response
const email = 'test@email.com'
@ -16,7 +17,7 @@ const recoveryKeyId = '000000'
const recoveryData = '11111111111'
const uid = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
describe('POST /recoveryKeys', () => {
describe('POST /recoveryKey', () => {
describe('should create recovery key', () => {
beforeEach(() => {
const requestOptions = {
@ -24,7 +25,7 @@ describe('POST /recoveryKeys', () => {
log,
payload: {recoveryKeyId, recoveryData}
}
return setup({db: {}}, {}, '/recoveryKeys', requestOptions).then(r => response = r)
return setup({db: {}}, {}, '/recoveryKey', requestOptions).then(r => response = r)
})
it('returned the correct response', () => {
@ -68,15 +69,15 @@ describe('POST /recoveryKeys', () => {
const requestOptions = {
credentials: {uid, email, tokenVerificationId: '1232311'},
}
return setup({db: {}}, {}, '/recoveryKeys', requestOptions)
return setup({db: {}}, {}, '/recoveryKey', requestOptions)
.then(assert.fail, (err) => {
assert.deepEqual(err.errno, 138, 'returns unverified session error')
assert.deepEqual(err.errno, errors.ERRNO.SESSION_UNVERIFIED, 'returns unverified session error')
})
})
})
})
describe('GET /recoveryKeys/{recoveryKeyId}', () => {
describe('GET /recoveryKey/{recoveryKeyId}', () => {
describe('should get recovery key', () => {
beforeEach(() => {
const requestOptions = {
@ -84,7 +85,7 @@ describe('GET /recoveryKeys/{recoveryKeyId}', () => {
params: {recoveryKeyId},
log
}
return setup({db: {recoveryData}}, {}, '/recoveryKeys/{recoveryKeyId}', requestOptions)
return setup({db: {recoveryData, recoveryKeyId}}, {}, '/recoveryKey/{recoveryKeyId}', requestOptions)
.then(r => response = r)
})
@ -112,9 +113,143 @@ describe('GET /recoveryKeys/{recoveryKeyId}', () => {
it('called db.getRecoveryKey correctly', () => {
assert.equal(db.getRecoveryKey.callCount, 1)
const args = db.getRecoveryKey.args[0]
assert.equal(args.length, 2)
assert.equal(args.length, 1)
assert.equal(args[0], uid)
assert.equal(args[1], recoveryKeyId)
})
})
describe('fails to return recovery data with recoveryKeyId mismatch', () => {
beforeEach(() => {
const requestOptions = {
credentials: {uid, email},
params: {recoveryKeyId},
log
}
return setup({db: {recoveryData, recoveryKeyId: '11111'}}, {}, '/recoveryKey/{recoveryKeyId}', requestOptions)
.then(assert.fail, (err) => response = err)
})
it('returned the correct response', () => {
assert.deepEqual(response.errno, errors.ERRNO.RECOVERY_KEY_INVALID, 'correct invalid recovery key errno')
})
})
})
describe('POST /recoveryKey/exists', () => {
describe('should check if recovery key exists using sessionToken', () => {
beforeEach(() => {
const requestOptions = {
credentials: {uid, email},
log
}
return setup({db: {recoveryData, }}, {}, '/recoveryKey/exists', requestOptions)
.then(r => response = r)
})
it('returned the correct response', () => {
assert.deepEqual(response.exists, true, 'exists ')
})
it('called log.begin correctly', () => {
assert.equal(log.begin.callCount, 1)
const args = log.begin.args[0]
assert.equal(args.length, 2)
assert.equal(args[0], 'recoveryKeyExists')
assert.equal(args[1], request)
})
it('called db.getRecoveryKey correctly', () => {
assert.equal(db.getRecoveryKey.callCount, 1)
const args = db.getRecoveryKey.args[0]
assert.equal(args.length, 1)
assert.equal(args[0], uid)
})
})
describe('should check if recovery key exists using email', () => {
beforeEach(() => {
const requestOptions = {
payload: {email},
log
}
return setup({db: {uid, email, recoveryData}}, {}, '/recoveryKey/exists', requestOptions)
.then(r => response = r)
})
it('returned the correct response', () => {
assert.deepEqual(response.exists, true, 'exists ')
})
it('called log.begin correctly', () => {
assert.equal(log.begin.callCount, 1)
const args = log.begin.args[0]
assert.equal(args.length, 2)
assert.equal(args[0], 'recoveryKeyExists')
assert.equal(args[1], request)
})
it('called customs.check correctly', () => {
assert.equal(customs.check.callCount, 1)
const args = customs.check.args[0]
assert.equal(args.length, 3)
assert.equal(args[1], email)
assert.equal(args[2], 'recoveryKeyExists')
})
it('called db.getRecoveryKey correctly', () => {
assert.equal(db.getRecoveryKey.callCount, 1)
const args = db.getRecoveryKey.args[0]
assert.equal(args.length, 1)
assert.equal(args[0], uid)
})
})
})
describe('DELETE /recoveryKey', () => {
describe('should delete recovery key', () => {
beforeEach(() => {
const requestOptions = {
method: 'DELETE',
credentials: {uid, email},
log
}
return setup({db: {recoveryData}}, {}, '/recoveryKey', requestOptions)
.then(r => response = r)
})
it('returned the correct response', () => {
assert.ok(response, 'empty response ')
})
it('called log.begin correctly', () => {
assert.equal(log.begin.callCount, 1)
const args = log.begin.args[0]
assert.equal(args.length, 2)
assert.equal(args[0], 'recoveryKeyDelete')
assert.equal(args[1], request)
})
it('called db.deleteRecoveryKey correctly', () => {
assert.equal(db.deleteRecoveryKey.callCount, 1)
const args = db.deleteRecoveryKey.args[0]
assert.equal(args.length, 1)
assert.equal(args[0], uid)
})
})
describe('should fail for unverified session', () => {
beforeEach(() => {
const requestOptions = {
method: 'DELETE',
credentials: {uid, email, tokenVerificationId: 'unverified'},
log
}
return setup({db: {recoveryData}}, {}, '/recoveryKey', requestOptions)
.then(assert.fail, (err) => response = err)
})
it('returned the correct response', () => {
assert.equal(response.errno, errors.ERRNO.SESSION_UNVERIFIED, 'unverified session')
})
})
})
@ -127,7 +262,7 @@ function setup(results, errors, path, requestOptions) {
db = mocks.mockDB(results.db, errors.db)
customs = mocks.mockCustoms(errors.customs)
routes = makeRoutes({log, db, customs})
route = getRoute(routes, path)
route = getRoute(routes, path, requestOptions.method)
request = mocks.mockRequest(requestOptions)
request.emitMetricsEvent = sinon.spy(() => P.resolve({}))
return runTest(route, request)
@ -139,7 +274,7 @@ function makeRoutes(options = {}) {
const customs = options.customs || mocks.mockCustoms()
const config = options.config || {signinConfirmation: {}}
const Password = require('../../../lib/crypto/password')(log, config)
return require('../../../lib/routes/recovery-keys')(log, db, Password, config.verifierVersion, customs)
return require('../../../lib/routes/recovery-key')(log, db, Password, config.verifierVersion, customs)
}
function runTest(route, request) {

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

@ -345,6 +345,7 @@ function mockDB (data, errors) {
}),
getRecoveryKey: sinon.spy(() => {
return P.resolve({
recoveryKeyId: data.recoveryKeyId,
recoveryData: data.recoveryData
})
}),
@ -553,6 +554,7 @@ function mockRequest (data, errors) {
},
path: data.path,
params: data.params || {},
method: data.method || undefined,
payload: data.payload || {},
query: data.query || {},
setMetricsFlowCompleteSignal: metricsContext.setFlowCompleteSignal,

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

@ -75,6 +75,14 @@ describe('remote recovery keys', function () {
})
})
it('should fail to get unknown recovery key', () => {
return getAccountResetToken(client, server, email)
.then(() => client.getRecoveryKey('abce1234567890'))
.then(assert.fail, (err) => {
assert.equal(err.errno, 159, 'recovery key is not valid')
})
})
it('should fail if recoveryKeyId is missing', () => {
return getAccountResetToken(client, server, email)
.then(() => client.getRecoveryKey(recoveryKeyId))
@ -122,6 +130,62 @@ describe('remote recovery keys', function () {
})
})
it('should delete recovery key', () => {
return client.deleteRecoveryKey()
.then((res) => {
assert.ok(res, 'empty response')
return client.getRecoveryKeyExists()
.then((result) => {
assert.equal(result.exists, false, 'recovery key deleted')
})
})
})
describe('check recovery key status', () => {
describe('with sessionToken', () => {
it('should return true if recovery key exists', () => {
return client.getRecoveryKeyExists()
.then((res) => {
assert.equal(res.exists, true, 'recovery key exists')
})
})
it('should return false if recovery key doesn\'t exist', () => {
email = server.uniqueEmail()
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys: true})
.then((c) => {
client = c
return client.getRecoveryKeyExists()
})
.then((res) => {
assert.equal(res.exists, false, 'recovery key doesnt exists')
})
})
})
describe('with email', () => {
it('should return true if recovery key exists', () => {
return client.getRecoveryKeyExists(email)
.then((res) => {
assert.equal(res.exists, true, 'recovery key exists')
})
})
it('should return false if recovery key doesn\'t exist', () => {
email = server.uniqueEmail()
return Client.createAndVerify(config.publicUrl, email, password, server.mailbox, {keys: true})
.then((c) => {
client = c
return client.getRecoveryKeyExists(email)
})
.then((res) => {
assert.equal(res.exists, false, 'recovery key doesn\'t exist')
})
})
})
})
after(() => {
return TestServer.stop(server)
})

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

@ -4,12 +4,20 @@
'use strict'
exports.getRoute = function (routes, path) {
exports.getRoute = function (routes, path, method) {
var route = null
routes.some(function (r) {
if (r.path === path) {
route = r
if (method) {
if (r.method === method) {
return true
}
return false
}
return true
}
})