Bug 1215509 - Allow signUp in FxAccountsClient to use ?key=true. r=markh
authorFernando Jimenez <ferjmoreno@gmail.com>
Thu, 22 Oct 2015 16:18:17 +0800
changeset 268964 6b4a97d6111ac549d6a99f3ffa66934daa0aaca5
parent 268963 24653ad483de41bc8e84fad1b4e9f83d2bf4b38f
child 268965 0ac4e2e6cb78db629e6d33d89fba5cc01dda945f
push id29567
push userkwierso@gmail.com
push dateThu, 22 Oct 2015 23:37:33 +0000
treeherdermozilla-central@3888eea6aaf2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1215509
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1215509 - Allow signUp in FxAccountsClient to use ?key=true. r=markh
services/fxaccounts/FxAccountsClient.jsm
services/fxaccounts/tests/xpcshell/test_client.js
services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -24,16 +24,19 @@ const STATUS_CODE_TO_OTHER_ERRORS_LABEL 
   403: 2,
   410: 3,
   411: 4,
   413: 5,
   429: 6,
   500: 7,
 };
 
+const SIGNIN = "/account/login";
+const SIGNUP = "/account/create";
+
 this.FxAccountsClient = function(host = HOST) {
   this.host = host;
 
   // The FxA auth server expects requests to certain endpoints to be authorized
   // using Hawk.
   this.hawk = new HawkClient(host);
   this.hawk.observerPrefix = "FxA:hawk";
 
@@ -63,43 +66,20 @@ this.FxAccountsClient.prototype = {
    * Not used by this module, but made available to the FxAccounts.jsm
    * that uses this client.
    */
   now: function() {
     return this.hawk.now();
   },
 
   /**
-   * Create a new Firefox Account and authenticate
+   * Common code from signIn and signUp.
    *
-   * @param email
-   *        The email address for the account (utf8)
-   * @param password
-   *        The user's password
-   * @return Promise
-   *        Returns a promise that resolves to an object:
-   *        {
-   *          uid: the user's unique ID (hex)
-   *          sessionToken: a session token (hex)
-   *          keyFetchToken: a key fetch token (hex)
-   *        }
-   */
-  signUp: function(email, password) {
-    return Credentials.setup(email, password).then((creds) => {
-      let data = {
-        email: creds.emailUTF8,
-        authPW: CommonUtils.bytesAsHex(creds.authPW),
-      };
-      return this._request("/account/create", "POST", null, data);
-    });
-  },
-
-  /**
-   * Authenticate and create a new session with the Firefox Account API server
-   *
+   * @param path
+   *        Request URL path. Can be /account/create or /account/login
    * @param email
    *        The email address for the account (utf8)
    * @param password
    *        The user's password
    * @param [getKeys=false]
    *        If set to true the keyFetchToken will be retrieved
    * @param [retryOK=true]
    *        If capitalization of the email is wrong and retryOK is set to true,
@@ -109,63 +89,117 @@ this.FxAccountsClient.prototype = {
    *        {
    *          authAt: authentication time for the session (seconds since epoch)
    *          email: the primary email for this account
    *          keyFetchToken: a key fetch token (hex)
    *          sessionToken: a session token (hex)
    *          uid: the user's unique ID (hex)
    *          unwrapBKey: used to unwrap kB, derived locally from the
    *                      password (not revealed to the FxA server)
-   *          verified: flag indicating verification status of the email
+   *          verified (optional): flag indicating verification status of the
+   *                               email
    *        }
    */
-  signIn: function signIn(email, password, getKeys=false, retryOK=true) {
+  _createSession: function(path, email, password, getKeys=false,
+                           retryOK=true) {
     return Credentials.setup(email, password).then((creds) => {
       let data = {
         authPW: CommonUtils.bytesAsHex(creds.authPW),
         email: creds.emailUTF8,
       };
       let keys = getKeys ? "?keys=true" : "";
 
-      return this._request("/account/login" + keys, "POST", null, data).then(
+      return this._request(path + keys, "POST", null, data).then(
         // Include the canonical capitalization of the email in the response so
         // the caller can set its signed-in user state accordingly.
         result => {
           result.email = data.email;
           result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey);
 
           return result;
         },
         error => {
-          log.debug("signIn error: " + JSON.stringify(error));
+          log.debug("Session creation failed", error);
           // If the user entered an email with different capitalization from
           // what's stored in the database (e.g., Greta.Garbo@gmail.COM as
           // opposed to greta.garbo@gmail.com), the server will respond with a
           // errno 120 (code 400) and the expected capitalization of the email.
           // We retry with this email exactly once.  If successful, we use the
           // server's version of the email as the signed-in-user's email. This
           // is necessary because the email also serves as salt; so we must be
           // in agreement with the server on capitalization.
           //
           // API reference:
           // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
           if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) {
             if (!error.email) {
               log.error("Server returned errno 120 but did not provide email");
               throw error;
             }
-            return this.signIn(error.email, password, getKeys, false);
+            return this._createSession(path, error.email, password, getKeys,
+                                       false);
           }
           throw error;
         }
       );
     });
   },
 
   /**
+   * Create a new Firefox Account and authenticate
+   *
+   * @param email
+   *        The email address for the account (utf8)
+   * @param password
+   *        The user's password
+   * @param [getKeys=false]
+   *        If set to true the keyFetchToken will be retrieved
+   * @return Promise
+   *        Returns a promise that resolves to an object:
+   *        {
+   *          uid: the user's unique ID (hex)
+   *          sessionToken: a session token (hex)
+   *          keyFetchToken: a key fetch token (hex),
+   *          unwrapBKey: used to unwrap kB, derived locally from the
+   *                      password (not revealed to the FxA server)
+   *        }
+   */
+  signUp: function(email, password, getKeys=false) {
+    return this._createSession(SIGNUP, email, password, getKeys,
+                               false /* no retry */);
+  },
+
+  /**
+   * Authenticate and create a new session with the Firefox Account API server
+   *
+   * @param email
+   *        The email address for the account (utf8)
+   * @param password
+   *        The user's password
+   * @param [getKeys=false]
+   *        If set to true the keyFetchToken will be retrieved
+   * @return Promise
+   *        Returns a promise that resolves to an object:
+   *        {
+   *          authAt: authentication time for the session (seconds since epoch)
+   *          email: the primary email for this account
+   *          keyFetchToken: a key fetch token (hex)
+   *          sessionToken: a session token (hex)
+   *          uid: the user's unique ID (hex)
+   *          unwrapBKey: used to unwrap kB, derived locally from the
+   *                      password (not revealed to the FxA server)
+   *          verified: flag indicating verification status of the email
+   *        }
+   */
+  signIn: function signIn(email, password, getKeys=false) {
+    return this._createSession(SIGNIN, email, password, getKeys,
+                               true /* retry */);
+  },
+
+  /**
    * Destroy the current session with the Firefox Account API server
    *
    * @param sessionTokenHex
    *        The session token encoded in hex
    * @return Promise
    */
   signOut: function (sessionTokenHex, options = {}) {
     let path = "/session/destroy";
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -156,55 +156,83 @@ add_task(function test_backoffError() {
   let result = yield client._request("/duringDelayIShouldNotBeCalled", method);
   do_check_eq(client.backoffError, null);
   do_check_eq(result.working, "yes");
 
   yield deferredStop(server);
 });
 
 add_task(function test_signUp() {
-  let creationMessage = JSON.stringify({
+  let creationMessage_noKey = JSON.stringify({
+    uid: "uid",
+    sessionToken: "sessionToken"
+  });
+  let creationMessage_withKey = JSON.stringify({
     uid: "uid",
     sessionToken: "sessionToken",
     keyFetchToken: "keyFetchToken"
   });
   let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"});
   let created = false;
 
   let server = httpd_setup({
     "/account/create": function(request, response) {
       let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
       let jsonBody = JSON.parse(body);
 
       // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
-      do_check_eq(jsonBody.email, "andré@example.org");
+
+      if (created) {
+        // Error trying to create same account a second time
+        response.setStatusLine(request.httpVersion, 400, "Bad request");
+        response.bodyOutputStream.write(errorMessage, errorMessage.length);
+        return;
+      }
 
-      if (!created) {
+      if (jsonBody.email == "andré@example.org") {
+        do_check_eq("", request._queryString);
         do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
+
+        response.setStatusLine(request.httpVersion, 200, "OK");
+        response.bodyOutputStream.write(creationMessage_noKey,
+                                        creationMessage_noKey.length);
+        return;
+      }
+
+      if (jsonBody.email == "you@example.org") {
+        do_check_eq("keys=true", request._queryString);
+        do_check_eq(jsonBody.authPW, "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930");
         created = true;
 
         response.setStatusLine(request.httpVersion, 200, "OK");
-        response.bodyOutputStream.write(creationMessage, creationMessage.length);
+        response.bodyOutputStream.write(creationMessage_withKey,
+                                        creationMessage_withKey.length);
         return;
       }
-
-      // Error trying to create same account a second time
-      response.setStatusLine(request.httpVersion, 400, "Bad request");
-      response.bodyOutputStream.write(errorMessage, errorMessage.length);
-      return;
     },
   });
 
+  // Try to create an account without retrieving optional keys.
   let client = new FxAccountsClient(server.baseURI);
   let result = yield client.signUp('andré@example.org', 'pässwörd');
   do_check_eq("uid", result.uid);
   do_check_eq("sessionToken", result.sessionToken);
-  do_check_eq("keyFetchToken", result.keyFetchToken);
+  do_check_eq(undefined, result.keyFetchToken);
+  do_check_eq(result.unwrapBKey,
+              "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28");
 
-  // Try to create account again.  Triggers error path.
+  // Try to create an account retrieving optional keys.
+  result = yield client.signUp('you@example.org', 'pässwörd', true);
+  do_check_eq("uid", result.uid);
+  do_check_eq("sessionToken", result.sessionToken);
+  do_check_eq("keyFetchToken", result.keyFetchToken);
+  do_check_eq(result.unwrapBKey,
+              "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c");
+
+  // Try to create an existing account.  Triggers error path.
   try {
     result = yield client.signUp('andré@example.org', 'pässwörd');
     do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(101, expectedError.errno);
   }
 
   yield deferredStop(server);
@@ -285,25 +313,16 @@ add_task(function test_signIn() {
 
   // Retry due to wrong email capitalization
   result = yield client.signIn('You@example.com', 'bigsecret', true);
   do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
   do_check_eq(result.unwrapBKey,
               "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624");
   do_check_eq("keyFetchToken", result.keyFetchToken);
 
-  // Don't retry due to wrong email capitalization
-  try {
-    let result = yield client.signIn('You@example.com', 'bigsecret', true, false);
-    do_throw("Expected to catch an exception");
-  } catch (expectedError) {
-    do_check_eq(120, expectedError.errno);
-    do_check_eq("you@example.com", expectedError.email);
-  }
-
   // Trigger error path
   try {
     result = yield client.signIn("yøü@bad.example.org", "nofear");
     do_throw("Expected to catch an exception");
   } catch (expectedError) {
     do_check_eq(102, expectedError.errno);
   }
 
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -65,17 +65,17 @@ var Authentication = {
     let cb = Async.makeSpinningCallback();
 
     Logger.AssertTrue(account["username"], "Username has been found");
     Logger.AssertTrue(account["password"], "Password has been found");
 
     Logger.logInfo("Login user: " + account["username"] + '\n');
 
     let client = new FxAccountsClient();
-    client.signIn(account["username"], account["password"], true).then(credentials => {
+    client.signIn(account["username"], account["password"]).then(credentials => {
       return fxAccounts.setSignedInUser(credentials);
     }).then(() => {
       cb(null, true);
     }, error => {
       cb(error, false);
     });
 
     try {