Bug 1044530 - Remove invalid session and key fetch tokens from account storage. r=markh
MozReview-Commit-ID: DOLlus0At8s
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -412,16 +412,20 @@ FxAccountsInternal.prototype = {
get localtimeOffsetMsec() {
return this.fxAccountsClient.localtimeOffsetMsec;
},
/**
* Ask the server whether the user's email has been verified
*/
checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) {
+ if (!sessionToken) {
+ return Promise.reject(new Error(
+ "checkEmailStatus called without a session token"));
+ }
return this.fxAccountsClient.recoveryEmailStatus(sessionToken, options);
},
/**
* Once the user's email is verified, we can request the keys
*/
fetchKeys: function fetchKeys(keyFetchToken) {
log.debug("fetchKeys: " + !!keyFetchToken);
@@ -570,27 +574,29 @@ FxAccountsInternal.prototype = {
// No signed-in user
return null;
}
if (!this.isUserEmailVerified(data)) {
// Signed-in user has not verified email
return null;
}
if (!data.sessionToken) {
- // can't get a signed certificate without a session token, but that
- // should be impossible - make log noise about it.
- log.error("getAssertion called without a session token!");
- return null;
+ // can't get a signed certificate without a session token. This
+ // can happen if we request an assertion after clearing an invalid
+ // session token from storage.
+ throw this._error(ERROR_AUTH_ERROR, "getAssertion called without a session token");
}
return this.getKeypairAndCertificate(currentState).then(
({keyPair, certificate}) => {
return this.getAssertionFromCert(data, keyPair, certificate, audience);
}
);
- }).then(result => currentState.resolve(result));
+ }).catch(err =>
+ this._handleTokenError(err)
+ ).then(result => currentState.resolve(result));
},
getDeviceId() {
return this.currentAccountState.getUserAccountData()
.then(data => {
if (data) {
if (data.isDeviceStale || !data.deviceId) {
// A previous device registration attempt failed or there is no
@@ -614,18 +620,23 @@ FxAccountsInternal.prototype = {
*/
resendVerificationEmail: function resendVerificationEmail() {
let currentState = this.currentAccountState;
return this.getSignedInUser().then(data => {
// If the caller is asking for verification to be re-sent, and there is
// no signed-in user to begin with, this is probably best regarded as an
// error.
if (data) {
+ if (!data.sessionToken) {
+ return Promise.reject(new Error(
+ "resendVerificationEmail called without a session token"));
+ }
this.pollEmailStatus(currentState, data.sessionToken, "start");
- return this.fxAccountsClient.resendVerificationEmail(data.sessionToken);
+ return this.fxAccountsClient.resendVerificationEmail(
+ data.sessionToken).catch(err => this._handleTokenError(err));
}
throw new Error("Cannot resend verification email; no signed-in user");
});
},
/*
* Reset state such that any previous flow is canceled.
*/
@@ -705,17 +716,20 @@ FxAccountsInternal.prototype = {
// 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.
- return this._signOutServer(sessionToken, deviceId);
+ if (sessionToken) {
+ return this._signOutServer(sessionToken, deviceId);
+ }
+ log.warn("Missing session token; skipping remote sign out");
}).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(() => {
// just for testing - notifications are cheap when no observers.
@@ -747,18 +761,17 @@ FxAccountsInternal.prototype = {
// 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 and session");
- return this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId, options)
- .then(() => this.currentAccountState.updateUserAccountData({ deviceId: null }));
+ return this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId, options);
}
log.debug("destroying session");
return this.fxAccountsClient.signOut(sessionToken, options);
},
/**
* Fetch encryption keys for the signed-in-user from the FxA API server.
@@ -805,17 +818,19 @@ FxAccountsInternal.prototype = {
currentState.whenKeysReadyDeferred.reject(err);
}
);
} else {
currentState.whenKeysReadyDeferred.reject('No keyFetchToken');
}
}
return currentState.whenKeysReadyDeferred.promise;
- }).then(result => currentState.resolve(result));
+ }).catch(err =>
+ this._handleTokenError(err)
+ ).then(result => currentState.resolve(result));
},
fetchAndUnwrapKeys: function(keyFetchToken) {
if (logPII) {
log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
}
let currentState = this.currentAccountState;
return Task.spawn(function* task() {
@@ -1110,28 +1125,35 @@ FxAccountsInternal.prototype = {
if (error && error.retryAfter) {
// If the server told us to back off, back off the requested amount.
timeoutMs = (error.retryAfter + 3) * 1000;
}
// The server will return 401 if a request parameter is erroneous or
// if the session token expired. Let's continue polling otherwise.
if (!error || !error.code || error.code != 401) {
this.pollEmailStatusAgain(currentState, sessionToken, timeoutMs);
+ } else {
+ let error = new Error("Verification status check failed");
+ this._rejectWhenVerified(currentState, error);
}
});
},
+ _rejectWhenVerified(currentState, error) {
+ currentState.whenVerifiedDeferred.reject(error);
+ delete currentState.whenVerifiedDeferred;
+ },
+
// Poll email status using truncated exponential back-off.
pollEmailStatusAgain: function (currentState, sessionToken, timeoutMs) {
let ageMs = Date.now() - this.pollStartDate;
if (ageMs >= this.POLL_SESSION) {
if (currentState.whenVerifiedDeferred) {
let error = new Error("User email verification timed out.");
- currentState.whenVerifiedDeferred.reject(error);
- delete currentState.whenVerifiedDeferred;
+ this._rejectWhenVerified(currentState, error);
}
log.debug("polling session exceeded, giving up");
return;
}
if (timeoutMs === undefined) {
let currentMinute = Math.ceil(ageMs / 60000);
timeoutMs = currentMinute <= 2 ? this.VERIFICATION_POLL_TIMEOUT_INITIAL
: this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
@@ -1380,17 +1402,18 @@ FxAccountsInternal.prototype = {
*/
_errorToErrorClass: function (aError) {
if (aError.errno) {
let error = SERVER_ERRNO_TO_ERROR[aError.errno];
return this._error(ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN, aError);
} else if (aError.message &&
(aError.message === "INVALID_PARAMETER" ||
aError.message === "NO_ACCOUNT" ||
- aError.message === "UNVERIFIED_ACCOUNT")) {
+ aError.message === "UNVERIFIED_ACCOUNT" ||
+ aError.message === "AUTH_ERROR")) {
return aError;
}
return this._error(ERROR_UNKNOWN, aError);
},
_error: function(aError, aDetails) {
log.error("FxA rejecting with error ${aError}, details: ${aDetails}", {aError, aDetails});
let reason = new Error(aError);
@@ -1453,16 +1476,21 @@ FxAccountsInternal.prototype = {
// 1. It makes remote requests to the auth server.
// 2. _getDeviceName does not work from xpcshell.
// 3. The B2G tests fail when attempting to import services-sync/util.js.
if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
return Promise.resolve();
}
} catch(ignore) {}
+ if (!signedInUser.sessionToken) {
+ return Promise.reject(new Error(
+ "_registerOrUpdateDevice called without a session token"));
+ }
+
return this.fxaPushService.registerPushEndpoint().then(subscription => {
const deviceName = this._getDeviceName();
let deviceOptions = {};
// if we were able to obtain a subscription
if (subscription && subscription.endpoint) {
deviceOptions.pushCallback = subscription.endpoint;
}
@@ -1499,18 +1527,21 @@ FxAccountsInternal.prototype = {
return this._recoverFromUnknownDevice();
}
if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) {
return this._recoverFromDeviceSessionConflict(error, sessionToken);
}
}
- return this._logErrorAndSetStaleDeviceFlag(error);
- }).catch(() => {});
+ // `_handleTokenError` re-throws the error.
+ return this._handleTokenError(error);
+ }).catch(error =>
+ this._logErrorAndSetStaleDeviceFlag(error)
+ ).catch(() => {});
},
_recoverFromUnknownDevice() {
// FxA did not recognise the device id. Handle it by clearing the device
// id on the account data. At next sync or next sign-in, registration is
// retried and should succeed.
log.warn("unknown device id, clearing the local device data");
return this.currentAccountState.updateUserAccountData({ deviceId: null })
@@ -1556,17 +1587,48 @@ FxAccountsInternal.prototype = {
log.error("device registration failed", error);
return this.currentAccountState.updateUserAccountData({
isDeviceStale: true
}).catch(secondError => {
log.error(
"failed to set stale device flag, device registration won't be retried",
secondError);
}).then(() => {});
- }
+ },
+
+ _handleTokenError(err) {
+ if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) {
+ throw err;
+ }
+ log.warn("recovering from invalid token error", err);
+ return this.accountStatus().then(exists => {
+ if (!exists) {
+ // Delete all local account data. Since the account no longer
+ // exists, we can skip the remote calls.
+ log.info("token invalidated because the account no longer exists");
+ return this.signOut(true);
+ }
+
+ // Delete all fields except those required for the user to
+ // reauthenticate.
+ log.info("clearing credentials to handle invalid token error");
+ let updateData = {};
+ let clearField = field => {
+ if (!FXA_PWDMGR_REAUTH_WHITELIST.has(field)) {
+ updateData[field] = null;
+ }
+ }
+ FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField);
+ FXA_PWDMGR_SECURE_FIELDS.forEach(clearField);
+ FXA_PWDMGR_MEMORY_FIELDS.forEach(clearField);
+
+ let currentState = this.currentAccountState;
+ return currentState.updateUserAccountData(updateData);
+ }).then(() => Promise.reject(err));
+ },
};
// A getter for the instance to export
XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
let a = new FxAccounts();
// XXX Bug 947061 - We need a strategy for resuming email verification after
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -234,16 +234,21 @@ exports.FXA_PWDMGR_PLAINTEXT_FIELDS = ne
// Fields we store in secure storage if it exists.
exports.FXA_PWDMGR_SECURE_FIELDS = new Set(
["kA", "kB", "keyFetchToken", "unwrapBKey", "assertion"]);
// Fields we keep in memory and don't persist anywhere.
exports.FXA_PWDMGR_MEMORY_FIELDS = new Set(
["cert", "keyPair"]);
+// A whitelist of fields that remain in storage when the user needs to
+// reauthenticate. All other fields will be removed.
+exports.FXA_PWDMGR_REAUTH_WHITELIST = new Set(
+ ["email", "uid", "profile", "deviceId", "isDeviceStale", "verified"]);
+
// The pseudo-host we use in the login manager
exports.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
// The realm we use in the login manager.
exports.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
// Error matching.
exports.SERVER_ERRNO_TO_ERROR = {};
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -548,16 +548,81 @@ add_test(function test_getKeys() {
do_check_eq(user.unwrapBKey, undefined);
run_next_test();
});
});
});
});
});
+add_task(function* test_getKeys_nonexistent_account() {
+ let fxa = new MockFxAccounts();
+ let bismarck = getTestUser("bismarck");
+
+ let client = fxa.internal.fxAccountsClient;
+ client.accountStatus = () => Promise.resolve(false);
+ client.accountKeys = () => {
+ return Promise.reject({
+ code: 401,
+ errno: ERRNO_INVALID_AUTH_TOKEN,
+ });
+ };
+
+ yield fxa.setSignedInUser(bismarck);
+
+ let promiseLogout = new Promise(resolve => {
+ makeObserver(ONLOGOUT_NOTIFICATION, function() {
+ log.debug("test_getKeys_nonexistent_account observed logout");
+ resolve();
+ });
+ });
+
+ try {
+ yield fxa.internal.getKeys();
+ do_check_true(false);
+ } catch (err) {
+ do_check_eq(err.code, 401);
+ do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
+ }
+
+ yield promiseLogout;
+
+ let user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user, null);
+});
+
+// getKeys with invalid keyFetchToken should delete keyFetchToken from storage
+add_task(function* test_getKeys_invalid_token() {
+ let fxa = new MockFxAccounts();
+ let yusuf = getTestUser("yusuf");
+
+ let client = fxa.internal.fxAccountsClient;
+ client.accountStatus = () => Promise.resolve(true);
+ client.accountKeys = () => {
+ return Promise.reject({
+ code: 401,
+ errno: ERRNO_INVALID_AUTH_TOKEN,
+ });
+ };
+
+ yield fxa.setSignedInUser(yusuf);
+
+ try {
+ yield fxa.internal.getKeys();
+ do_check_true(false);
+ } catch (err) {
+ do_check_eq(err.code, 401);
+ do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
+ }
+
+ let user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user.email, yusuf.email);
+ do_check_eq(user.keyFetchToken, null);
+});
+
// fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
add_test(function test_fetchAndUnwrapKeys_no_token() {
let fxa = new MockFxAccounts();
let user = getTestUser("lettuce.protheroe");
delete user.keyFetchToken
makeObserver(ONLOGOUT_NOTIFICATION, function() {
log.debug("test_fetchAndUnwrapKeys_no_token observed logout");
@@ -612,16 +677,49 @@ add_test(function test_overlapping_signi
log.debug("Bob verifying his email ...");
fxa.internal.fxAccountsClient._verified = true;
});
});
});
});
});
+add_task(function* test_getAssertion_invalid_token() {
+ let fxa = new MockFxAccounts();
+
+ let client = fxa.internal.fxAccountsClient;
+ client.accountStatus = () => Promise.resolve(true);
+
+ let creds = {
+ sessionToken: "sessionToken",
+ kA: expandHex("11"),
+ kB: expandHex("66"),
+ verified: true,
+ email: "sonia@example.com",
+ };
+ yield fxa.setSignedInUser(creds);
+
+ try {
+ let promiseAssertion = fxa.getAssertion("audience.example.com");
+ fxa.internal._d_signCertificate.reject({
+ code: 401,
+ errno: ERRNO_INVALID_AUTH_TOKEN,
+ });
+ yield promiseAssertion;
+ do_check_true(false, "getAssertion should reject invalid session token");
+ } catch (err) {
+ do_check_eq(err.code, 401);
+ do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
+ }
+
+ let user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user.email, creds.email);
+ do_check_eq(user.sessionToken, null);
+});
+
add_task(function* test_getAssertion() {
let fxa = new MockFxAccounts();
do_check_throws(function* () {
yield fxa.getAssertion("nonaudience");
});
let creds = {
@@ -778,16 +876,49 @@ add_test(function test_accountStatus() {
}
)
}
);
}
);
});
+add_task(function* test_resend_email_invalid_token() {
+ let fxa = new MockFxAccounts();
+ let sophia = getTestUser("sophia");
+ do_check_neq(sophia.sessionToken, null);
+
+ let client = fxa.internal.fxAccountsClient;
+ client.resendVerificationEmail = () => {
+ return Promise.reject({
+ code: 401,
+ errno: ERRNO_INVALID_AUTH_TOKEN,
+ });
+ };
+ client.accountStatus = () => Promise.resolve(true);
+
+ yield fxa.setSignedInUser(sophia);
+ let user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user.email, sophia.email);
+ do_check_eq(user.verified, false);
+ log.debug("Sophia wants verification email resent");
+
+ try {
+ yield fxa.resendVerificationEmail();
+ do_check_true(false, "resendVerificationEmail should reject invalid session token");
+ } catch (err) {
+ do_check_eq(err.code, 401);
+ do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
+ }
+
+ user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user.email, sophia.email);
+ do_check_eq(user.sessionToken, null);
+});
+
add_test(function test_resend_email() {
let fxa = new MockFxAccounts();
let alice = getTestUser("alice");
let initialState = fxa.internal.currentAccountState;
// Alice is the user signing in; her email is unverified.
fxa.setSignedInUser(alice).then(() => {
@@ -913,32 +1044,37 @@ add_task(function* test_sign_out_without
});
});
yield fxa.signOut();
yield promise;
});
-add_test(function test_sign_out_with_remote_error() {
+add_task(function* test_sign_out_with_remote_error() {
let fxa = new MockFxAccounts();
let client = fxa.internal.fxAccountsClient;
let remoteSignOutCalled = false;
// Force remote sign out to trigger an error
- client.signOut = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
- makeObserver(ONLOGOUT_NOTIFICATION, function() {
- log.debug("test_sign_out_with_remote_error observed onlogout");
- // user should be undefined after sign out
- fxa.internal.getUserAccountData().then(user => {
- do_check_eq(user, null);
- do_check_true(remoteSignOutCalled);
- run_next_test();
+ client.signOutAndDestroyDevice = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
+ let promiseLogout = new Promise(resolve => {
+ makeObserver(ONLOGOUT_NOTIFICATION, function() {
+ log.debug("test_sign_out_with_remote_error observed onlogout");
+ resolve();
});
});
- fxa.signOut();
+
+ let jane = getTestUser("jane");
+ yield fxa.setSignedInUser(jane);
+ yield fxa.signOut();
+ yield promiseLogout;
+
+ let user = yield fxa.internal.getUserAccountData();
+ do_check_eq(user, null);
+ do_check_true(remoteSignOutCalled);
});
add_test(function test_getOAuthToken() {
let fxa = new MockFxAccounts();
let alice = getTestUser("alice");
alice.verified = true;
let getTokenFromAssertionCalled = false;
--- a/services/sync/modules-testing/utils.js
+++ b/services/sync/modules-testing/utils.js
@@ -6,16 +6,17 @@
this.EXPORTED_SYMBOLS = [
"btoa", // It comes from a module import.
"encryptPayload",
"isConfiguredWithLegacyIdentity",
"ensureLegacyIdentityManager",
"setBasicCredentials",
"makeIdentityConfig",
+ "makeFxAccountsInternalMock",
"configureFxAccountIdentity",
"configureIdentity",
"SyncTestingInfrastructure",
"waitForZeroTimer",
"Promise", // from a module import
"add_identity_test",
"MockFxaStorageManager",
"AccountState", // from a module import
@@ -174,43 +175,45 @@ this.makeIdentityConfig = function(overr
if (overrides.fxaccount) {
// TODO: allow just some attributes to be specified
result.fxaccount = overrides.fxaccount;
}
}
return result;
}
-// Configure an instance of an FxAccount identity provider with the specified
-// config (or the default config if not specified).
-this.configureFxAccountIdentity = function(authService,
- config = makeIdentityConfig()) {
- // until we get better test infrastructure for bid_identity, we set the
- // signedin user's "email" to the username, simply as many tests rely on this.
- config.fxaccount.user.email = config.username;
-
- let fxa;
- let MockInternal = {
+this.makeFxAccountsInternalMock = function(config) {
+ return {
newAccountState(credentials) {
// We only expect this to be called with null indicating the (mock)
// storage should be read.
if (credentials) {
throw new Error("Not expecting to have credentials passed");
}
let storageManager = new MockFxaStorageManager();
storageManager.initialize(config.fxaccount.user);
let accountState = new AccountState(storageManager);
return accountState;
},
_getAssertion(audience) {
return Promise.resolve("assertion");
},
+ };
+};
- };
- fxa = new FxAccounts(MockInternal);
+// Configure an instance of an FxAccount identity provider with the specified
+// config (or the default config if not specified).
+this.configureFxAccountIdentity = function(authService,
+ config = makeIdentityConfig(),
+ fxaInternal = makeFxAccountsInternalMock(config)) {
+ // until we get better test infrastructure for bid_identity, we set the
+ // signedin user's "email" to the username, simply as many tests rely on this.
+ config.fxaccount.user.email = config.username;
+
+ let fxa = new FxAccounts(fxaInternal);
let MockFxAccountsClient = function() {
FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype,
accountStatus() {
return Promise.resolve(true);
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -240,32 +240,21 @@ this.BrowserIDManager.prototype = {
this._log.info("Background fetch for key bundle done");
Weave.Status.login = LOGIN_SUCCEEDED;
if (isInitialSync) {
this._log.info("Doing initial sync actions");
Svc.Prefs.set("firstSync", "resetClient");
Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
}
- }).catch(err => {
- let authErr = err; // note that we must reject with this error and not a
- // subsequent one
+ }).catch(authErr => {
// report what failed...
this._log.error("Background fetch for key bundle failed", authErr);
- // check if the account still exists
- this._fxaService.accountStatus().then(exists => {
- if (!exists) {
- return fxAccounts.signOut(true);
- }
- }).catch(err => {
- this._log.error("Error while trying to determine FXA existence", err);
- }).then(() => {
- this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
- this.whenReadyToAuthenticate.reject(authErr)
- });
+ this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
+ this.whenReadyToAuthenticate.reject(authErr);
});
// and we are done - the fetch continues on in the background...
}).catch(err => {
this._log.error("Processing logged in account", err);
});
},
_updateSignedInUser: function(userData) {
@@ -621,16 +610,19 @@ this.BrowserIDManager.prototype = {
// TODO: unify these errors - we need to handle errors thrown by
// both tokenserverclient and hawkclient.
// A tokenserver error thrown based on a bad response.
if (err.response && err.response.status === 401) {
err = new AuthenticationError(err);
// A hawkclient error.
} else if (err.code && err.code === 401) {
err = new AuthenticationError(err);
+ // An FxAccounts.jsm error.
+ } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
+ err = new AuthenticationError(err);
}
// TODO: write tests to make sure that different auth error cases are handled here
// properly: auth error getting assertion, auth error getting token (invalid generation
// and client-state error)
if (err instanceof AuthenticationError) {
this._log.error("Authentication error in _fetchTokenForUser", err);
// set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -84,52 +84,61 @@ add_task(function* test_initialializeWit
do_check_true(browseridManager.hasValidToken());
do_check_eq(browseridManager.account, identityConfig.fxaccount.user.email);
}
);
add_task(function* test_initialializeWithAuthErrorAndDeletedAccount() {
_("Verify sync unpair after initializeWithCurrentIdentity with auth error + account deleted");
+ var identityConfig = makeIdentityConfig();
+ var browseridManager = new BrowserIDManager();
+
+ // Use the real `_getAssertion` method that calls
+ // `mockFxAClient.signCertificate`.
+ let fxaInternal = makeFxAccountsInternalMock(identityConfig);
+ delete fxaInternal._getAssertion;
+
+ configureFxAccountIdentity(browseridManager, identityConfig, fxaInternal);
browseridManager._fxaService.internal.initialize();
- let fetchTokenForUserCalled = false;
+ let signCertificateCalled = false;
let accountStatusCalled = false;
let MockFxAccountsClient = function() {
FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
__proto__: FxAccountsClient.prototype,
+ signCertificate() {
+ signCertificateCalled = true;
+ return Promise.reject({
+ code: 401,
+ errno: ERRNO_INVALID_AUTH_TOKEN,
+ });
+ },
accountStatus() {
accountStatusCalled = true;
return Promise.resolve(false);
}
};
let mockFxAClient = new MockFxAccountsClient();
browseridManager._fxaService.internal._fxAccountsClient = mockFxAClient;
- let oldFetchTokenForUser = browseridManager._fetchTokenForUser;
- browseridManager._fetchTokenForUser = function() {
- fetchTokenForUserCalled = true;
- return Promise.reject(false);
- }
-
yield browseridManager.initializeWithCurrentIdentity();
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
"should reject due to an auth error");
- do_check_true(fetchTokenForUserCalled);
+ do_check_true(signCertificateCalled);
do_check_true(accountStatusCalled);
do_check_false(browseridManager.account);
do_check_false(browseridManager._token);
do_check_false(browseridManager.hasValidToken());
do_check_false(browseridManager.account);
- browseridManager._fetchTokenForUser = oldFetchTokenForUser;
});
add_task(function* test_initialializeWithNoKeys() {
_("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken");
let identityConfig = makeIdentityConfig();
delete identityConfig.fxaccount.user.kA;
delete identityConfig.fxaccount.user.kB;
// there's no keyFetchToken by default, so the initialize should fail.