summaryrefslogtreecommitdiffstats
path: root/services/sync/modules/browserid_identity.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/modules/browserid_identity.js')
-rw-r--r--services/sync/modules/browserid_identity.js145
1 files changed, 31 insertions, 114 deletions
diff --git a/services/sync/modules/browserid_identity.js b/services/sync/modules/browserid_identity.js
index db3821518..9709b9196 100644
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -4,7 +4,7 @@
"use strict";
-this.EXPORTED_SYMBOLS = ["BrowserIDManager", "AuthenticationError"];
+this.EXPORTED_SYMBOLS = ["BrowserIDManager"];
var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
@@ -45,7 +45,6 @@ Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
const OBSERVER_TOPICS = [
fxAccountsCommon.ONLOGIN_NOTIFICATION,
fxAccountsCommon.ONLOGOUT_NOTIFICATION,
- fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
];
const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
@@ -66,9 +65,8 @@ function deriveKeyBundle(kB) {
some other error object (which should do the right thing when toString() is
called on it)
*/
-function AuthenticationError(details, source) {
+function AuthenticationError(details) {
this.details = details;
- this.source = source;
}
AuthenticationError.prototype = {
@@ -106,6 +104,12 @@ this.BrowserIDManager.prototype = {
// we don't consider the lack of a keybundle as a failure state.
_shouldHaveSyncKeyBundle: false,
+ get readyToAuthenticate() {
+ // We are finished initializing when we *should* have a sync key bundle,
+ // although we might not actually have one due to auth failures etc.
+ return this._shouldHaveSyncKeyBundle;
+ },
+
get needsCustomization() {
try {
return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
@@ -114,34 +118,11 @@ this.BrowserIDManager.prototype = {
}
},
- hashedUID() {
- if (!this._token) {
- throw new Error("hashedUID: Don't have token");
- }
- return this._token.hashed_fxa_uid
- },
-
- deviceID() {
- return this._signedInUser && this._signedInUser.deviceId;
- },
-
initialize: function() {
for (let topic of OBSERVER_TOPICS) {
Services.obs.addObserver(this, topic, false);
}
- // and a background fetch of account data just so we can set this.account,
- // so we have a username available before we've actually done a login.
- // XXX - this is actually a hack just for tests and really shouldn't be
- // necessary. Also, you'd think it would be safe to allow this.account to
- // be set to null when there's no user logged in, but argue with the test
- // suite, not with me :)
- this._fxaService.getSignedInUser().then(accountData => {
- if (accountData) {
- this.account = accountData.email;
- }
- }).catch(err => {
- // As above, this is only for tests so it is safe to ignore.
- });
+ return this.initializeWithCurrentIdentity();
},
/**
@@ -149,7 +130,7 @@ this.BrowserIDManager.prototype = {
* the user is logged in, or is rejected if the login attempt has failed.
*/
ensureLoggedIn: function() {
- if (!this._shouldHaveSyncKeyBundle && this.whenReadyToAuthenticate) {
+ if (!this._shouldHaveSyncKeyBundle) {
// We are already in the process of logging in.
return this.whenReadyToAuthenticate.promise;
}
@@ -179,6 +160,7 @@ this.BrowserIDManager.prototype = {
}
this.resetCredentials();
this._signedInUser = null;
+ return Promise.resolve();
},
offerSyncOptions: function () {
@@ -202,7 +184,7 @@ this.BrowserIDManager.prototype = {
// Reset the world before we do anything async.
this.whenReadyToAuthenticate = Promise.defer();
- this.whenReadyToAuthenticate.promise.catch(err => {
+ this.whenReadyToAuthenticate.promise.then(null, (err) => {
this._log.error("Could not authenticate", err);
});
@@ -258,14 +240,14 @@ this.BrowserIDManager.prototype = {
Services.obs.notifyObservers(null, "weave:service:setup-complete", null);
Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
}
- }).catch(authErr => {
- // report what failed...
- this._log.error("Background fetch for key bundle failed", authErr);
+ }).then(null, err => {
this._shouldHaveSyncKeyBundle = true; // but we probably don't have one...
- this.whenReadyToAuthenticate.reject(authErr);
+ this.whenReadyToAuthenticate.reject(err);
+ // report what failed...
+ this._log.error("Background fetch for key bundle failed", err);
});
// and we are done - the fetch continues on in the background...
- }).catch(err => {
+ }).then(null, err => {
this._log.error("Processing logged in account", err);
});
},
@@ -301,8 +283,7 @@ this.BrowserIDManager.prototype = {
// reauth with the server - in that case we will also get here, but
// should have the same identity.
// initializeWithCurrentIdentity will throw and log if these constraints
- // aren't met (indirectly, via _updateSignedInUser()), so just go ahead
- // and do the init.
+ // aren't met, so just go ahead and do the init.
this.initializeWithCurrentIdentity(true);
break;
@@ -311,13 +292,6 @@ this.BrowserIDManager.prototype = {
// startOver will cause this instance to be thrown away, so there's
// nothing else to do.
break;
-
- case fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION:
- // throw away token and fetch a new one
- this.resetCredentials();
- this._ensureValidToken().catch(err =>
- this._log.error("Error while fetching a new token", err));
- break;
}
},
@@ -413,9 +387,6 @@ this.BrowserIDManager.prototype = {
resetCredentials: function() {
this.resetSyncKey();
this._token = null;
- // The cluster URL comes from the token, so resetting it to empty will
- // force Sync to not accidentally use a value from an earlier token.
- Weave.Service.clusterURL = null;
},
/**
@@ -503,12 +474,7 @@ this.BrowserIDManager.prototype = {
// If we still can't get keys it probably means the user authenticated
// without unlocking the MP or cleared the saved logins, so we've now
// lost them - the user will need to reauth before continuing.
- let result;
- if (this._canFetchKeys()) {
- result = STATUS_OK;
- } else {
- result = LOGIN_FAILED_LOGIN_REJECTED;
- }
+ let result = this._canFetchKeys() ? STATUS_OK : LOGIN_FAILED_LOGIN_REJECTED;
log.debug("unlockAndVerifyAuthState re-fetched credentials and is returning", result);
return result;
}
@@ -540,27 +506,14 @@ this.BrowserIDManager.prototype = {
return true;
},
- // Get our tokenServerURL - a private helper. Returns a string.
- get _tokenServerUrl() {
- // We used to support services.sync.tokenServerURI but this was a
- // pain-point for people using non-default servers as Sync may auto-reset
- // all services.sync prefs. So if that still exists, it wins.
- let url = Svc.Prefs.get("tokenServerURI"); // Svc.Prefs "root" is services.sync
- if (!url) {
- url = Services.prefs.getCharPref("identity.sync.tokenserver.uri");
- }
- while (url.endsWith("/")) { // trailing slashes cause problems...
- url = url.slice(0, -1);
- }
- return url;
- },
-
// Refresh the sync token for our user. Returns a promise that resolves
// with a token (which may be null in one sad edge-case), or rejects with an
// error.
_fetchTokenForUser: function() {
- // tokenServerURI is mis-named - convention is uri means nsISomething...
- let tokenServerURI = this._tokenServerUrl;
+ let tokenServerURI = Svc.Prefs.get("tokenServerURI");
+ if (tokenServerURI.endsWith("/")) { // trailing slashes cause problems...
+ tokenServerURI = tokenServerURI.slice(0, -1);
+ }
let log = this._log;
let client = this._tokenServerClient;
let fxa = this._fxaService;
@@ -589,7 +542,7 @@ this.BrowserIDManager.prototype = {
);
}
- let getToken = assertion => {
+ let getToken = (tokenServerURI, assertion) => {
log.debug("Getting a token");
let deferred = Promise.defer();
let cb = function (err, token) {
@@ -617,18 +570,7 @@ this.BrowserIDManager.prototype = {
return fxa.whenVerified(this._signedInUser)
.then(() => maybeFetchKeys())
.then(() => getAssertion())
- .then(assertion => getToken(assertion))
- .catch(err => {
- // If we get a 401 fetching the token it may be that our certificate
- // needs to be regenerated.
- if (!err.response || err.response.status !== 401) {
- return Promise.reject(err);
- }
- log.warn("Token server returned 401, refreshing certificate and retrying token fetch");
- return fxa.invalidateCertificate()
- .then(() => getAssertion())
- .then(assertion => getToken(assertion))
- })
+ .then(assertion => getToken(tokenServerURI, assertion))
.then(token => {
// TODO: Make it be only 80% of the duration, so refresh the token
// before it actually expires. This is to avoid sync storage errors
@@ -640,18 +582,15 @@ this.BrowserIDManager.prototype = {
}
return token;
})
- .catch(err => {
+ .then(null, err => {
// 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, "tokenserver");
+ err = new AuthenticationError(err);
// A hawkclient error.
} else if (err.code && err.code === 401) {
- err = new AuthenticationError(err, "hawkclient");
- // An FxAccounts.jsm error.
- } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
- err = new AuthenticationError(err, "fxaccounts");
+ err = new AuthenticationError(err);
}
// TODO: write tests to make sure that different auth error cases are handled here
@@ -673,6 +612,7 @@ this.BrowserIDManager.prototype = {
// that there is no authentication dance still under way.
this._shouldHaveSyncKeyBundle = true;
Weave.Status.login = this._authFailureReason;
+ Services.obs.notifyObservers(null, "weave:ui:login:error", null);
throw err;
});
},
@@ -684,19 +624,12 @@ this.BrowserIDManager.prototype = {
this._log.debug("_ensureValidToken already has one");
return Promise.resolve();
}
- const notifyStateChanged =
- () => Services.obs.notifyObservers(null, "weave:service:login:change", null);
// reset this._token as a safety net to reduce the possibility of us
// repeatedly attempting to use an invalid token if _fetchTokenForUser throws.
this._token = null;
return this._fetchTokenForUser().then(
token => {
this._token = token;
- notifyStateChanged();
- },
- error => {
- notifyStateChanged();
- throw error
}
);
},
@@ -719,16 +652,9 @@ this.BrowserIDManager.prototype = {
_getAuthenticationHeader: function(httpObject, method) {
let cb = Async.makeSpinningCallback();
this._ensureValidToken().then(cb, cb);
- // Note that in failure states we return null, causing the request to be
- // made without authorization headers, thereby presumably causing a 401,
- // which causes Sync to log out. If we throw, this may not happen as
- // expected.
try {
cb.wait();
} catch (ex) {
- if (Async.isShutdownException(ex)) {
- throw ex;
- }
this._log.error("Failed to fetch a token for authentication", ex);
return null;
}
@@ -764,17 +690,8 @@ this.BrowserIDManager.prototype = {
createClusterManager: function(service) {
return new BrowserIDClusterManager(service);
- },
+ }
- // Tell Sync what the login status should be if it saw a 401 fetching
- // info/collections as part of login verification (typically immediately
- // after login.)
- // In our case, it almost certainly means a transient error fetching a token
- // (and hitting this will cause us to logout, which will correctly handle an
- // authoritative login issue.)
- loginStatusFromVerification404() {
- return LOGIN_FAILED_NETWORK_ERROR;
- },
};
/* An implementation of the ClusterManager for this identity
@@ -820,7 +737,7 @@ BrowserIDClusterManager.prototype = {
// it's likely a 401 was received using the existing token - in which
// case we just discard the existing token and fetch a new one.
if (this.service.clusterURL) {
- log.debug("_findCluster has a pre-existing clusterURL, so discarding the current token");
+ log.debug("_findCluster found existing clusterURL, so discarding the current token");
this.identity._token = null;
}
return this.identity._ensureValidToken();