Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
feat(emails): Add ability to change email (#1983), r=@philbooth
Browse files Browse the repository at this point in the history
  • Loading branch information
vbudhram authored Jul 18, 2017
1 parent 23ab4f8 commit 0541f13
Show file tree
Hide file tree
Showing 17 changed files with 649 additions and 211 deletions.
52 changes: 49 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ see [`mozilla/fxa-js-client`](https://github.com/mozilla/fxa-js-client).
* [GET /recovery_emails (:lock: sessionToken)](#get-recovery_emails)
* [POST /recovery_email (:lock: sessionToken)](#post-recovery_email)
* [POST /recovery_email/destroy (:lock: sessionToken)](#post-recovery_emaildestroy)
* [POST /recovery_email/set_primary (:lock: sessionToken)](#post-recovery_emailset_primary)
* [Password](#password)
* [POST /password/change/start](#post-passwordchangestart)
* [POST /password/change/finish (:lock: passwordChangeToken)](#post-passwordchangefinish)
Expand Down Expand Up @@ -245,6 +246,10 @@ for `code` and `errno` are:
Reset password with this email type is not currently supported
* `code: 400, errno: 146`:
Invalid signin code
* `code: 400, errno: 147`:
Can not change primary email to an unverified email
* `code: 400, errno: 148`:
Can not change primary email to an email that does not belong to this account
* `code: 503, errno: 201`:
Service unavailable
* `code: 503, errno: 202`:
Expand Down Expand Up @@ -516,6 +521,12 @@ Obtain a `sessionToken` and, optionally, a `keyFetchToken` if `keys=true`.

<!--end-request-body-post-accountlogin-metricsContext-->

* `originalLoginEmail`: *validators.email.optional*

<!--begin-request-body-post-accountlogin-originalLoginEmail-->
This parameter is the original email used to login with. Typically, it is specified after a user logins with a different email case, or changed their primary email address.
<!--end-request-body-post-accountlogin-originalLoginEmail-->

##### Response body

* `uid`: *string, regex(HEX_STRING), required*
Expand Down Expand Up @@ -572,12 +583,12 @@ by the following errors
* `code: 400, errno: 142`:
Sign in with this email type is not currently supported

* `code: 400, errno: 103`:
Incorrect password

* `code: 400, errno: 127`:
Invalid unblock code

* `code: 400, errno: 103`:
Incorrect password


#### GET /account/status

Expand Down Expand Up @@ -1563,6 +1574,41 @@ by the following errors
Unverified session


#### POST /recovery_email/set_primary

:lock: HAWK-authenticated with session token
<!--begin-route-post-recovery_emailset_primary-->
This endpoint changes a user's primary email address. This email address must
belong to the user and be verified.
<!--end-route-post-recovery_emailset_primary-->

##### Request body

* `email`: *validators.email.required*

<!--begin-request-body-post-recovery_emailset_primary-email-->
The new primary email address of the user.
<!--end-request-body-post-recovery_emailset_primary-email-->

##### Error responses

Failing requests may be caused
by the following errors
(this is not an exhaustive list):

* `code: 503, errno: 202`:
Feature not enabled

* `code: 400, errno: 138`:
Unverified session

* `code: 400, errno: 148`:
Can not change primary email to an email that does not belong to this account

* `code: 400, errno: 147`:
Can not change primary email to an unverified email


### Password

#### POST /password/change/start
Expand Down
70 changes: 59 additions & 11 deletions lib/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ module.exports = (
const PasswordForgotToken = Token.PasswordForgotToken
const PasswordChangeToken = Token.PasswordChangeToken

function setAccountEmails(account) {
return this.accountEmails(account.uid)
.then((emails) => {
account.emails = emails

// Set primary email on account object
account.emails.forEach((item) => {
item.isVerified = !! item.isVerified
item.isPrimary = !! item.isPrimary

if (item.isPrimary) {
account.primaryEmail = item
}
})

return account
})
}

function DB(options) {
this.pool = new Pool(options.url)
if (config.redis.enabled) {
Expand Down Expand Up @@ -357,11 +376,10 @@ module.exports = (
log.trace({ op: 'DB.emailRecord', email: email })
return this.pool.get('/emailRecord/' + hexEncode(email))
.then(
function (data) {
data.emailVerified = !! data.emailVerified
return data
(body) => {
return setAccountEmails.call(this, body)
},
function (err) {
(err) => {
if (isNotFoundError(err)) {
err = error.unknownAccount(email)
}
Expand All @@ -370,23 +388,53 @@ module.exports = (
)
}

DB.prototype.account = function (uid) {
log.trace({ op: 'DB.account', uid: uid })
return this.pool.get('/account/' + uid)
DB.prototype.accountRecord = function (email) {
log.trace({op: 'DB.accountFromEmail', email: email})
return this.pool.get('/email/' + hexEncode(email) + '/account')
.then(
function (data) {
data.emailVerified = !! data.emailVerified
return data
(body) => {
return setAccountEmails.call(this, body)
},
(err) => {
if (isNotFoundError(err)) {
err = error.unknownAccount(email)
}
throw err
}
)
}

DB.prototype.setPrimaryEmail = function (uid, email) {
log.trace({op: 'DB.accountFromEmail', email: email})
return this.pool.post('/email/' + Buffer(email, 'utf8').toString('hex') + '/account/' + uid)
.then(
function (body) {
return body
},
function (err) {
if (isNotFoundError(err)) {
err = error.unknownAccount()
err = error.unknownAccount(email)
}
throw err
}
)
}

DB.prototype.account = function (uid) {
log.trace({op: 'DB.account', uid: uid})
return this.pool.get('/account/' + uid)
.then((body) => {
body.emailVerified = !! body.emailVerified

return setAccountEmails.call(this, body)
}, (err) => {
if (isNotFoundError(err)) {
err = error.unknownAccount()
}
throw err
})
}

DB.prototype.devices = function (uid) {
log.trace({ op: 'DB.devices', uid: uid })
const promises = [
Expand Down
20 changes: 20 additions & 0 deletions lib/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ var ERRNO = {
VERIFIED_SECONDARY_EMAIL_EXISTS: 144,
RESET_PASSWORD_WITH_SECONDARY_EMAIL: 145,
INVALID_SIGNIN_CODE: 146,
CHANGE_EMAIL_TO_UNVERIFIED_EMAIL: 147,
CHANGE_EMAIL_TO_UNOWNED_EMAIL: 148,

SERVER_BUSY: 201,
FEATURE_NOT_ENABLED: 202,
Expand Down Expand Up @@ -673,6 +675,24 @@ AppError.invalidSigninCode = function () {
})
}

AppError.cannotChangeEmailToUnverifiedEmail = function () {
return new AppError({
code: 400,
error: 'Bad Request',
errno: ERRNO.CHANGE_EMAIL_TO_UNVERIFIED_EMAIL,
message: 'Can not change primary email to an unverified email'
})
}

AppError.cannotChangeEmailToUnownedEmail = function () {
return new AppError({
code: 400,
error: 'Bad Request',
errno: ERRNO.CHANGE_EMAIL_TO_UNOWNED_EMAIL,
message: 'Can not change primary email to an email that does not belong to this account'
})
}

AppError.unexpectedError = () => {
return new AppError({})
}
Expand Down
Loading

0 comments on commit 0541f13

Please sign in to comment.