- /* 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/. */
- define([
- 'es6-promise',
- 'sjcl',
- './lib/credentials',
- './lib/errors',
- './lib/hawkCredentials',
- './lib/metricsContext',
- './lib/request'
- ], function (ES6Promise, sjcl, credentials, ERRORS, hawkCredentials, metricsContext, Request) {
- 'use strict';
-
- // polyfill ES6 promises on browsers that do not support them.
- ES6Promise.polyfill();
-
- var VERSION = 'v1';
- var uriVersionRegExp = new RegExp('/' + VERSION + '$');
- var HKDF_SIZE = 2 * 32;
-
- function isUndefined(val) {
- return typeof val === 'undefined';
- }
-
- function isNull(val) {
- return val === null;
- }
-
- function isEmptyObject(val) {
- return Object.prototype.toString.call(val) === '[object Object]' && ! Object.keys(val).length;
- }
-
- function isEmptyString(val) {
- return val === '';
- }
-
- function required(val, name) {
- if (isUndefined(val) ||
- isNull(val) ||
- isEmptyObject(val) ||
- isEmptyString(val)) {
- throw new Error('Missing ' + name);
- }
- }
-
- /**
- * @class FxAccountClient
- * @constructor
- * @param {String} uri Auth Server URI
- * @param {Object} config Configuration
- */
- function FxAccountClient(uri, config) {
- if (! uri && ! config) {
- throw new Error('Firefox Accounts auth server endpoint or configuration object required.');
- }
-
- if (typeof uri !== 'string') {
- config = uri || {};
- uri = config.uri;
- }
-
- if (typeof config === 'undefined') {
- config = {};
- }
-
- if (! uri) {
- throw new Error('FxA auth server uri not set.');
- }
-
- if (!uriVersionRegExp.test(uri)) {
- uri = uri + '/' + VERSION;
- }
-
- this.request = new Request(uri, config.xhr, { localtimeOffsetMsec: config.localtimeOffsetMsec });
- }
-
- FxAccountClient.VERSION = VERSION;
-
- /**
- * @method signUp
- * @param {String} email Email input
- * @param {String} password Password input
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, calls the API with `?keys=true` to get the keyFetchToken
- * @param {String} [options.service]
- * Opaque alphanumeric token to be included in verification links
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.preVerified]
- * set email to be verified if possible
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.lang]
- * set the language for the 'Accept-Language' header
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.signUp = function (email, password, options) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(password, 'password');
-
- return credentials.setup(email, password);
- })
- .then(
- function (result) {
- /*eslint complexity: [2, 13] */
- var endpoint = '/account/create';
- var data = {
- email: result.emailUTF8,
- authPW: sjcl.codec.hex.fromBits(result.authPW)
- };
- var requestOpts = {};
-
- if (options) {
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- // preVerified is used for unit/functional testing
- if (options.preVerified) {
- data.preVerified = options.preVerified;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.keys) {
- endpoint += '?keys=true';
- }
-
- if (options.lang) {
- requestOpts.headers = {
- 'Accept-Language': options.lang
- };
- }
-
- if (options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
- }
-
- return self.request.send(endpoint, 'POST', null, data, requestOpts)
- .then(
- function(accountData) {
- if (options && options.keys) {
- accountData.unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
- }
- return accountData;
- }
- );
- }
- );
- };
-
- /**
- * @method signIn
- * @param {String} email Email input
- * @param {String} password Password input
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, calls the API with `?keys=true` to get the keyFetchToken
- * @param {Boolean} [options.skipCaseError]
- * If `true`, the request will skip the incorrect case error
- * @param {String} [options.service]
- * Service being signed into
- * @param {String} [options.reason]
- * Reason for sign in. Can be one of: `signin`, `password_check`,
- * `password_change`, `password_reset`
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.originalLoginEmail]
- * If retrying after an "incorrect email case" error, this specifies
- * the email address as originally entered by the user.
- * @param {String} [options.verificationMethod]
- * Request a specific verification method be used for verifying the session,
- * e.g. 'email-2fa' or 'totp-2fa'.
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- * @param {String} [options.unblockCode]
- * Login unblock code.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.signIn = function (email, password, options) {
- var self = this;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(password, 'password');
-
- return credentials.setup(email, password);
- })
- .then(
- function (result) {
- var endpoint = '/account/login';
-
- if (options.keys) {
- endpoint += '?keys=true';
- }
-
- var data = {
- email: result.emailUTF8,
- authPW: sjcl.codec.hex.fromBits(result.authPW)
- };
-
- if (options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
-
- if (options.reason) {
- data.reason = options.reason;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.unblockCode) {
- data.unblockCode = options.unblockCode;
- }
-
- if (options.originalLoginEmail) {
- data.originalLoginEmail = options.originalLoginEmail;
- }
-
- if (options.verificationMethod) {
- data.verificationMethod = options.verificationMethod;
- }
-
- return self.request.send(endpoint, 'POST', null, data)
- .then(
- function(accountData) {
- if (options.keys) {
- accountData.unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
- }
- return accountData;
- },
- function(error) {
- if (error && error.email && error.errno === ERRORS.INCORRECT_EMAIL_CASE && !options.skipCaseError) {
- options.skipCaseError = true;
- options.originalLoginEmail = email;
-
- return self.signIn(error.email, password, options);
- } else {
- throw error;
- }
- }
- );
- }
- );
- };
-
- /**
- * @method verifyCode
- * @param {String} uid Account ID
- * @param {String} code Verification code
- * @param {Object} [options={}] Options
- * @param {String} [options.service]
- * Service being signed into
- * @param {String} [options.reminder]
- * Reminder that was used to verify the account
- * @param {String} [options.type]
- * Type of code being verified, only supports `secondary` otherwise will verify account/sign-in
- * @param {Boolean} [options.marketingOptIn]
- * If `true`, notifies marketing of opt-in intent.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.verifyCode = function(uid, code, options) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(uid, 'uid');
- required(code, 'verify code');
-
- var data = {
- uid: uid,
- code: code
- };
-
- if (options) {
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.reminder) {
- data.reminder = options.reminder;
- }
-
- if (options.type) {
- data.type = options.type;
- }
-
- if (options.marketingOptIn) {
- data.marketingOptIn = true;
- }
- }
-
- return self.request.send('/recovery_email/verify_code', 'POST', null, data);
- });
- };
-
- FxAccountClient.prototype.verifyTokenCode = function(sessionToken, uid, code) {
- var self = this;
-
- required(uid, 'uid');
- required(code, 'verify token code');
- required(sessionToken, 'sessionToken');
-
- return Promise.resolve()
- .then(function () {
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var data = {
- uid: uid,
- code: code
- };
-
- return self.request.send('/session/verify/token', 'POST', creds, data);
- });
- };
-
- /**
- * @method recoveryEmailStatus
- * @param {String} sessionToken sessionToken obtained from signIn
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.recoveryEmailStatus = function(sessionToken) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/recovery_email/status', 'GET', creds);
- });
- };
-
- /**
- * Re-sends a verification code to the account's recovery email address.
- *
- * @method recoveryEmailResendCode
- * @param {String} sessionToken sessionToken obtained from signIn
- * @param {Object} [options={}] Options
- * @param {String} [options.email]
- * Code will be resent to this email, only used for secondary email codes
- * @param {String} [options.service]
- * Opaque alphanumeric token to be included in verification links
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.type]
- * Specifies the type of code to send, currently only supported type is
- * `upgradeSession`.
- * @param {String} [options.lang]
- * set the language for the 'Accept-Language' header
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.recoveryEmailResendCode = function(sessionToken, options) {
- var self = this;
- var data = {};
- var requestOpts = {};
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- if (options) {
- if (options.email) {
- data.email = options.email;
- }
-
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.type) {
- data.type = options.type;
- }
-
- if (options.lang) {
- requestOpts.headers = {
- 'Accept-Language': options.lang
- };
- }
- }
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/recovery_email/resend_code', 'POST', creds, data, requestOpts);
- });
- };
-
- /**
- * Used to ask the server to send a recovery code.
- * The API returns passwordForgotToken to the client.
- *
- * @method passwordForgotSendCode
- * @param {String} email
- * @param {Object} [options={}] Options
- * @param {String} [options.service]
- * Opaque alphanumeric token to be included in verification links
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.lang]
- * set the language for the 'Accept-Language' header
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.passwordForgotSendCode = function(email, options) {
- var self = this;
- var data = {
- email: email
- };
- var requestOpts = {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
-
- if (options) {
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.lang) {
- requestOpts.headers = {
- 'Accept-Language': options.lang
- };
- }
-
- if (options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
- }
-
- return self.request.send('/password/forgot/send_code', 'POST', null, data, requestOpts);
- });
- };
-
- /**
- * Re-sends a verification code to the account's recovery email address.
- * HAWK-authenticated with the passwordForgotToken.
- *
- * @method passwordForgotResendCode
- * @param {String} email
- * @param {String} passwordForgotToken
- * @param {Object} [options={}] Options
- * @param {String} [options.service]
- * Opaque alphanumeric token to be included in verification links
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.lang]
- * set the language for the 'Accept-Language' header
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.passwordForgotResendCode = function(email, passwordForgotToken, options) {
- var self = this;
- var data = {
- email: email
- };
- var requestOpts = {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(passwordForgotToken, 'passwordForgotToken');
-
- if (options) {
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.lang) {
- requestOpts.headers = {
- 'Accept-Language': options.lang
- };
- }
- }
-
- return hawkCredentials(passwordForgotToken, 'passwordForgotToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/password/forgot/resend_code', 'POST', creds, data, requestOpts);
- });
- };
-
- /**
- * Submits the verification token to the server.
- * The API returns accountResetToken to the client.
- * HAWK-authenticated with the passwordForgotToken.
- *
- * @method passwordForgotVerifyCode
- * @param {String} code
- * @param {String} passwordForgotToken
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.accountResetWithRecoveryKey] verifying code to be use in account recovery
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.passwordForgotVerifyCode = function(code, passwordForgotToken, options) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(code, 'reset code');
- required(passwordForgotToken, 'passwordForgotToken');
-
- return hawkCredentials(passwordForgotToken, 'passwordForgotToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- code: code
- };
-
- if (options && options.accountResetWithRecoveryKey ) {
- data.accountResetWithRecoveryKey = options.accountResetWithRecoveryKey;
- }
-
- return self.request.send('/password/forgot/verify_code', 'POST', creds, data);
- });
- };
-
- /**
- * Returns the status for the passwordForgotToken.
- * If the request returns a success response, the token has not yet been consumed.
-
- * @method passwordForgotStatus
- * @param {String} passwordForgotToken
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.passwordForgotStatus = function(passwordForgotToken) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(passwordForgotToken, 'passwordForgotToken');
-
- return hawkCredentials(passwordForgotToken, 'passwordForgotToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/password/forgot/status', 'GET', creds);
- });
- };
-
- /**
- * The API returns reset result to the client.
- * HAWK-authenticated with accountResetToken
- *
- * @method accountReset
- * @param {String} email
- * @param {String} newPassword
- * @param {String} accountResetToken
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, a new `keyFetchToken` is provisioned. `options.sessionToken`
- * is required if `options.keys` is true.
- * @param {Boolean} [options.sessionToken]
- * If `true`, a new `sessionToken` is provisioned.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.accountReset = function(email, newPassword, accountResetToken, options) {
- var self = this;
- var data = {};
- var unwrapBKey;
-
- options = options || {};
-
- if (options.sessionToken) {
- data.sessionToken = options.sessionToken;
- }
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(newPassword, 'new password');
- required(accountResetToken, 'accountResetToken');
-
- if (options.keys) {
- required(options.sessionToken, 'sessionToken');
- }
-
- return credentials.setup(email, newPassword);
- })
- .then(
- function (result) {
- if (options.keys) {
- unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
- }
-
- data.authPW = sjcl.codec.hex.fromBits(result.authPW);
-
- return hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE);
- }
- ).then(
- function (creds) {
- var queryParams = '';
- if (options.keys) {
- queryParams = '?keys=true';
- }
-
- var endpoint = '/account/reset' + queryParams;
- return self.request.send(endpoint, 'POST', creds, data)
- .then(
- function(accountData) {
- if (options.keys && accountData.keyFetchToken) {
- accountData.unwrapBKey = unwrapBKey;
- }
-
- return accountData;
- }
- );
- }
- );
- };
-
- /**
- * Get the base16 bundle of encrypted kA|wrapKb.
- *
- * @method accountKeys
- * @param {String} keyFetchToken
- * @param {String} oldUnwrapBKey
- * @return {Promise} A promise that will be fulfilled with JSON of {kA, kB} of the key bundle
- */
- FxAccountClient.prototype.accountKeys = function(keyFetchToken, oldUnwrapBKey) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(keyFetchToken, 'keyFetchToken');
- required(oldUnwrapBKey, 'oldUnwrapBKey');
-
- return hawkCredentials(keyFetchToken, 'keyFetchToken', 3 * 32);
- })
- .then(function(creds) {
- var bundleKey = sjcl.codec.hex.fromBits(creds.bundleKey);
-
- return self.request.send('/account/keys', 'GET', creds)
- .then(
- function(payload) {
-
- return credentials.unbundleKeyFetchResponse(bundleKey, payload.bundle);
- });
- })
- .then(function(keys) {
- return {
- kB: sjcl.codec.hex.fromBits(
- credentials.xor(
- sjcl.codec.hex.toBits(keys.wrapKB),
- sjcl.codec.hex.toBits(oldUnwrapBKey)
- )
- ),
- kA: keys.kA
- };
- });
- };
-
- /**
- * This deletes the account completely. All stored data is erased.
- *
- * @method accountDestroy
- * @param {String} email Email input
- * @param {String} password Password input
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.skipCaseError]
- * If `true`, the request will skip the incorrect case error
- * @param {String} sessionToken User session token
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.accountDestroy = function (email, password, options, sessionToken) {
- var self = this;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(password, 'password');
-
- var defers = [credentials.setup(email, password)];
- if (sessionToken) {
- defers.push(hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE));
- }
-
- return Promise.all(defers);
- })
- .then(
- function (results) {
- var auth = results[0];
- var creds = results[1];
- var data = {
- email: auth.emailUTF8,
- authPW: sjcl.codec.hex.fromBits(auth.authPW)
- };
-
- return self.request.send('/account/destroy', 'POST', creds, data)
- .then(
- function (response) {
- return response;
- },
- function (error) {
- // if incorrect email case error
- if (error && error.email && error.errno === ERRORS.INCORRECT_EMAIL_CASE && !options.skipCaseError) {
- options.skipCaseError = true;
-
- return self.accountDestroy(error.email, password, options, sessionToken);
- } else {
- throw error;
- }
- }
- );
- }
- );
- };
-
- /**
- * Gets the status of an account by uid.
- *
- * @method accountStatus
- * @param {String} uid User account id
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.accountStatus = function(uid) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(uid, 'uid');
-
- return self.request.send('/account/status?uid=' + uid, 'GET');
- });
- };
-
- /**
- * Gets the status of an account by email.
- *
- * @method accountStatusByEmail
- * @param {String} email User account email
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.accountStatusByEmail = function(email) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
-
- return self.request.send('/account/status', 'POST', null, {email: email});
- });
- };
-
- /**
- * Destroys this session, by invalidating the sessionToken.
- *
- * @method sessionDestroy
- * @param {String} sessionToken User session token
- * @param {Object} [options={}] Options
- * @param {String} [options.customSessionToken] Override which session token to destroy for this same user
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.sessionDestroy = function(sessionToken, options) {
- var self = this;
- var data = {};
- options = options || {};
-
- if (options.customSessionToken) {
- data.customSessionToken = options.customSessionToken;
- }
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/session/destroy', 'POST', creds, data);
- });
- };
-
- /**
- * Responds successfully if the session status is valid, requires the sessionToken.
- *
- * @method sessionStatus
- * @param {String} sessionToken User session token
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.sessionStatus = function(sessionToken) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return self.request.send('/session/status', 'GET', creds);
- });
- };
-
- /**
- * @method sessionReauth
- * @param {String} sessionToken sessionToken obtained from signIn
- * @param {String} email Email input
- * @param {String} password Password input
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, calls the API with `?keys=true` to get the keyFetchToken
- * @param {Boolean} [options.skipCaseError]
- * If `true`, the request will skip the incorrect case error
- * @param {String} [options.service]
- * Service being accessed that needs reauthentication
- * @param {String} [options.reason]
- * Reason for reauthentication. Can be one of: `signin`, `password_check`,
- * `password_change`, `password_reset`
- * @param {String} [options.redirectTo]
- * a URL that the client should be redirected to after handling the request
- * @param {String} [options.resume]
- * Opaque url-encoded string that will be included in the verification link
- * as a querystring parameter, useful for continuing an OAuth flow for
- * example.
- * @param {String} [options.originalLoginEmail]
- * If retrying after an "incorrect email case" error, this specifies
- * the email address as originally entered by the user.
- * @param {String} [options.verificationMethod]
- * Request a specific verification method be used for verifying the session,
- * e.g. 'email-2fa' or 'totp-2fa'.
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- * @param {String} [options.unblockCode]
- * Login unblock code.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.sessionReauth = function (sessionToken, email, password, options) {
- var self = this;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(email, 'email');
- required(password, 'password');
-
- return credentials.setup(email, password);
- })
- .then(
- function (result) {
- var endpoint = '/session/reauth';
-
- if (options.keys) {
- endpoint += '?keys=true';
- }
-
- var data = {
- email: result.emailUTF8,
- authPW: sjcl.codec.hex.fromBits(result.authPW)
- };
-
- if (options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
-
- if (options.reason) {
- data.reason = options.reason;
- }
-
- if (options.redirectTo) {
- data.redirectTo = options.redirectTo;
- }
-
- if (options.resume) {
- data.resume = options.resume;
- }
-
- if (options.service) {
- data.service = options.service;
- }
-
- if (options.unblockCode) {
- data.unblockCode = options.unblockCode;
- }
-
- if (options.originalLoginEmail) {
- data.originalLoginEmail = options.originalLoginEmail;
- }
-
- if (options.verificationMethod) {
- data.verificationMethod = options.verificationMethod;
- }
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE)
- .then(function (creds) {
- return self.request.send(endpoint, 'POST', creds, data);
- })
- .then(
- function(accountData) {
- if (options.keys) {
- accountData.unwrapBKey = sjcl.codec.hex.fromBits(result.unwrapBKey);
- }
- return accountData;
- },
- function(error) {
- if (error && error.email && error.errno === ERRORS.INCORRECT_EMAIL_CASE && !options.skipCaseError) {
- options.skipCaseError = true;
- options.originalLoginEmail = email;
-
- return self.sessionReauth(sessionToken, error.email, password, options);
- } else {
- throw error;
- }
- }
- );
- }
- );
- };
-
- /**
- * Sign a BrowserID public key
- *
- * @method certificateSign
- * @param {String} sessionToken User session token
- * @param {Object} publicKey The key to sign
- * @param {int} duration Time interval from now when the certificate will expire in milliseconds
- * @param {Object} [options={}] Options
- * @param {String} [service=''] The requesting service, sent via the query string
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.certificateSign = function(sessionToken, publicKey, duration, options) {
- var self = this;
- var data = {
- publicKey: publicKey,
- duration: duration
- };
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(publicKey, 'publicKey');
- required(duration, 'duration');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- options = options || {};
-
- var queryString = '';
- if (options.service) {
- queryString = '?service=' + encodeURIComponent(options.service);
- }
-
- return self.request.send('/certificate/sign' + queryString, 'POST', creds, data);
- });
- };
-
- /**
- * Change the password from one known value to another.
- *
- * @method passwordChange
- * @param {String} email
- * @param {String} oldPassword
- * @param {String} newPassword
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, calls the API with `?keys=true` to get a new keyFetchToken
- * @param {String} [options.sessionToken]
- * If a `sessionToken` is passed, a new sessionToken will be returned
- * with the same `verified` status as the existing sessionToken.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.passwordChange = function(email, oldPassword, newPassword, options) {
- var self = this;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(oldPassword, 'old password');
- required(newPassword, 'new password');
-
- return self._passwordChangeStart(email, oldPassword);
- })
- .then(function (credentials) {
-
- var oldCreds = credentials;
- var emailToHashWith = credentials.emailToHashWith || email;
-
- return self._passwordChangeKeys(oldCreds)
- .then(function (keys) {
-
- return self._passwordChangeFinish(emailToHashWith, newPassword, oldCreds, keys, options);
- });
- });
-
- };
-
- /**
- * First step to change the password.
- *
- * @method passwordChangeStart
- * @private
- * @param {String} email
- * @param {String} oldPassword
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.skipCaseError]
- * If `true`, the request will skip the incorrect case error
- * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText` and `oldUnwrapBKey`
- */
- FxAccountClient.prototype._passwordChangeStart = function(email, oldPassword, options) {
- var self = this;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(oldPassword, 'old password');
-
- return credentials.setup(email, oldPassword);
- })
- .then(function (oldCreds) {
- var data = {
- email: oldCreds.emailUTF8,
- oldAuthPW: sjcl.codec.hex.fromBits(oldCreds.authPW)
- };
-
- return self.request.send('/password/change/start', 'POST', null, data)
- .then(
- function(passwordData) {
- passwordData.oldUnwrapBKey = sjcl.codec.hex.fromBits(oldCreds.unwrapBKey);
-
- // Similar to password reset, this keeps the contract that we always
- // hash passwords with the original account email.
- passwordData.emailToHashWith = email;
- return passwordData;
- },
- function(error) {
- // if incorrect email case error
- if (error && error.email && error.errno === ERRORS.INCORRECT_EMAIL_CASE && !options.skipCaseError) {
- options.skipCaseError = true;
-
- return self._passwordChangeStart(error.email, oldPassword, options);
- } else {
- throw error;
- }
- }
- );
- });
- };
-
- function checkCreds(creds) {
- required(creds, 'credentials');
- required(creds.oldUnwrapBKey, 'credentials.oldUnwrapBKey');
- required(creds.keyFetchToken, 'credentials.keyFetchToken');
- required(creds.passwordChangeToken, 'credentials.passwordChangeToken');
- }
-
- /**
- * Second step to change the password.
- *
- * @method _passwordChangeKeys
- * @private
- * @param {Object} oldCreds This object should consists of `oldUnwrapBKey`, `keyFetchToken` and `passwordChangeToken`.
- * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText`
- */
- FxAccountClient.prototype._passwordChangeKeys = function(oldCreds) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- checkCreds(oldCreds);
- })
- .then(function () {
- return self.accountKeys(oldCreds.keyFetchToken, oldCreds.oldUnwrapBKey);
- });
- };
-
- /**
- * Third step to change the password.
- *
- * @method _passwordChangeFinish
- * @private
- * @param {String} email
- * @param {String} newPassword
- * @param {Object} oldCreds This object should consists of `oldUnwrapBKey`, `keyFetchToken` and `passwordChangeToken`.
- * @param {Object} keys This object should contain the unbundled keys
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, calls the API with `?keys=true` to get the keyFetchToken
- * @param {String} [options.sessionToken]
- * If a `sessionToken` is passed, a new sessionToken will be returned
- * with the same `verified` status as the existing sessionToken.
- * @return {Promise} A promise that will be fulfilled with JSON of `xhr.responseText`
- */
- FxAccountClient.prototype._passwordChangeFinish = function(email, newPassword, oldCreds, keys, options) {
- options = options || {};
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(newPassword, 'new password');
- checkCreds(oldCreds);
- required(keys, 'keys');
- required(keys.kB, 'keys.kB');
-
- var defers = [];
- defers.push(credentials.setup(email, newPassword));
- defers.push(hawkCredentials(oldCreds.passwordChangeToken, 'passwordChangeToken', HKDF_SIZE));
-
- if (options.sessionToken) {
- // Unbundle session data to get session id
- defers.push(hawkCredentials(options.sessionToken, 'sessionToken', HKDF_SIZE));
- }
-
- return Promise.all(defers);
- })
- .then(function (results) {
- var newCreds = results[0];
- var hawkCreds = results[1];
- var sessionData = results[2];
- var newWrapKb = sjcl.codec.hex.fromBits(
- credentials.xor(
- sjcl.codec.hex.toBits(keys.kB),
- newCreds.unwrapBKey
- )
- );
-
- var queryParams = '';
- if (options.keys) {
- queryParams = '?keys=true';
- }
-
- var sessionTokenId;
- if (sessionData && sessionData.id) {
- sessionTokenId = sessionData.id;
- }
-
- return self.request.send('/password/change/finish' + queryParams, 'POST', hawkCreds, {
- wrapKb: newWrapKb,
- authPW: sjcl.codec.hex.fromBits(newCreds.authPW),
- sessionToken: sessionTokenId
- })
- .then(function (accountData) {
- if (options.keys && accountData.keyFetchToken) {
- accountData.unwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
- }
- return accountData;
- });
- });
- };
-
- /**
- * Get 32 bytes of random data. This should be combined with locally-sourced entropy when creating salts, etc.
- *
- * @method getRandomBytes
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.getRandomBytes = function() {
-
- return this.request.send('/get_random_bytes', 'POST');
- };
-
- /**
- * Add a new device
- *
- * @method deviceRegister
- * @param {String} sessionToken User session token
- * @param {String} deviceName Name of device
- * @param {String} deviceType Type of device (mobile|desktop)
- * @param {Object} [options={}] Options
- * @param {string} [options.deviceCallback] Device's push endpoint.
- * @param {string} [options.devicePublicKey] Public key used to encrypt push messages.
- * @param {string} [options.deviceAuthKey] Authentication secret used to encrypt push messages.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.deviceRegister = function (sessionToken, deviceName, deviceType, options) {
- var request = this.request;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(deviceName, 'deviceName');
- required(deviceType, 'deviceType');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- name: deviceName,
- type: deviceType
- };
-
- if (options.deviceCallback) {
- data.pushCallback = options.deviceCallback;
- }
-
- if (options.devicePublicKey && options.deviceAuthKey) {
- data.pushPublicKey = options.devicePublicKey;
- data.pushAuthKey = options.deviceAuthKey;
- }
-
- return request.send('/account/device', 'POST', creds, data);
- });
- };
-
- /**
- * Update the name of an existing device
- *
- * @method deviceUpdate
- * @param {String} sessionToken User session token
- * @param {String} deviceId User-unique identifier of device
- * @param {String} deviceName Name of device
- * @param {Object} [options={}] Options
- * @param {string} [options.deviceCallback] Device's push endpoint.
- * @param {string} [options.devicePublicKey] Public key used to encrypt push messages.
- * @param {string} [options.deviceAuthKey] Authentication secret used to encrypt push messages.
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.deviceUpdate = function (sessionToken, deviceId, deviceName, options) {
- var request = this.request;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(deviceId, 'deviceId');
- required(deviceName, 'deviceName');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- id: deviceId,
- name: deviceName
- };
-
- if (options.deviceCallback) {
- data.pushCallback = options.deviceCallback;
- }
-
- if (options.devicePublicKey && options.deviceAuthKey) {
- data.pushPublicKey = options.devicePublicKey;
- data.pushAuthKey = options.deviceAuthKey;
- }
-
- return request.send('/account/device', 'POST', creds, data);
- });
- };
-
- /**
- * Unregister an existing device
- *
- * @method deviceDestroy
- * @param {String} sessionToken Session token obtained from signIn
- * @param {String} deviceId User-unique identifier of device
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.deviceDestroy = function (sessionToken, deviceId) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(deviceId, 'deviceId');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- id: deviceId
- };
-
- return request.send('/account/device/destroy', 'POST', creds, data);
- });
- };
-
- /**
- * Get a list of all devices for a user
- *
- * @method deviceList
- * @param {String} sessionToken sessionToken obtained from signIn
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.deviceList = function (sessionToken) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return request.send('/account/devices', 'GET', creds);
- });
- };
-
- /**
- * Get a list of user's sessions
- *
- * @method sessions
- * @param {String} sessionToken sessionToken obtained from signIn
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.sessions = function (sessionToken) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return request.send('/account/sessions', 'GET', creds);
- });
- };
-
- /**
- * Send an unblock code
- *
- * @method sendUnblockCode
- * @param {String} email email where to send the login authorization code
- * @param {Object} [options={}] Options
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.sendUnblockCode = function (email, options) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
-
- var data = {
- email: email
- };
-
- if (options && options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
-
- return self.request.send('/account/login/send_unblock_code', 'POST', null, data);
- });
- };
-
- /**
- * Reject a login unblock code. Code will be deleted from the server
- * and will not be able to be used again.
- *
- * @method rejectLoginAuthorizationCode
- * @param {String} uid Account ID
- * @param {String} unblockCode unblock code
- * @return {Promise} A promise that will be fulfilled with JSON `xhr.responseText` of the request
- */
- FxAccountClient.prototype.rejectUnblockCode = function (uid, unblockCode) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(uid, 'uid');
- required(unblockCode, 'unblockCode');
-
- var data = {
- uid: uid,
- unblockCode: unblockCode
- };
-
- return self.request.send('/account/login/reject_unblock_code', 'POST', null, data);
- });
- };
-
- /**
- * Send an sms.
- *
- * @method sendSms
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} phoneNumber Phone number sms will be sent to
- * @param {String} messageId Corresponding message id that will be sent
- * @param {Object} [options={}] Options
- * @param {String} [options.lang] Language that sms will be sent in
- * @param {Array} [options.features] Array of features to be enabled for the request
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- */
- FxAccountClient.prototype.sendSms = function (sessionToken, phoneNumber, messageId, options) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(phoneNumber, 'phoneNumber');
- required(messageId, 'messageId');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- phoneNumber: phoneNumber,
- messageId: messageId
- };
- var requestOpts = {};
-
- if (options) {
- if (options.lang) {
- requestOpts.headers = {
- 'Accept-Language': options.lang
- };
- }
-
- if (options.features) {
- data.features = options.features;
- }
-
- if (options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
- }
-
- return request.send('/sms', 'POST', creds, data, requestOpts);
- });
- };
-
- /**
- * Get SMS status for the current user.
- *
- * @method smsStatus
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {Object} [options={}] Options
- * @param {String} [options.country] country Country to force for testing.
- */
- FxAccountClient.prototype.smsStatus = function (sessionToken, options) {
- var request = this.request;
- options = options || {};
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var url = '/sms/status';
- if (options.country) {
- url += '?country=' + encodeURIComponent(options.country);
- }
- return request.send(url, 'GET', creds);
- });
- };
-
- /**
- * Consume a signinCode.
- *
- * @method consumeSigninCode
- * @param {String} code The signinCode entered by the user
- * @param {String} flowId Identifier for the current event flow
- * @param {Number} flowBeginTime Timestamp for the flow.begin event
- * @param {String} [deviceId] Identifier for the current device
- */
- FxAccountClient.prototype.consumeSigninCode = function (code, flowId, flowBeginTime, deviceId) {
- var self = this;
-
- return Promise.resolve()
- .then(function () {
- required(code, 'code');
- required(flowId, 'flowId');
- required(flowBeginTime, 'flowBeginTime');
-
- return self.request.send('/signinCodes/consume', 'POST', null, {
- code: code,
- metricsContext: {
- deviceId: deviceId,
- flowId: flowId,
- flowBeginTime: flowBeginTime
- }
- });
- });
- };
-
- /**
- * Get the recovery emails associated with the signed in account.
- *
- * @method recoveryEmails
- * @param {String} sessionToken SessionToken obtained from signIn
- */
- FxAccountClient.prototype.recoveryEmails = function (sessionToken) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return request.send('/recovery_emails', 'GET', creds);
- });
- };
-
- /**
- * Create a new recovery email for the signed in account.
- *
- * @method recoveryEmailCreate
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} email new email to be added
- */
- FxAccountClient.prototype.recoveryEmailCreate = function (sessionToken, email) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(sessionToken, 'email');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- email: email
- };
-
- return request.send('/recovery_email', 'POST', creds, data);
- });
- };
-
- /**
- * Remove the recovery email for the signed in account.
- *
- * @method recoveryEmailDestroy
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} email email to be removed
- */
- FxAccountClient.prototype.recoveryEmailDestroy = function (sessionToken, email) {
- var request = this.request;
-
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(sessionToken, 'email');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- email: email
- };
-
- return request.send('/recovery_email/destroy', 'POST', creds, data);
- });
- };
-
- /**
- * Changes user's primary email address.
- *
- * @method recoveryEmailSetPrimaryEmail
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} email Email that will be the new primary email for user
- */
- FxAccountClient.prototype.recoveryEmailSetPrimaryEmail = function (sessionToken, email) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- var data = {
- email: email
- };
- return request.send('/recovery_email/set_primary', 'POST', creds, data);
- });
- };
-
- /**
- * Creates a new TOTP token for the user associated with this session.
- *
- * @method createTotpToken
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {Object} [options.metricsContext={}] Metrics context metadata
- * @param {String} options.metricsContext.deviceId identifier for the current device
- * @param {String} options.metricsContext.flowId identifier for the current event flow
- * @param {Number} options.metricsContext.flowBeginTime flow.begin event time
- * @param {Number} options.metricsContext.utmCampaign marketing campaign identifier
- * @param {Number} options.metricsContext.utmContent content identifier
- * @param {Number} options.metricsContext.utmMedium acquisition medium
- * @param {Number} options.metricsContext.utmSource traffic source
- * @param {Number} options.metricsContext.utmTerm search terms
- */
- FxAccountClient.prototype.createTotpToken = function (sessionToken, options) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var data = {};
-
- if (options && options.metricsContext) {
- data.metricsContext = metricsContext.marshall(options.metricsContext);
- }
-
- return request.send('/totp/create', 'POST', creds, data);
- });
- };
-
- /**
- * Deletes this user's TOTP token.
- *
- * @method deleteTotpToken
- * @param {String} sessionToken SessionToken obtained from signIn
- */
- FxAccountClient.prototype.deleteTotpToken = function (sessionToken) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return request.send('/totp/destroy', 'POST', creds, {});
- });
- };
-
- /**
- * Check to see if the current user has a TOTP token associated with
- * their account.
- *
- * @method checkTotpTokenExists
- * @param {String} sessionToken SessionToken obtained from signIn
- */
- FxAccountClient.prototype.checkTotpTokenExists = function (sessionToken) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function(creds) {
- return request.send('/totp/exists', 'GET', creds);
- });
- };
-
- /**
- * Verify tokens if using a valid TOTP code.
- *
- * @method verifyTotpCode
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} code TOTP code to verif
- * @param {String} [options.service] Service being used
- */
- FxAccountClient.prototype.verifyTotpCode = function (sessionToken, code, options) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(code, 'code');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var data = {
- code: code
- };
-
- if (options && options.service) {
- data.service = options.service;
- }
-
- return request.send('/session/verify/totp', 'POST', creds, data);
- });
- };
-
- /**
- * Replace user's recovery codes.
- *
- * @method replaceRecoveryCodes
- * @param {String} sessionToken SessionToken obtained from signIn
- */
- FxAccountClient.prototype.replaceRecoveryCodes = function (sessionToken) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
-
- return request.send('/recoveryCodes', 'GET', creds);
- });
- };
-
- /**
- * Consume recovery code.
- *
- * @method consumeRecoveryCode
- * @param {String} sessionToken SessionToken obtained from signIn
- * @param {String} code recovery code
- */
- FxAccountClient.prototype.consumeRecoveryCode = function (sessionToken, code) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(code, 'code');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var data = {
- code: code
- };
-
- return request.send('/session/verify/recoveryCode', 'POST', creds, data);
- });
- };
-
- /**
- * Creates a new recovery key for the account. The recovery key contains encrypted
- * data the corresponds the the accounts current `kB`. This data can be used during
- * the password reset process to avoid regenerating the `kB`.
- *
- * @param sessionToken
- * @param recoveryKeyId The recoveryKeyId that can be used to retrieve saved bundle
- * @param bundle The encrypted recovery bundle to store
- * @returns {Promise} A promise that will be fulfilled with decoded recovery data (`kB`)
- */
- FxAccountClient.prototype.createRecoveryKey = function (sessionToken, recoveryKeyId, bundle) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
- required(recoveryKeyId, 'recoveryKeyId');
- required(bundle, 'bundle');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- var data = {
- recoveryKeyId: recoveryKeyId,
- recoveryData: bundle
- };
-
- return request.send('/recoveryKey', 'POST', creds, data);
- });
- };
-
- /**
- * Retrieves the encrypted recovery data that corresponds to the recovery key which
- * then gets decoded into the stored `kB`.
- *
- * @param accountResetToken
- * @param recoveryKeyId The recovery key id to retrieve encrypted bundle
- * @returns {Promise} A promise that will be fulfilled with decoded recovery data (`kB`)
- */
- FxAccountClient.prototype.getRecoveryKey = function (accountResetToken, recoveryKeyId) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(accountResetToken, 'accountResetToken');
- required(recoveryKeyId, 'recoveryKeyId');
-
- return hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE);
- })
- .then(function (creds) {
- return request.send('/recoveryKey/' + recoveryKeyId, 'GET', creds);
- });
- };
-
- /**
- * Reset a user's account using keys (kB) derived from a recovery key. This
- * process can be used to maintain the account's original kB.
- *
- * @param accountResetToken The account reset token
- * @param email The current email of the account
- * @param newPassword The new password of the account
- * @param recoveryKeyId The recovery key id used for account recovery
- * @param keys Keys used to create the new wrapKb
- * @param {Object} [options={}] Options
- * @param {Boolean} [options.keys]
- * If `true`, a new `keyFetchToken` is provisioned. `options.sessionToken`
- * is required if `options.keys` is true.
- * @param {Boolean} [options.sessionToken]
- * If `true`, a new `sessionToken` is provisioned.
- * @returns {Promise} A promise that will be fulfilled with updated account data
- */
- FxAccountClient.prototype.resetPasswordWithRecoveryKey = function (accountResetToken, email, newPassword, recoveryKeyId, keys, options) {
- options = options || {};
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(email, 'email');
- required(newPassword, 'new password');
- required(keys, 'keys');
- required(keys.kB, 'keys.kB');
- required(accountResetToken, 'accountResetToken');
- required(recoveryKeyId, 'recoveryKeyId');
-
- var defers = [];
- defers.push(credentials.setup(email, newPassword));
- defers.push(hawkCredentials(accountResetToken, 'accountResetToken', HKDF_SIZE));
-
- return Promise.all(defers);
- })
- .then(function (results) {
- var newCreds = results[0];
- var hawkCreds = results[1];
- var newWrapKb = sjcl.codec.hex.fromBits(
- credentials.xor(
- sjcl.codec.hex.toBits(keys.kB),
- newCreds.unwrapBKey
- )
- );
-
- var data = {
- wrapKb: newWrapKb,
- authPW: sjcl.codec.hex.fromBits(newCreds.authPW),
- recoveryKeyId: recoveryKeyId
- };
-
- if (options.sessionToken) {
- data.sessionToken = options.sessionToken;
- }
-
- if (options.keys) {
- required(options.sessionToken, 'sessionToken');
- }
-
- var queryParams = '';
- if (options.keys) {
- queryParams = '?keys=true';
- }
-
- return request.send('/account/reset' + queryParams, 'POST', hawkCreds, data)
- .then(function (accountData) {
- if (options.keys && accountData.keyFetchToken) {
- accountData.unwrapBKey = sjcl.codec.hex.fromBits(newCreds.unwrapBKey);
- }
- return accountData;
- });
- });
- };
-
- /**
- * Deletes the recovery key associated with this user.
- *
- * @param sessionToken
- */
- FxAccountClient.prototype.deleteRecoveryKey = function (sessionToken) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
- required(sessionToken, 'sessionToken');
-
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE);
- })
- .then(function (creds) {
- return request.send('/recoveryKey', 'DELETE', creds, {});
- });
- };
-
- /**
- * This checks to see if a recovery key exists for a user. This check
- * can be performed with either a sessionToken or an email.
- *
- * Typically, sessionToken is used when checking from within the `/settings`
- * view. If it exists, we can give the user an option to revoke the key.
- *
- * Checking with an email is typically performed during the password reset
- * flow. It is used to decide whether or not we can redirect a user to
- * the `Reset password with recovery key` page or regular password reset page.
- *
- * @param sessionToken
- * @param {String} email User's email
- * @returns {Promise} A promise that will be fulfilled with whether or not account has recovery ket
- */
- FxAccountClient.prototype.recoveryKeyExists = function (sessionToken, email) {
- var request = this.request;
- return Promise.resolve()
- .then(function () {
-
- if (sessionToken) {
- return hawkCredentials(sessionToken, 'sessionToken', HKDF_SIZE)
- .then(function (creds) {
- return request.send('/recoveryKey/exists', 'POST', creds, {});
- });
- }
-
- return request.send('/recoveryKey/exists', 'POST', null, {email: email});
- });
- };
-
- /**
- * Check for a required argument. Exposed for unit testing.
- *
- * @param {Value} val - value to check
- * @param {String} name - name of value
- * @throws {Error} if argument is falsey, or an empty object
- */
- FxAccountClient.prototype._required = required;
-
- return FxAccountClient;
- });
-
-