Bug 1448165 p1 - Simplify and factorize FxAccounts signout mechanism. r?markh draft
authorEdouard Oger <eoger@fastmail.com>
Thu, 22 Mar 2018 11:54:38 -0400
changeset 774427 5e6d727d51a8f256e2e1f19188698fc40b521028
parent 773797 a456475502b80a1264642d9eaee9394a8fad8315
child 774428 4cdad8fd11bcc68505890249b0d7c70ab1d65b17
push id104396
push userbmo:eoger@fastmail.com
push dateWed, 28 Mar 2018 21:42:47 +0000
reviewersmarkh
bugs1448165
milestone61.0a1
Bug 1448165 p1 - Simplify and factorize FxAccounts signout mechanism. r?markh Since FxA's /session/destroy now also removes the current device registration, there's no need to differenciate signouts w/ and w/o device registration. MozReview-Commit-ID: 3lHV3JC1NU6
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/FxAccountsClient.jsm
services/fxaccounts/tests/xpcshell/test_accounts.js
services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
services/fxaccounts/tests/xpcshell/test_client.js
services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -65,17 +65,16 @@ var publicProperties = [
   "removeCachedOAuthToken",
   "resendVerificationEmail",
   "resetCredentials",
   "sessionStatus",
   "setProfileCache",
   "setSignedInUser",
   "signOut",
   "updateDeviceRegistration",
-  "deleteDeviceRegistration",
   "updateUserAccountData",
   "whenVerified",
 ];
 
 // An AccountState object holds all state related to one specific account.
 // Only one AccountState is ever "current" in the FxAccountsInternal object -
 // whenever a user logs out or logs in, the current AccountState is discarded,
 // making it impossible for the wrong state or state data to be accidentally
@@ -114,46 +113,40 @@ AccountState.prototype = {
   },
 
   abort() {
     if (this.whenVerifiedDeferred) {
       this.whenVerifiedDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
       this.whenVerifiedDeferred = null;
     }
-
     if (this.whenKeysReadyDeferred) {
       this.whenKeysReadyDeferred.reject(
         new Error("Verification aborted; Another user signing in"));
       this.whenKeysReadyDeferred = null;
     }
+    return this.signOut();
+  },
 
+  // Clobber all cached data and write that empty data to storage.
+  async signOut() {
     this.cert = null;
     this.keyPair = null;
     this.oauthTokens = null;
+
     // Avoid finalizing the storageManager multiple times (ie, .signOut()
     // followed by .abort())
     if (!this.storageManager) {
-      return Promise.resolve();
+      return;
     }
-    let storageManager = this.storageManager;
+    const storageManager = this.storageManager;
     this.storageManager = null;
-    return storageManager.finalize();
-  },
 
