diff options
Diffstat (limited to 'services/fxaccounts/FxAccounts.sys.mjs')
-rw-r--r-- | services/fxaccounts/FxAccounts.sys.mjs | 52 |
1 files changed, 43 insertions, 9 deletions
diff --git a/services/fxaccounts/FxAccounts.sys.mjs b/services/fxaccounts/FxAccounts.sys.mjs index 18169c6b2d..790a6195f8 100644 --- a/services/fxaccounts/FxAccounts.sys.mjs +++ b/services/fxaccounts/FxAccounts.sys.mjs @@ -65,6 +65,8 @@ XPCOMUtils.defineLazyPreferenceGetter( true ); +export const ERROR_INVALID_ACCOUNT_STATE = "ERROR_INVALID_ACCOUNT_STATE"; + // An AccountState object holds all state related to one specific account. // It is considered "private" to the FxAccounts modules. // Only one AccountState is ever "current" in the FxAccountsInternal object - @@ -170,7 +172,7 @@ AccountState.prototype = { delete updatedFields.uid; } if (!this.isCurrent) { - return Promise.reject(new Error("Another user has signed in")); + return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE)); } return this.storageManager.updateAccountData(updatedFields); }, @@ -179,11 +181,11 @@ AccountState.prototype = { if (!this.isCurrent) { log.info( "An accountState promise was resolved, but was actually rejected" + - " due to a different user being signed in. Originally resolved" + - " with", + " due to the account state changing. This can happen if a new account signed in, or" + + " the account was signed out. Originally resolved with, ", result ); - return Promise.reject(new Error("A different user signed in")); + return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE)); } return Promise.resolve(result); }, @@ -195,12 +197,13 @@ AccountState.prototype = { // problems. if (!this.isCurrent) { log.info( - "An accountState promise was rejected, but we are ignoring that " + - "reason and rejecting it due to a different user being signed in. " + - "Originally rejected with", + "An accountState promise was rejected, but we are ignoring that" + + " reason and rejecting it due to the account state changing. This can happen if" + + " a different account signed in or the account was signed out" + + " originally resolved with, ", error ); - return Promise.reject(new Error("A different user signed in")); + return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE)); } return Promise.reject(error); }, @@ -215,7 +218,7 @@ AccountState.prototype = { // A preamble for the cache helpers... _cachePreamble() { if (!this.isCurrent) { - throw new Error("Another user has signed in"); + throw new Error(ERROR_INVALID_ACCOUNT_STATE); } }, @@ -466,6 +469,37 @@ export class FxAccounts { } } + /** Gets both the OAuth token and the users scoped keys for that token + * and verifies that both operations were done for the same user, + * preventing race conditions where a caller + * can get the key for one user, and the id of another if the user + * is rapidly switching between accounts + * + * @param options + * { + * scope: string the oauth scope being requested. This must + * be a scope with an associated key, otherwise an error + * will be thrown that the key is not available. + * ttl: (number) OAuth token TTL in seconds + * } + * + * @return Promise.<Object | Error> + * The promise resolve to both the access token being requested, and the scoped key + * { + * token: (string) access token + * key: (object) the scoped key object + * } + * The promise can reject, with one of the errors `getOAuthToken`, `FxAccountKeys.getKeyForScope`, or + * error if the user changed in-between operations + */ + getOAuthTokenAndKey(options = {}) { + return this._withCurrentAccountState(async () => { + const key = await this.keys.getKeyForScope(options.scope); + const token = await this.getOAuthToken(options); + return { token, key }; + }); + } + /** * Remove an OAuth token from the token cache. Callers should call this * after they determine a token is invalid, so a new token will be fetched |