Bug 938635 - Implement forceAuthentication for firefox accounts. Part 2: FxAccountsManager. r=markh
authorFernando Jiménez <ferjmoreno@gmail.com>
Wed, 19 Feb 2014 11:47:11 +0100
changeset 169908 a8cdafcb11f054b67a9e2ca13d5f3625dcfbb909
parent 169907 fb1e20021f2d45df8f4871c856135739d253bb9f
child 169909 2cf9d97e26841c1afff969163180c45799361c94
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersmarkh
bugs938635
milestone30.0a1
Bug 938635 - Implement forceAuthentication for firefox accounts. Part 2: FxAccountsManager. r=markh
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/FxAccountsCommon.js
services/fxaccounts/FxAccountsManager.jsm
services/fxaccounts/tests/xpcshell/test_manager.js
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -168,16 +168,18 @@ FxAccountsInternal.prototype = {
    *        The promise resolves to the credentials object of the signed-in user:
    *        {
    *          email: The user's email address
    *          uid: The user's unique id
    *          sessionToken: Session for the FxA server
    *          kA: An encryption key from the FxA server
    *          kB: An encryption key derived from the user's FxA password
    *          verified: email verification status
+   *          authAt: The time (seconds since epoch) that this record was
+   *                  authenticated
    *        }
    *        or null if no user is signed in.
    */
   getSignedInUser: function getSignedInUser() {
     return this.getUserAccountData().then(data => {
       if (!data) {
         return null;
       }
@@ -198,16 +200,18 @@ FxAccountsInternal.prototype = {
    *        The credentials object obtained by logging in or creating
    *        an account on the FxA server:
    *        {
    *          email: The users email address
    *          uid: The user's unique id
    *          sessionToken: Session for the FxA server
    *          keyFetchToken: an unused keyFetchToken
    *          verified: true/false
+   *          authAt: The time (seconds since epoch) that this record was
+   *                  authenticated
    *        }
    * @return Promise
    *         The promise resolves to null when the data is saved
    *         successfully and is rejected on error.
    */
   setSignedInUser: function setSignedInUser(credentials) {
     log.debug("setSignedInUser - aborting any existing flows");
     this.abortExistingFlow();
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -41,16 +41,20 @@ this.KEY_LIFETIME       = 1000 * 3600 * 
 this.POLL_SESSION       = 1000 * 60 * 5;    // 5 minutes
 this.POLL_STEP          = 1000 * 3;         // 3 seconds
 
 // Observer notifications.
 this.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
 this.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
 this.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
 
+// UI Requests.
+this.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
+this.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
+
 // Server errno.
 // From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
 this.ERRNO_ACCOUNT_ALREADY_EXISTS     = 101;
 this.ERRNO_ACCOUNT_DOES_NOT_EXIST     = 102;
 this.ERRNO_INCORRECT_PASSWORD         = 103;
 this.ERRNO_UNVERIFIED_ACCOUNT         = 104;
 this.ERRNO_INVALID_VERIFICATION_CODE  = 105;
 this.ERRNO_NOT_VALID_JSON_BODY        = 106;
@@ -73,28 +77,30 @@ this.ERROR_ALREADY_SIGNED_IN_USER     = 
 this.ERROR_INVALID_ACCOUNTID          = "INVALID_ACCOUNTID";
 this.ERROR_INVALID_AUDIENCE           = "INVALID_AUDIENCE";
 this.ERROR_INVALID_AUTH_TOKEN         = "INVALID_AUTH_TOKEN";
 this.ERROR_INVALID_AUTH_TIMESTAMP     = "INVALID_AUTH_TIMESTAMP";
 this.ERROR_INVALID_AUTH_NONCE         = "INVALID_AUTH_NONCE";
 this.ERROR_INVALID_BODY_PARAMETERS    = "INVALID_BODY_PARAMETERS";
 this.ERROR_INVALID_PASSWORD           = "INVALID_PASSWORD";
 this.ERROR_INVALID_VERIFICATION_CODE  = "INVALID_VERIFICATION_CODE";
+this.ERROR_INVALID_REFRESH_AUTH_VALUE = "INVALID_REFRESH_AUTH_VALUE";
 this.ERROR_INVALID_REQUEST_SIGNATURE  = "INVALID_REQUEST_SIGNATURE";
 this.ERROR_INTERNAL_INVALID_USER      = "INTERNAL_ERROR_INVALID_USER";
 this.ERROR_MISSING_BODY_PARAMETERS    = "MISSING_BODY_PARAMETERS";
 this.ERROR_MISSING_CONTENT_LENGTH     = "MISSING_CONTENT_LENGTH";
 this.ERROR_NO_TOKEN_SESSION           = "NO_TOKEN_SESSION";
 this.ERROR_NOT_VALID_JSON_BODY        = "NOT_VALID_JSON_BODY";
 this.ERROR_OFFLINE                    = "OFFLINE";
 this.ERROR_REQUEST_BODY_TOO_LARGE     = "REQUEST_BODY_TOO_LARGE";
 this.ERROR_SERVER_ERROR               = "SERVER_ERROR";
 this.ERROR_TOO_MANY_CLIENT_REQUESTS   = "TOO_MANY_CLIENT_REQUESTS";
 this.ERROR_SERVICE_TEMP_UNAVAILABLE   = "SERVICE_TEMPORARY_UNAVAILABLE";
 this.ERROR_UI_ERROR                   = "UI_ERROR";
+this.ERROR_UI_REQUEST                 = "UI_REQUEST";
 this.ERROR_UNKNOWN                    = "UNKNOWN_ERROR";
 this.ERROR_UNVERIFIED_ACCOUNT         = "UNVERIFIED_ACCOUNT";
 
 // Error matching.
 this.SERVER_ERRNO_TO_ERROR = {};
 SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS]     = ERROR_ACCOUNT_ALREADY_EXISTS;
 SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXIST]     = ERROR_ACCOUNT_DOES_NOT_EXIST;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD]         = ERROR_INVALID_PASSWORD;
--- a/services/fxaccounts/FxAccountsManager.jsm
+++ b/services/fxaccounts/FxAccountsManager.jsm
@@ -54,96 +54,82 @@ this.FxAccountsManager = {
     }
 
     return {
       accountId: this._activeSession.email,
       verified: this._activeSession.verified
     }
   },
 
+  _error: function(aError, aDetails) {
+    log.error(aError);
+    let reason = {
+      error: aError
+    };
+    if (aDetails) {
+      reason.details = aDetails;
+    }
+    return Promise.reject(reason);
+  },
+
   _getError: function(aServerResponse) {
     if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
       return;
     }
     let error = SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
-    log.error(error);
     return error;
   },
 
   _serverError: function(aServerResponse) {
     let error = this._getError({ error: aServerResponse });
-    return Promise.reject({
-      error: error ? error : ERROR_SERVER_ERROR,
-      details: aServerResponse
-    });
+    return this._error(error ? error : ERROR_SERVER_ERROR, aServerResponse);
   },
 
   // As we do with _fxAccounts, we don't really need this factory, but this way
   // we allow tests to mock FxAccountsClient.
   _createFxAccountsClient: function() {
     return new FxAccountsClient();
   },
 
   _signInSignUp: function(aMethod, aAccountId, aPassword) {
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     if (!aAccountId) {
-      log.error(ERROR_INVALID_ACCOUNTID);
-      return Promise.reject({
-        error: ERROR_INVALID_ACCOUNTID
-      });
+      return this._error(ERROR_INVALID_ACCOUNTID);
     }
 
     if (!aPassword) {
-      log.error(ERROR_INVALID_PASSWORD);
-      return Promise.reject({
-        error: ERROR_INVALID_PASSWORD
-      });
+      return this._error(ERROR_INVALID_PASSWORD);
     }
 
     // Check that there is no signed in account first.
     if (this._activeSession) {
-      log.error(ERROR_ALREADY_SIGNED_IN_USER);
-      return Promise.reject({
-        error: ERROR_ALREADY_SIGNED_IN_USER,
-        details: {
-          user: this._user
-        }
+      return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
+        user: this._user
       });
     }
 
     let client = this._createFxAccountsClient();
     return this._fxAccounts.getSignedInUser().then(
       user => {
         if (user) {
-          log.error(ERROR_ALREADY_SIGNED_IN_USER);
-          return Promise.reject({
-            error: ERROR_ALREADY_SIGNED_IN_USER,
-            details: {
-              user: user
-            }
+          return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
+            user: this._user
           });
         }
         return client[aMethod](aAccountId, aPassword);
       }
     ).then(
       user => {
         let error = this._getError(user);
         if (!user || !user.uid || !user.sessionToken || error) {
-          log.error(error ? error : ERROR_INTERNAL_INVALID_USER);
-          return Promise.reject({
-            error: error ? error : ERROR_INTERNAL_INVALID_USER,
-            details: {
-              user: user
-            }
+          return this._error(error ? error : ERROR_INTERNAL_INVALID_USER, {
+            user: user
           });
         }
 
         // Save the credentials of the signed in user.
         user.email = aAccountId;
         return this._fxAccounts.setSignedInUser(user, false).then(
           () => {
             this._activeSession = user;
@@ -185,32 +171,58 @@ this.FxAccountsManager = {
           return Promise.resolve();
         }
         // Otherwise, we try to remove the remote session.
         let client = this._createFxAccountsClient();
         return client.signOut(sessionToken).then(
           result => {
             let error = this._getError(result);
             if (error) {
-              return Promise.reject({
-                error: error,
-                details: result
-              });
+              return this._error(error, result);
             }
             log.debug("Signed out");
             return Promise.resolve();
           },
           reason => {
             return this._serverError(reason);
           }
         );
       }
     );
   },
 
+  _uiRequest: function(aRequest, aAudience, aParams) {
+    let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
+               .createInstance(Ci.nsIFxAccountsUIGlue);
+    if (!ui[aRequest]) {
+      return this._error(ERROR_UI_REQUEST);
+    }
+
+    if (!aParams || !Array.isArray(aParams)) {
+      aParams = [aParams];
+    }
+
+    return ui[aRequest].apply(this, aParams).then(
+      result => {
+        // Even if we get a successful result from the UI, the account will
+        // most likely be unverified, so we cannot get an assertion.
+        if (result && result.verified) {
+          return this._getAssertion(aAudience);
+        }
+
+        return this._error(ERROR_UNVERIFIED_ACCOUNT, {
+          user: result
+        });
+      },
+      error => {
+        return this._error(ERROR_UI_ERROR, error);
+      }
+    );
+  },
+
   // -- API --
 
   signIn: function(aAccountId, aPassword) {
     return this._signInSignUp("signIn", aAccountId, aPassword);
   },
 
   signUp: function(aAccountId, aPassword) {
     return this._signInSignUp("signUp", aAccountId, aPassword);
@@ -267,83 +279,65 @@ this.FxAccountsManager = {
         return Promise.resolve(this._user);
       }
     );
   },
 
   queryAccount: function(aAccountId) {
     log.debug("queryAccount " + aAccountId);
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     let deferred = Promise.defer();
 
     if (!aAccountId) {
-      log.error(ERROR_INVALID_ACCOUNTID);
-      return Promise.reject({
-        error: ERROR_INVALID_ACCOUNTID
-      });
+      return this._error(ERROR_INVALID_ACCOUNTID);
     }
 
     let client = this._createFxAccountsClient();
     return client.accountExists(aAccountId).then(
       result => {
         log.debug("Account " + result ? "" : "does not" + " exists");
         let error = this._getError(result);
         if (error) {
-          return Promise.reject({
-            error: error,
-            details: result
-          });
+          return this._error(error, result);
         }
 
         return Promise.resolve({
           registered: result
         });
       },
       reason => { this._serverError(reason); }
     );
   },
 
   verificationStatus: function() {
     log.debug("verificationStatus");
     if (!this._activeSession || !this._activeSession.sessionToken) {
-      log.error(ERROR_NO_TOKEN_SESSION);
-      return Promise.reject({
-        error: ERROR_NO_TOKEN_SESSION
-      });
+      return this._error(ERROR_NO_TOKEN_SESSION);
     }
 
     // There is no way to unverify an already verified account, so we just
     // return the account details of a verified account
     if (this._activeSession.verified) {
       log.debug("Account already verified");
       return Promise.resolve(this._user);
     }
 
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     let client = this._createFxAccountsClient();
     return client.recoveryEmailStatus(this._activeSession.sessionToken).then(
       data => {
         let error = this._getError(data);
         if (error) {
-          return Promise.reject({
-            error: error,
-            details: data
-          });
+          return this._error(error, data);
         }
 
         // If the verification status is different from the one that we have
         // stored, we update it and return the session data. If not, we simply
         // return the session data.
         if (this._activeSession.verified != data.verified) {
           this._activeSession.verified = data.verified;
           return this._fxAccounts.setSignedInUser(this._activeSession).then(
@@ -355,76 +349,63 @@ this.FxAccountsManager = {
         }
         log.debug(JSON.stringify(this._user));
         return Promise.resolve(this._user);
       },
       reason => { return this._serverError(reason); }
     );
   },
 
-  getAssertion: function(aAudience) {
-    log.debug("getAssertion " + aAudience);
+  getAssertion: function(aAudience, aOptions) {
+    log.debug("getAssertion " + aAudience + JSON.stringify(aOptions));
     if (!aAudience) {
-      log.error(ERROR_INVALID_AUDIENCE);
-      return Promise.reject({
-        error: ERROR_INVALID_AUDIENCE
-      });
+      return this._error(ERROR_INVALID_AUDIENCE);
     }
 
     if (Services.io.offline) {
-      log.error(ERROR_OFFLINE);
-      return Promise.reject({
-        error: ERROR_OFFLINE
-      });
+      return this._error(ERROR_OFFLINE);
     }
 
     return this.getAccount().then(
       user => {
         if (user) {
           // We cannot get assertions for unverified accounts.
-          if (user.verified) {
-            return this._getAssertion(aAudience);
+          if (!user.verified) {
+            return this._error(ERROR_UNVERIFIED_ACCOUNT, {
+              user: user
+            });
           }
 
-          log.error(ERROR_UNVERIFIED_ACCOUNT);
-          return Promise.reject({
-            error: ERROR_UNVERIFIED_ACCOUNT,
-            details: {
-              user: user
+          // RPs might require an authentication refresh.
+          if (aOptions &&
+              aOptions.refreshAuthentication) {
+            let gracePeriod = aOptions.refreshAuthentication;
+            if (typeof gracePeriod != 'number' || isNaN(gracePeriod)) {
+              return this._error(ERROR_INVALID_REFRESH_AUTH_VALUE);
             }
-          });
+
+            if ((Date.now() / 1000) - this._activeSession.authAt > gracePeriod) {
+              // Grace period expired, so we sign out and request the user to
+              // authenticate herself again. If the authentication succeeds, we
+              // will return the assertion. Otherwise, we will return an error.
+              return this._signOut().then(
+                () => {
+                  return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
+                                         aAudience, user.accountId);
+                }
+              );
+            }
+          }
+
+          return this._getAssertion(aAudience);
         }
 
         log.debug("No signed in user");
         // If there is no currently signed in user, we trigger the signIn UI
         // flow.
-        let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
-                   .createInstance(Ci.nsIFxAccountsUIGlue);
-        return ui.signInFlow().then(
-          result => {
-            // Even if we get a successful result from the UI, the account will
-            // most likely be unverified, so we cannot get an assertion.
-            if (result && result.verified) {
-              return this._getAssertion(aAudience);
-            }
-
-            log.error(ERROR_UNVERIFIED_ACCOUNT);
-            return Promise.reject({
-              error: ERROR_UNVERIFIED_ACCOUNT,
-              details: {
-                user: result
-              }
-            });
-          },
-          error => {
-            log.error(ERROR_UI_ERROR + " " + error);
-            return Promise.reject({
-              error: ERROR_UI_ERROR,
-              details: error
-            });
-          }
-        );
+        return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience);
       }
     );
   }
+
 };
 
 FxAccountsManager.init();
--- a/services/fxaccounts/tests/xpcshell/test_manager.js
+++ b/services/fxaccounts/tests/xpcshell/test_manager.js
@@ -30,44 +30,56 @@ let fakeFxAccountsUIGlueFactory = {
 // FxAccountsUIGlue fake component.
 let FxAccountsUIGlue = {
   _reject: false,
 
   _error: 'error',
 
   _signInFlowCalled: false,
 
+  _refreshAuthCalled: false,
+
+  _activeSession: null,
+
   _reset: function() {
     this._reject = false;
     this._error = 'error';
     this._signInFlowCalled = false;
+    this._refreshAuthCalled = false;
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFxAccountsUIGlue]),
 
-  getUserPermission: function() {},
-
-  signInFlow: function() {
-    this._signInFlowCalled = true;
+  _promise: function() {
     let deferred = Promise.defer();
 
     if (this._reject) {
       deferred.reject(this._error);
     } else {
-      FxAccountsManager._activeSession = {
+      FxAccountsManager._activeSession = this._activeSession || {
         email: "user@domain.org",
         verified: false,
         sessionToken: "1234"
       };
       FxAccountsManager._fxAccounts
                        .setSignedInUser(FxAccountsManager._activeSession);
-      deferred.resolve();
+      deferred.resolve(FxAccountsManager._activeSession);
     }
 
     return deferred.promise;
+  },
+
+  signInFlow: function() {
+    this._signInFlowCalled = true;
+    return this._promise();
+  },
+
+  refreshAuthentication: function() {
+    this._refreshAuthCalled = true;
+    return this._promise();
   }
 };
 
 (function registerFakeFxAccountsUIGlue() {
   Cm.QueryInterface(Ci.nsIComponentRegistrar)
     .registerFactory(Components.ID(kFxAccountsUIGlueUUID),
                      "FxAccountsUIGlue",
                      kFxAccountsUIGlueContractID,
@@ -219,132 +231,201 @@ do_register_cleanup(function() {
 
 // === Tests ===
 
 function run_test() {
   run_next_test();
 }
 
 add_test(function test_initial_state() {
-  do_print("= Test 0 | Initial state =");
+  do_print("= Initial state =");
   do_check_neq(FxAccountsManager, undefined);
   do_check_null(FxAccountsManager._activeSession);
   do_check_null(FxAccountsManager._user);
   run_next_test();
 });
 
 add_test(function(test_getAccount_no_session) {
-  do_print("= Test 1 | getAccount no session =");
+  do_print("= getAccount no session =");
   FxAccountsManager.getAccount().then(
     result => {
       do_check_null(result);
       do_check_null(FxAccountsManager._activeSession);
       do_check_null(FxAccountsManager._user);
       do_check_true(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       FxAccountsManager._fxAccounts._reset();
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAssertion_no_audience) {
-  do_print("= Test 2 | getAssertion no audience =");
+  do_print("= getAssertion no audience =");
   FxAccountsManager.getAssertion().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_AUDIENCE);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_no_session_ui_error) {
-  do_print("= Test 3 | getAssertion no session, UI error =");
+  do_print("= getAssertion no session, UI error =");
   FxAccountsUIGlue._reject = true;
   FxAccountsManager.getAssertion("audience").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_UI_ERROR);
       do_check_eq(error.details, "error");
       FxAccountsUIGlue._reset();
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_no_session_ui_success) {
-  do_print("= Test 4 | getAssertion no session, UI success =");
+  do_print("= getAssertion no session, UI success =");
   FxAccountsManager.getAssertion("audience").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_true(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(error.error, ERROR_UNVERIFIED_ACCOUNT);
       FxAccountsUIGlue._reset();
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_active_session_unverified_account) {
-  do_print("= Test 5 | getAssertion active session, unverified account =");
+  do_print("= getAssertion active session, unverified account =");
   FxAccountsManager.getAssertion("audience").then(
     result => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_false(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(error.error, ERROR_UNVERIFIED_ACCOUNT);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_getAssertion_active_session_verified_account) {
-  do_print("= Test 6 | getAssertion active session, verified account =");
+  do_print("= getAssertion active session, verified account =");
   FxAccountsManager._fxAccounts._signedInUser.verified = true;
   FxAccountsManager._activeSession.verified = true;
   FxAccountsManager.getAssertion("audience").then(
     result => {
       do_check_false(FxAccountsUIGlue._signInFlowCalled);
       do_check_eq(result, "assertion");
       FxAccountsManager._fxAccounts._reset();
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
+add_test(function(test_getAssertion_refreshAuth) {
+  do_print("= getAssertion refreshAuth =");
+  let gracePeriod = 1200;
+  FxAccountsUIGlue._activeSession = {
+    email: "user@domain.org",
+    verified: true,
+    sessionToken: "1234"
+  };
+  FxAccountsManager._fxAccounts._signedInUser.verified = true;
+  FxAccountsManager._activeSession.verified = true;
+  FxAccountsManager._activeSession.authAt =
+    (Date.now() / 1000) - gracePeriod;
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": gracePeriod
+  }).then(
+    result => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_true(FxAccountsUIGlue._refreshAuthCalled);
+      do_check_eq(result, "assertion");
+      FxAccountsManager._fxAccounts._reset();
+      FxAccountsUIGlue._reset();
+      run_next_test();
+    },
+    error => {
+      do_throw("Unexpected error: " + error);
+    }
+  );
+});
+
+add_test(function(test_getAssertion_refreshAuth_NaN) {
+  do_print("= getAssertion refreshAuth NaN=");
+  let gracePeriod = "NaN";
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": gracePeriod
+  }).then(
+    result => {
+      do_throw("Unexpected success");
+    },
+    error => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_false(FxAccountsUIGlue._refreshAuthCalled);
+      do_check_eq(error.error, ERROR_INVALID_REFRESH_AUTH_VALUE);
+      FxAccountsManager._fxAccounts._reset();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function(test_getAssertion_refresh_auth_no_refresh) {
+  do_print("= getAssertion refreshAuth no refresh =");
+  FxAccountsManager._fxAccounts._signedInUser.verified = true;
+  FxAccountsManager._activeSession.verified = true;
+  FxAccountsManager._activeSession.authAt =
+    (Date.now() / 1000) + 10000;
+  FxAccountsManager.getAssertion("audience", {
+    "refreshAuthentication": 1
+  }).then(
+    result => {
+      do_check_false(FxAccountsUIGlue._signInFlowCalled);
+      do_check_eq(result, "assertion");
+      FxAccountsManager._fxAccounts._reset();
+      run_next_test();
+    },
+    error => {
+      do_throw("Unexpected error: " + error);
+    }
+  );
+});
+
 add_test(function(test_getAccount_existing_verified_session) {
-  do_print("= Test 7 | getAccount, existing verified session =");
+  do_print("= getAccount, existing verified session =");
   FxAccountsManager.getAccount().then(
     result => {
       do_check_false(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
       do_check_eq(result.verified, FxAccountsManager._user.verified);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAccount_existing_unverified_session_unverified_user) {
-  do_print("= Test 8 | getAccount, existing unverified session, unverified user =");
+  do_print("= getAccount, existing unverified session, unverified user =");
   FxAccountsManager._activeSession.verified = false;
   FxAccountsManager._fxAccounts._signedInUser.verified = false;
   FxAccountsManager.getAccount().then(
     result => {
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_false(result.verified);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
       FakeFxAccountsClient._reset();
@@ -352,17 +433,17 @@ add_test(function(test_getAccount_existi
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_getAccount_existing_unverified_session_verified_user) {
-  do_print("= Test 8 | getAccount, existing unverified session, verified user =");
+  do_print("= getAccount, existing unverified session, verified user =");
   FxAccountsManager._activeSession.verified = false;
   FxAccountsManager._fxAccounts._signedInUser.verified = false;
   FakeFxAccountsClient._verified = true;
   FxAccountsManager.getAccount().then(
     result => {
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_true(result.verified);
       do_check_eq(result.accountId, FxAccountsManager._user.accountId);
@@ -371,99 +452,99 @@ add_test(function(test_getAccount_existi
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_signOut) {
-  do_print("= Test 9 | signOut =");
+  do_print("= signOut =");
   do_check_true(FxAccountsManager._activeSession != null);
   FxAccountsManager.signOut().then(
     result => {
       do_check_null(result);
       do_check_null(FxAccountsManager._activeSession);
       do_check_true(FakeFxAccountsClient._signOutCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_verificationStatus_no_token_session) {
-  do_print("= Test 10 | verificationStatus, no token session =");
+  do_print("= verificationStatus, no token session =");
   do_check_null(FxAccountsManager._activeSession);
   FxAccountsManager.verificationStatus().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_NO_TOKEN_SESSION);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp_no_accountId) {
-  do_print("= Test 11 | signUp, no accountId=");
+  do_print("= signUp, no accountId=");
   FxAccountsManager.signUp().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_no_accountId) {
-  do_print("= Test 12 | signIn, no accountId=");
+  do_print("= signIn, no accountId=");
   FxAccountsManager.signIn().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp_no_password) {
-  do_print("= Test 13 | signUp, no accountId=");
+  do_print("= signUp, no accountId=");
   FxAccountsManager.signUp("user@domain.org").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_PASSWORD);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_no_accountId) {
-  do_print("= Test 14 | signIn, no accountId=");
+  do_print("= signIn, no accountId=");
   FxAccountsManager.signIn("user@domain.org").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_PASSWORD);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signUp) {
-  do_print("= Test 15 | signUp =");
+  do_print("= signUp =");
   FakeFxAccountsClient._verified = false;
   FxAccountsManager.signUp("user@domain.org", "password").then(
     result => {
       do_check_true(FakeFxAccountsClient._signInCalled);
       do_check_true(FakeFxAccountsClient._signUpCalled);
       do_check_true(FxAccountsManager._fxAccounts._getSignedInUserCalled);
       do_check_eq(FxAccountsManager._fxAccounts._signedInUser.email, "user@domain.org");
       do_check_eq(FakeFxAccountsClient._password, "password");
@@ -476,119 +557,119 @@ add_test(function(test_signUp) {
     },
     error => {
       do_throw("Unexpected error: " + error.error);
     }
   );
 });
 
 add_test(function(test_signUp_already_signed_user) {
-  do_print("= Test 16 | signUp, already signed user =");
+  do_print("= signUp, already signed user =");
   FxAccountsManager.signUp("user@domain.org", "password").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_false(FakeFxAccountsClient._signInCalled);
       do_check_eq(error.error, ERROR_ALREADY_SIGNED_IN_USER);
       do_check_eq(error.details.user.accountId, "user@domain.org");
       do_check_false(error.details.user.verified);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_signIn_already_signed_user) {
-  do_print("= Test 17 | signIn, already signed user =");
+  do_print("= signIn, already signed user =");
   FxAccountsManager.signIn("user@domain.org", "password").then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_ALREADY_SIGNED_IN_USER);
       do_check_eq(error.details.user.accountId, "user@domain.org");
       do_check_false(error.details.user.verified);
       run_next_test();
     }
   );
 });
 
 add_test(function(test_verificationStatus_unverified_session_unverified_user) {
-  do_print("= Test 18 | verificationStatus unverified session and user =");
+  do_print("= verificationStatus unverified session and user =");
   FakeFxAccountsClient._verified = false;
   FxAccountsManager.verificationStatus().then(
     user => {
       do_check_false(user.verified);
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_false(FxAccountsManager._fxAccounts._setSignedInUserCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_verificationStatus_unverified_session_verified_user) {
-  do_print("= Test 19 | verificationStatus unverified session, verified user =");
+  do_print("= verificationStatus unverified session, verified user =");
   FakeFxAccountsClient._verified = true;
   FxAccountsManager.verificationStatus().then(
     user => {
       do_check_true(user.verified);
       do_check_true(FakeFxAccountsClient._recoveryEmailStatusCalled);
       do_check_true(FxAccountsManager._fxAccounts._setSignedInUserCalled);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_no_exists) {
-  do_print("= Test 20 | queryAccount, no exists =");
+  do_print("= queryAccount, no exists =");
   FxAccountsManager.queryAccount("user@domain.org").then(
     result => {
       do_check_false(result.registered);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_exists) {
-  do_print("= Test 21 | queryAccount, exists =");
+  do_print("= queryAccount, exists =");
   FakeFxAccountsClient._accountExists = true;
   FxAccountsManager.queryAccount("user@domain.org").then(
     result => {
       do_check_true(result.registered);
       run_next_test();
     },
     error => {
       do_throw("Unexpected error: " + error);
     }
   );
 });
 
 add_test(function(test_queryAccount_no_accountId) {
-  do_print("= Test 22 | queryAccount, no accountId =");
+  do_print("= queryAccount, no accountId =");
   FxAccountsManager.queryAccount().then(
     () => {
       do_throw("Unexpected success");
     },
     error => {
       do_check_eq(error.error, ERROR_INVALID_ACCOUNTID);
       run_next_test();
     }
   );
 });
 
 add_test(function() {
-  do_print("= Test 23 | fxaccounts:onlogout notification =");
+  do_print("= fxaccounts:onlogout notification =");
   do_check_true(FxAccountsManager._activeSession != null);
   Services.obs.notifyObservers(null, ONLOGOUT_NOTIFICATION, null);
   do_execute_soon(function() {
     do_check_null(FxAccountsManager._activeSession);
     run_next_test();
   });
 });