-  // Clobber all cached data and write that empty data to storage.
-  signOut() {
-    this.cert = null;
-    this.keyPair = null;
-    this.oauthTokens = null;
-    let storageManager = this.storageManager;
-    this.storageManager = null;
-    return storageManager.deleteAccountData().then(() => {
-      return storageManager.finalize();
-    });
+    await storageManager.deleteAccountData();
+    await storageManager.finalize();
   },
 
   // Get user account data. Optionally specify explicit field names to fetch
   // (and note that if you require an in-memory field you *must* specify the
   // field name(s).)
   getUserAccountData(fieldNames = null) {
     if (!this.isCurrent) {
       return Promise.reject(new Error("Another user has signed in"));
@@ -567,17 +560,17 @@ FxAccountsInternal.prototype = {
    */
   async setSignedInUser(credentials) {
     if (!FXA_ENABLED) {
       throw new Error("Cannot call setSignedInUser when FxA is disabled.");
     }
     log.debug("setSignedInUser - aborting any existing flows");
     const signedInUser = await this.getSignedInUser();
     if (signedInUser) {
-      await this.deleteDeviceRegistration(signedInUser.sessionToken, signedInUser.deviceId);
+      await this._signOutServer(signedInUser.sessionToken, signedInUser.oauthTokens);
     }
     await this.abortExistingFlow();
     let currentAccountState = this.currentAccountState = this.newAccountState(
       Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object.
     );
     // This promise waits for storage, but not for verification.
     // We're telling the caller that this is durable now (although is that
     // really something we should commit to? Why not let the write happen in
@@ -714,17 +707,17 @@ FxAccountsInternal.prototype = {
       }
       throw new Error("Cannot resend verification email; no signed-in user");
     });
   },
 
   /*
    * Reset state such that any previous flow is canceled.
    */
-  abortExistingFlow: function abortExistingFlow() {
+  abortExistingFlow() {
     if (this.currentTimer) {
       log.debug("Polling aborted; Another user signing in");
       clearTimeout(this.currentTimer);
       this.currentTimer = 0;
     }
     if (this._profile) {
       this._profile.tearDown();
       this._profile = null;
@@ -764,104 +757,84 @@ FxAccountsInternal.prototype = {
     let client = new FxAccountsOAuthGrantClient({
       serverURL: tokenData.server,
       client_id: FX_OAUTH_CLIENT_ID
     });
     return client.destroyToken(tokenData.token);
   },
 
   _destroyAllOAuthTokens(tokenInfos) {
+    if (!tokenInfos) {
+      return Promise.resolve();
+    }
     // let's just destroy them all in parallel...
     let promises = [];
-    for (let tokenInfo of Object.values(tokenInfos || {})) {
+    for (let tokenInfo of Object.values(tokenInfos)) {
       promises.push(this._destroyOAuthToken(tokenInfo));
     }
     return Promise.all(promises);
   },
 
-  signOut: function signOut(localOnly) {
-    let currentState = this.currentAccountState;
+  async signOut(localOnly) {
     let sessionToken;
     let tokensToRevoke;
-    let deviceId;
-    return currentState.getUserAccountData().then(data => {
-      // Save the session token, tokens to revoke and the
-      // device id for use in the call to signOut below.
-      if (data) {
-        sessionToken = data.sessionToken;
-        tokensToRevoke = data.oauthTokens;
-        deviceId = data.deviceId;
-      }
-      return this._signOutLocal();
-    }).then(() => {
-      // FxAccountsManager calls here, then does its own call
-      // to FxAccountsClient.signOut().
-      if (!localOnly) {
-        // Wrap this in a promise so *any* errors in signOut won't
-        // block the local sign out. This is *not* returned.
-        Promise.resolve().then(() => {
-          // This can happen in the background and shouldn't block
-          // the user from signing out. The server must tolerate
-          // clients just disappearing, so this call should be best effort.
-          if (sessionToken) {
-            return this._signOutServer(sessionToken, deviceId);
-          }
-          log.warn("Missing session token; skipping remote sign out");
-          return null;
-        }).catch(err => {
-          log.error("Error during remote sign out of Firefox Accounts", err);
-        }).then(() => {
-          return this._destroyAllOAuthTokens(tokensToRevoke);
-        }).catch(err => {
-          log.error("Error during destruction of oauth tokens during signout", err);
-        }).then(() => {
-          FxAccountsConfig.resetConfigURLs();
-          // just for testing - notifications are cheap when no observers.
-          return this.notifyObservers("testhelper-fxa-signout-complete");
-        });
-      } else {
-        // We want to do this either way -- but if we're signing out remotely we
-        // need to wait until we destroy the oauth tokens if we want that to succeed.
+    const data = await this.currentAccountState.getUserAccountData();
+    // Save the sessionToken, tokens before resetting them in _signOutLocal().
+    if (data) {
+      sessionToken = data.sessionToken;
+      tokensToRevoke = data.oauthTokens;
+    }
+    await this._signOutLocal();
+    if (!localOnly) {
+      // Do this in the background so *any* slow request won't
+      // block the local sign out.
+      Services.tm.dispatchToMainThread(async () => {
+        await this._signOutServer(sessionToken, tokensToRevoke);
         FxAccountsConfig.resetConfigURLs();
-      }
-    }).then(() => {
-      return this.notifyObservers(ONLOGOUT_NOTIFICATION);
-    });
+        this.notifyObservers("testhelper-fxa-signout-complete");
+      });
+    } else {
+      // We want to do this either way -- but if we're signing out remotely we
+      // need to wait until we destroy the oauth tokens if we want that to succeed.
+      FxAccountsConfig.resetConfigURLs();
+    }
+    return this.notifyObservers(ONLOGOUT_NOTIFICATION);
   },
 
-  /**
-   * This function should be called in conjunction with a server-side
-   * signOut via FxAccountsClient.
-   */
-  _signOutLocal: function signOutLocal() {
-    let currentAccountState = this.currentAccountState;
-    return currentAccountState.signOut().then(() => {
-      // this "aborts" this.currentAccountState but doesn't make a new one.
-      return this.abortExistingFlow();
-    }).then(() => {
-      this.currentAccountState = this.newAccountState();
-      return this.currentAccountState.promiseInitialized;
-    });
+  async _signOutLocal() {
+    await this.currentAccountState.signOut();
+    // this "aborts" this.currentAccountState but doesn't make a new one.
+    await this.abortExistingFlow();
+    this.currentAccountState = this.newAccountState();
+    return this.currentAccountState.promiseInitialized;
   },
 
-  _signOutServer(sessionToken, deviceId) {
-    // For now we assume the service being logged out from is Sync, so
-    // we must tell the server to either destroy the device or sign out
-    // (if no device exists). We might need to revisit this when this
-    // FxA code is used in a context that isn't Sync.
-
-    const options = { service: "sync" };
-
-    if (deviceId) {
-      log.debug("destroying device, session and unsubscribing from FxA push");
-      return this.deleteDeviceRegistration(sessionToken, deviceId);
+  async _signOutServer(sessionToken, tokensToRevoke) {
+    log.debug("Unsubscribing from FxA push.");
+    try {
+      await this.fxaPushService.unsubscribe();
+    } catch (err) {
+      log.error("Could not unsubscribe from push.", err);
     }
-
-    log.debug("destroying session");
-    return this.fxAccountsClient.signOut(sessionToken, options);
+    if (sessionToken) {
+      log.debug("Destroying session and device.");
+      try {
+        await this.fxAccountsClient.signOut(sessionToken, {service: "sync"});
+      } catch (err) {
+        log.error("Error during remote sign out of Firefox Accounts", err);
+      }
+    } else {
+      log.warn("Missing session token; skipping remote sign out");
+    }
+    log.debug("Destroying all OAuth tokens.");
+    try {
+      await this._destroyAllOAuthTokens(tokensToRevoke);
+    } catch (err) {
+      log.error("Error during destruction of oauth tokens during signout", err);
+    }
   },
 
   /**
    * Check the status of the current session using cached credentials.
    *
    * @return Promise
    *        Resolves with a boolean indicating if the session is still valid
    */
@@ -1581,41 +1554,16 @@ FxAccountsInternal.prototype = {
     return this.getSignedInUser().then(signedInUser => {
       if (signedInUser) {
         return this._registerOrUpdateDevice(signedInUser);
       }
       return null;
     }).catch(error => this._logErrorAndResetDeviceRegistrationVersion(error));
   },
 
-  // Delete the Push Subscription and the device registration on the auth server.
-  // Returns a promise that always resolves, never rejects.
-  async deleteDeviceRegistration(sessionToken, deviceId) {
-    try {
-      // Allow tests to skip device registration because it makes remote requests to the auth server.
-      if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
-        return Promise.resolve();
-      }
-    } catch (ignore) {}
-
-    try {
-      await this.fxaPushService.unsubscribe();
-      if (sessionToken && deviceId) {
-        await this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId);
-      }
-      await this.currentAccountState.updateUserAccountData({
-        deviceId: null,
-        deviceRegistrationVersion: null
-      });
-    } catch (err) {
-      log.error("Could not delete the device registration", err);
-    }
-    return Promise.resolve();
-  },
-
   async handleDeviceDisconnection(deviceId) {
     const accountData = await this.currentAccountState.getUserAccountData();
     const localDeviceId = accountData ? accountData.deviceId : null;
     const isLocalDevice = (deviceId == localDeviceId);
     if (isLocalDevice) {
       this.signOut(true);
     }
     const data = JSON.stringify({ isLocalDevice });
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -197,17 +197,18 @@ this.FxAccountsClient.prototype = {
             return Promise.resolve(false);
           }
           throw error;
         }
       );
   },
 
   /**
-   * Destroy the current session with the Firefox Account API server
+   * Destroy the current session with the Firefox Account API server and its
+   * associated device.
    *
    * @param sessionTokenHex
    *        The session token encoded in hex
    * @return Promise
    */
   signOut(sessionTokenHex, options = {}) {
     let path = "/session/destroy";
     if (options.service) {
@@ -482,46 +483,16 @@ this.FxAccountsClient.prototype = {
       body.pushPublicKey = options.pushPublicKey;
       body.pushAuthKey = options.pushAuthKey;
     }
 
     return this._request(path, "POST", creds, body);
   },
 
   /**
-   * Delete a device and its associated session token, signing the user
-   * out of the server.
-   *
-   * @method signOutAndDestroyDevice
-   * @param  sessionTokenHex
-   *         Session token obtained from signIn
-   * @param  id
-   *         Device identifier
-   * @param  [options]
-   *         Options object
-   * @param  [options.service]
-   *         `service` query parameter
-   * @return Promise
-   *         Resolves to an empty object:
-   *         {}
-   */
-  signOutAndDestroyDevice(sessionTokenHex, id, options = {}) {
-    let path = "/account/device/destroy";
-
-    if (options.service) {
-      path += "?service=" + encodeURIComponent(options.service);
-    }
-
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-    let body = { id };
-
-    return this._request(path, "POST", creds, body);
-  },
-
-  /**
    * Get a list of currently registered devices
    *
    * @method getDeviceList
    * @param  sessionTokenHex
    *         Session token obtained from signIn
    * @return Promise
    *         Resolves to an array of objects:
    *         [
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -108,17 +108,16 @@ function MockFxAccountsClient() {
   this.resendVerificationEmail = function(sessionToken) {
     // Return the session token to show that we received it in the first place
     return Promise.resolve(sessionToken);
   };
 
   this.signCertificate = function() { throw new Error("no"); };
 
   this.signOut = () => Promise.resolve();
-  this.signOutAndDestroyDevice = () => Promise.resolve({});
 
   FxAccountsClient.apply(this);
 }
 MockFxAccountsClient.prototype = {
   __proto__: FxAccountsClient.prototype
 };
 
 /*
@@ -222,40 +221,40 @@ add_task(async function test_get_signed_
   let localOnly = true;
   await account.signOut(localOnly);
 
   // user should be undefined after sign out
   result = await account.getSignedInUser();
   Assert.equal(result, null);
 });
 
-add_task(async function test_set_signed_in_user_deletes_previous_device() {
-  _("Check setSignedInUser tries to delete a previous registered device");
+add_task(async function test_set_signed_in_user_signs_out_previous_account() {
+  _("Check setSignedInUser signs out the previous account.");
   let account = MakeFxAccounts();
-  let deleteDeviceRegistrationCalled = false;
+  let signOutServerCalled = false;
   let credentials = {
     email: "foo@example.com",
     uid: "1234@lcip.org",
     assertion: "foobar",
     sessionToken: "dead",
     kSync: "beef",
     kXCS: "cafe",
     kExtSync: "bacon",
     kExtKbHash: "cheese",
     verified: true
   };
   await account.setSignedInUser(credentials);
 
-  account.internal.deleteDeviceRegistration = () => {
-    deleteDeviceRegistrationCalled = true;
+  account.internal._signOutServer = () => {
+    signOutServerCalled = true;
     return Promise.resolve(true);
   };
 
   await account.setSignedInUser(credentials);
-  Assert.ok(deleteDeviceRegistrationCalled);
+  Assert.ok(signOutServerCalled);
 });
 
 add_task(async function test_update_account_data() {
   _("Check updateUserAccountData does the right thing.");
   let account = MakeFxAccounts();
   let credentials = {
     email: "foo@example.com",
     uid: "1234@lcip.org",
@@ -1074,133 +1073,16 @@ add_test(function test_resend_email() {
         // Ok abort polling before we go on to the next test
         fxa.internal.abortExistingFlow();
         run_next_test();
       });
     });
   });
 });
 
-add_task(async function test_sign_out_with_device() {
-  const fxa = new MockFxAccounts();
-
-  const credentials = getTestUser("alice");
-  await fxa.internal.setSignedInUser(credentials);
-
-  const user = await fxa.internal.getUserAccountData();
-  Assert.ok(user);
-  Object.keys(credentials).forEach(key => Assert.equal(credentials[key], user[key]));
-
-  const spy = {
-    signOut: { count: 0 },
-    deleteDeviceRegistration: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.signOut = function() {
-    spy.signOut.count += 1;
-    return Promise.resolve();
-  };
-  fxa.internal.deleteDeviceRegistration = function() {
-    spy.deleteDeviceRegistration.count += 1;
-    spy.deleteDeviceRegistration.args.push(arguments);
-    return Promise.resolve();
-  };
-
-  const promise = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, () => {
-      log.debug("test_sign_out_with_device observed onlogout");
-      // user should be undefined after sign out
-      fxa.internal.getUserAccountData().then(user2 => {
-        Assert.equal(user2, null);
-        Assert.equal(spy.signOut.count, 0);
-        Assert.equal(spy.deleteDeviceRegistration.count, 1);
-        Assert.equal(spy.deleteDeviceRegistration.args[0].length, 2);
-        Assert.equal(spy.deleteDeviceRegistration.args[0][0], credentials.sessionToken);
-        Assert.equal(spy.deleteDeviceRegistration.args[0][1], credentials.deviceId);
-        resolve();
-      });
-    });
-  });
-
-  await fxa.signOut();
-
-  await promise;
-});
-
-add_task(async function test_sign_out_without_device() {
-  const fxa = new MockFxAccounts();
-
-  const credentials = getTestUser("alice");
-  delete credentials.deviceId;
-  await fxa.internal.setSignedInUser(credentials);
-
-  await fxa.internal.getUserAccountData();
-
-  const spy = {
-    signOut: { count: 0, args: [] },
-    deleteDeviceRegistration: { count: 0 }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.signOut = function() {
-    spy.signOut.count += 1;
-    spy.signOut.args.push(arguments);
-    return Promise.resolve();
-  };
-  fxa.internal.deleteDeviceRegistration = function() {
-    spy.deleteDeviceRegistration.count += 1;
-    return Promise.resolve();
-  };
-
-  const promise = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, () => {
-      log.debug("test_sign_out_without_device observed onlogout");
-      // user should be undefined after sign out
-      fxa.internal.getUserAccountData().then(user2 => {
-        Assert.equal(user2, null);
-        Assert.equal(spy.signOut.count, 1);
-        Assert.equal(spy.signOut.args[0].length, 2);
-        Assert.equal(spy.signOut.args[0][0], credentials.sessionToken);
-        Assert.ok(spy.signOut.args[0][1]);
-        Assert.equal(spy.signOut.args[0][1].service, "sync");
-        Assert.equal(spy.deleteDeviceRegistration.count, 0);
-        resolve();
-      });
-    });
-  });
-
-  await fxa.signOut();
-
-  await promise;
-});
-
-add_task(async function test_sign_out_with_remote_error() {
-  let fxa = new MockFxAccounts();
-  let remoteSignOutCalled = false;
-  // Force remote sign out to trigger an error
-  fxa.internal.deleteDeviceRegistration = function() {
-    remoteSignOutCalled = true;
-    throw new Error("Remote sign out error");
-  };
-  let promiseLogout = new Promise(resolve => {
-    makeObserver(ONLOGOUT_NOTIFICATION, function() {
-      log.debug("test_sign_out_with_remote_error observed onlogout");
-      resolve();
-    });
-  });
-
-  let jane = getTestUser("jane");
-  await fxa.setSignedInUser(jane);
-  await fxa.signOut();
-  await promiseLogout;
-
-  let user = await fxa.internal.getUserAccountData();
-  Assert.equal(user, null);
-  Assert.ok(remoteSignOutCalled);
-});
-
 add_test(function test_getOAuthToken() {
   let fxa = new MockFxAccounts();
   let alice = getTestUser("alice");
   alice.verified = true;
   let getTokenFromAssertionCalled = false;
 
   fxa.internal._d_signCertificate.resolve("cert1");
 
--- a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
@@ -73,17 +73,17 @@ function MockFxAccountsClient(device) {
   this.accountStatus = function(uid) {
     return Promise.resolve(!!uid && (!this._deletedOnServer));
   };
 
   const { id: deviceId, name: deviceName, type: deviceType, sessionToken } = device;
 
   this.registerDevice = (st, name, type) => Promise.resolve({ id: deviceId, name });
   this.updateDevice = (st, id, name) => Promise.resolve({ id, name });
-  this.signOutAndDestroyDevice = () => Promise.resolve({});
+  this.signOut = () => Promise.resolve({});
   this.getDeviceList = (st) =>
     Promise.resolve([
       { id: deviceId, name: deviceName, type: deviceType, isCurrentDevice: st === sessionToken }
     ]);
 
   FxAccountsClient.apply(this);
 }
 MockFxAccountsClient.prototype = {
@@ -281,48 +281,16 @@ add_task(async function test_updateDevic
 
   const state = fxa.internal.currentAccountState;
   const data = await state.getUserAccountData();
 
   Assert.equal(null, data.deviceId);
   Assert.equal(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
 });
 
-add_task(async function test_deleteDeviceRegistration() {
-  const credentials = getTestUser("pb");
-  const fxa = new MockFxAccounts({ name: "my device" });
-  await fxa.internal.setSignedInUser(credentials);
-
-  const state = fxa.internal.currentAccountState;
-  let data = await state.getUserAccountData();
-  Assert.equal(data.deviceId, credentials.deviceId);
-  Assert.equal(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-
-  const spy = {
-    signOutAndDestroyDevice: { count: 0, args: [] }
-  };
-  const client = fxa.internal.fxAccountsClient;
-  client.signOutAndDestroyDevice = function() {
-    spy.signOutAndDestroyDevice.count += 1;
-    spy.signOutAndDestroyDevice.args.push(arguments);
-    return Promise.resolve({});
-  };
-  await fxa.deleteDeviceRegistration(credentials.sessionToken, credentials.deviceId);
-
-  Assert.equal(spy.signOutAndDestroyDevice.count, 1);
-  Assert.equal(spy.signOutAndDestroyDevice.args[0].length, 2);
-  Assert.equal(spy.signOutAndDestroyDevice.args[0][0], credentials.sessionToken);
-  Assert.equal(spy.signOutAndDestroyDevice.args[0][1], credentials.deviceId);
-
-  data = await state.getUserAccountData();
-
-  Assert.ok(!data.deviceId);
-  Assert.ok(!data.deviceRegistrationVersion);
-});
-
 add_task(async function test_updateDeviceRegistration_with_device_session_conflict_error() {
   const deviceName = "foo";
   const deviceType = "bar";
 
   const credentials = getTestUser("baz");
   const fxa = new MockFxAccounts({ name: deviceName });
   await fxa.internal.setSignedInUser(credentials);
 
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -727,58 +727,16 @@ add_task(async function test_updateDevic
     do_throw("Expected to catch an exception");
   } catch (unexpectedError) {
     Assert.equal(unexpectedError.code, 500);
   }
 
   await promiseStopServer(server);
 });
 
-add_task(async function test_signOutAndDestroyDevice() {
-  const DEVICE_ID = "device id";
-  const ERROR_ID = "test that the client promise rejects";
-  let emptyMessage = "{}";
-
-  const server = httpd_setup({
-    "/account/device/destroy": function(request, response) {
-      const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
-
-      if (!body.id) {
-        response.setStatusLine(request.httpVersion, 400, "Invalid request");
-        response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
-        return;
-      }
-
-      if (body.id === ERROR_ID) {
-        response.setStatusLine(request.httpVersion, 500, "Alas");
-        response.bodyOutputStream.write("{}", 2);
-        return;
-      }
-
-      response.setStatusLine(request.httpVersion, 200, "OK");
-      response.bodyOutputStream.write("{}", 2);
-    },
-  });
-
-  const client = new FxAccountsClient(server.baseURI);
-  const result = await client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, DEVICE_ID);
-
-  Assert.ok(result);
-  Assert.equal(Object.keys(result).length, 0);
-
-  try {
-    await client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, ERROR_ID);
-    do_throw("Expected to catch an exception");
-  } catch (unexpectedError) {
-    Assert.equal(unexpectedError.code, 500);
-  }
-
-  await promiseStopServer(server);
-});
-
 add_task(async function test_getDeviceList() {
   let canReturnDevices;
 
   const server = httpd_setup({
     "/account/devices": function(request, response) {
       if (canReturnDevices) {
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.bodyOutputStream.write("[]", 2);
--- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
@@ -41,17 +41,17 @@ function createFxAccounts() {
   return new FxAccounts({
     _fxAccountsClient: {
       async registerDevice() {
         return { id: "deviceAAAAAA" };
       },
       async recoveryEmailStatus() {
         return { verified: true };
       },
-      async signOutAndDestroyDevice() {},
+      async signOut() {},
     },
     _getDeviceName() {
       return "mock device name";
     },
     observerPreloads: [],
     fxaPushService: {
       async registerPushEndpoint() {
         return {
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
@@ -194,21 +194,14 @@ var Authentication = {
    * Sign out of Firefox Accounts. It also clears out the device ID, if we find one.
    */
   async signOut() {
     if (await Authentication.isLoggedIn()) {
       let user = await Authentication.getSignedInUser();
       if (!user) {
         throw new Error("Failed to get signed in user!");
       }
+      let { sessionToken } = user;
       let fxc = new FxAccountsClient();
-      let { sessionToken, deviceId } = user;
-      if (deviceId) {
-        Logger.logInfo("Destroying device " + deviceId);
-        await fxAccounts.deleteDeviceRegistration(sessionToken, deviceId);
-        await fxAccounts.signOut(true);
-      } else {
-        Logger.logError("No device found.");
-        await fxc.signOut(sessionToken, { service: "sync" });
-      }
+      await fxc.signOut(sessionToken, { service: "sync" });
     }
   }
 };