зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1869641: Adds oauth handling for fxa behind pref. r=skhamis
- Adds new pref disabled by default that enables the oauth flow - When the pref is enabled, the URLs returned by FxA are oauth urls - Adds listening to oauth login web channel message from FxA Differential Revision: https://phabricator.services.mozilla.com/D196314
This commit is contained in:
Родитель
9210241675
Коммит
8e1906638e
|
@ -1850,6 +1850,9 @@ pref("identity.fxaccounts.remote.root", "https://accounts.firefox.com/");
|
|||
// The value of the context query parameter passed in fxa requests.
|
||||
pref("identity.fxaccounts.contextParam", "fx_desktop_v3");
|
||||
|
||||
// Whether to use the oauth flow for desktop or not
|
||||
pref("identity.fxaccounts.oauth.enabled", false);
|
||||
|
||||
// The remote URL of the FxA Profile Server
|
||||
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
|
||||
|
||||
|
|
|
@ -831,7 +831,7 @@ FxAccountsInternal.prototype = {
|
|||
_oauth: null,
|
||||
get oauth() {
|
||||
if (!this._oauth) {
|
||||
this._oauth = new lazy.FxAccountsOAuth();
|
||||
this._oauth = new lazy.FxAccountsOAuth(this.fxAccountsClient);
|
||||
}
|
||||
return this._oauth;
|
||||
},
|
||||
|
@ -844,6 +844,18 @@ FxAccountsInternal.prototype = {
|
|||
return this._telemetry;
|
||||
},
|
||||
|
||||
beginOAuthFlow(scopes) {
|
||||
return this.oauth.beginOAuthFlow(scopes);
|
||||
},
|
||||
|
||||
completeOAuthFlow(sessionToken, code, state) {
|
||||
return this.oauth.completeOAuthFlow(sessionToken, code, state);
|
||||
},
|
||||
|
||||
setScopedKeys(scopedKeys) {
|
||||
return this.keys.setScopedKeys(scopedKeys);
|
||||
},
|
||||
|
||||
// A hook-point for tests who may want a mocked AccountState or mocked storage.
|
||||
newAccountState(credentials) {
|
||||
let storage = new FxAccountsStorageManager();
|
||||
|
@ -1031,7 +1043,10 @@ FxAccountsInternal.prototype = {
|
|||
return this.startPollEmailStatus(state, data.sessionToken, "push");
|
||||
},
|
||||
|
||||
_destroyOAuthToken(tokenData) {
|
||||
/** Destroyes an OAuth Token by sending a request to the FxA server
|
||||
* @param { Object } tokenData: The token's data, with `tokenData.token` being the token itself
|
||||
**/
|
||||
destroyOAuthToken(tokenData) {
|
||||
return this.fxAccountsClient.oauthDestroy(
|
||||
FX_OAUTH_CLIENT_ID,
|
||||
tokenData.token
|
||||
|
@ -1045,7 +1060,7 @@ FxAccountsInternal.prototype = {
|
|||
// let's just destroy them all in parallel...
|
||||
let promises = [];
|
||||
for (let tokenInfo of Object.values(tokenInfos)) {
|
||||
promises.push(this._destroyOAuthToken(tokenInfo));
|
||||
promises.push(this.destroyOAuthToken(tokenInfo));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
},
|
||||
|
@ -1419,13 +1434,25 @@ FxAccountsInternal.prototype = {
|
|||
let existing = currentState.removeCachedToken(options.token);
|
||||
if (existing) {
|
||||
// background destroy.
|
||||
this._destroyOAuthToken(existing).catch(err => {
|
||||
this.destroyOAuthToken(existing).catch(err => {
|
||||
log.warn("FxA failed to revoke a cached token", err);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Sets the user to be verified in the account state,
|
||||
* This prevents any polling for the user's verification state from the FxA server
|
||||
**/
|
||||
setUserVerified() {
|
||||
return this.withCurrentAccountState(async currentState => {
|
||||
const userData = await currentState.getUserAccountData();
|
||||
if (!userData.verified) {
|
||||
await currentState.updateAccountData({ verified: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async _getVerifiedAccountOrReject() {
|
||||
let data = await this.currentAccountState.getUserAccountData();
|
||||
if (!data) {
|
||||
|
|
|
@ -284,21 +284,27 @@ FxAccountsClient.prototype = {
|
|||
},
|
||||
/**
|
||||
* Exchanges an OAuth authorization code with a refresh token, access tokens and an optional JWE representing scoped keys
|
||||
* Takes in the sessionToken to tie the device record associated with the session, with the device record associated with the refreshToken
|
||||
*
|
||||
* @param string sessionTokenHex: The session token encoded in hex
|
||||
* @param String code: OAuth authorization code
|
||||
* @param String verifier: OAuth PKCE verifier
|
||||
* @param String clientId: OAuth client ID
|
||||
*
|
||||
* @returns { Object } object containing `refresh_token`, `access_token` and `keys_jwe`
|
||||
**/
|
||||
async oauthToken(code, verifier, clientId) {
|
||||
async oauthToken(sessionTokenHex, code, verifier, clientId) {
|
||||
const credentials = await deriveHawkCredentials(
|
||||
sessionTokenHex,
|
||||
"sessionToken"
|
||||
);
|
||||
const body = {
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
client_id: clientId,
|
||||
code_verifier: verifier,
|
||||
};
|
||||
return this._request("/oauth/token", "POST", null, body);
|
||||
return this._request("/oauth/token", "POST", credentials, body);
|
||||
},
|
||||
/**
|
||||
* Destroy an OAuth access token or refresh token.
|
||||
|
|
|
@ -115,6 +115,7 @@ export let COMMAND_PAIR_COMPLETE = "fxaccounts:pair_complete";
|
|||
export let COMMAND_PROFILE_CHANGE = "profile:change";
|
||||
export let COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
|
||||
export let COMMAND_LOGIN = "fxaccounts:login";
|
||||
export let COMMAND_OAUTH = "fxaccounts:oauth_login";
|
||||
export let COMMAND_LOGOUT = "fxaccounts:logout";
|
||||
export let COMMAND_DELETE = "fxaccounts:delete";
|
||||
export let COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
import { RESTRequest } from "resource://services-common/rest.sys.mjs";
|
||||
|
||||
import { log } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
|
||||
import {
|
||||
log,
|
||||
SCOPE_OLD_SYNC,
|
||||
SCOPE_PROFILE,
|
||||
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
@ -51,25 +55,33 @@ const SYNC_PARAM = "sync";
|
|||
|
||||
export var FxAccountsConfig = {
|
||||
async promiseEmailURI(email, entrypoint, extraParams = {}) {
|
||||
const authParams = await this._getAuthParam();
|
||||
return this._buildURL("", {
|
||||
extraParams: { entrypoint, email, service: SYNC_PARAM, ...extraParams },
|
||||
extraParams: {
|
||||
entrypoint,
|
||||
email,
|
||||
...authParams,
|
||||
...extraParams,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async promiseConnectAccountURI(entrypoint, extraParams = {}) {
|
||||
const authParams = await this._getAuthParams();
|
||||
return this._buildURL("", {
|
||||
extraParams: {
|
||||
entrypoint,
|
||||
action: "email",
|
||||
service: SYNC_PARAM,
|
||||
...authParams,
|
||||
...extraParams,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async promiseForceSigninURI(entrypoint, extraParams = {}) {
|
||||
const authParams = await this._getAuthParams();
|
||||
return this._buildURL("force_auth", {
|
||||
extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams },
|
||||
extraParams: { entrypoint, ...authParams, ...extraParams },
|
||||
addAccountIdentifiers: true,
|
||||
});
|
||||
},
|
||||
|
@ -330,4 +342,19 @@ export var FxAccountsConfig = {
|
|||
getSignedInUser() {
|
||||
return lazy.fxAccounts.getSignedInUser();
|
||||
},
|
||||
|
||||
_isOAuthFlow() {
|
||||
return Services.prefs.getBoolPref(
|
||||
"identity.fxaccounts.oauth.enabled",
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
async _getAuthParams() {
|
||||
if (this._isOAuthFlow()) {
|
||||
const scopes = [SCOPE_OLD_SYNC, SCOPE_PROFILE];
|
||||
return lazy.fxAccounts._internal.beginOAuthFlow(scopes);
|
||||
}
|
||||
return { service: SYNC_PARAM };
|
||||
},
|
||||
};
|
||||
|
|
|
@ -215,6 +215,25 @@ export class FxAccountsKeys {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set externally derived scoped keys in internal storage
|
||||
* @param { Object } scopedKeys: The scoped keys object derived by the oauth flow
|
||||
*
|
||||
* @return { Promise }: A promise that resolves if the keys were successfully stored,
|
||||
* or rejects if we failed to persist the keys, or if the user is not signed in already
|
||||
*/
|
||||
async setScopedKeys(scopedKeys) {
|
||||
return this._fxai.withCurrentAccountState(async currentState => {
|
||||
const userData = await currentState.getUserAccountData();
|
||||
if (!userData) {
|
||||
throw new Error("Cannot persist keys, no user signed in");
|
||||
}
|
||||
await currentState.updateUserAccountData({
|
||||
scopedKeys,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Key storage migration or fetching logic.
|
||||
*
|
||||
|
|
|
@ -168,6 +168,7 @@ export class FxAccountsOAuth {
|
|||
}
|
||||
|
||||
/** Completes an OAuth flow and invalidates any other ongoing flows
|
||||
* @param { string } sessionTokenHex: The session token encoded in hexadecimal
|
||||
* @param { string } code: OAuth authorization code provided by running an OAuth flow
|
||||
* @param { string } state: The state first provided by `beginOAuthFlow`, then roundtripped through the server
|
||||
*
|
||||
|
@ -177,14 +178,19 @@ export class FxAccountsOAuth {
|
|||
* - 'refreshToken': The refresh token provided by the server
|
||||
* - 'accessToken': The access token provided by the server
|
||||
* */
|
||||
async completeOAuthFlow(code, state) {
|
||||
async completeOAuthFlow(sessionTokenHex, code, state) {
|
||||
const flow = this.getFlow(state);
|
||||
if (!flow) {
|
||||
throw new Error(ERROR_INVALID_STATE);
|
||||
}
|
||||
const { key, verifier, requestedScopes } = flow;
|
||||
const { keys_jwe, refresh_token, access_token, scope } =
|
||||
await this.#fxaClient.oauthToken(code, verifier, FX_OAUTH_CLIENT_ID);
|
||||
await this.#fxaClient.oauthToken(
|
||||
sessionTokenHex,
|
||||
code,
|
||||
verifier,
|
||||
FX_OAUTH_CLIENT_ID
|
||||
);
|
||||
if (
|
||||
requestedScopes.includes(SCOPE_OLD_SYNC) &&
|
||||
!scope.includes(SCOPE_OLD_SYNC)
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
COMMAND_PROFILE_CHANGE,
|
||||
COMMAND_LOGIN,
|
||||
COMMAND_LOGOUT,
|
||||
COMMAND_OAUTH,
|
||||
COMMAND_DELETE,
|
||||
COMMAND_CAN_LINK_ACCOUNT,
|
||||
COMMAND_SYNC_PREFERENCES,
|
||||
|
@ -84,6 +85,19 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
// older versions of Firefox.
|
||||
const EXTRA_ENGINES = ["addresses", "creditcards"];
|
||||
|
||||
// These engines will be displayed to the user to pick which they would like to
|
||||
// use
|
||||
const CHOOSE_WHAT_TO_SYNC = [
|
||||
"addons",
|
||||
"addresses",
|
||||
"bookmarks",
|
||||
"creditcards",
|
||||
"history",
|
||||
"passwords",
|
||||
"preferences",
|
||||
"tabs",
|
||||
];
|
||||
|
||||
/**
|
||||
* A helper function that extracts the message and stack from an error object.
|
||||
* Returns a `{ message, stack }` tuple. `stack` will be null if the error
|
||||
|
@ -220,6 +234,11 @@ FxAccountsWebChannel.prototype = {
|
|||
.login(data)
|
||||
.catch(error => this._sendError(error, message, sendingContext));
|
||||
break;
|
||||
case COMMAND_OAUTH:
|
||||
this._helpers
|
||||
.oauthLogin(data)
|
||||
.catch(error => this._sendError(error, message, sendingContext));
|
||||
break;
|
||||
case COMMAND_LOGOUT:
|
||||
case COMMAND_DELETE:
|
||||
this._helpers
|
||||
|
@ -408,6 +427,37 @@ FxAccountsWebChannelHelpers.prototype = {
|
|||
);
|
||||
},
|
||||
|
||||
async _initializeSync() {
|
||||
// A sync-specific hack - we want to ensure sync has been initialized
|
||||
// before we set the signed-in user.
|
||||
// XXX - probably not true any more, especially now we have observerPreloads
|
||||
// in FxAccounts.jsm?
|
||||
let xps =
|
||||
this._weaveXPCOM ||
|
||||
Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
await xps.whenLoaded();
|
||||
return xps;
|
||||
},
|
||||
|
||||
_setEnabledEngines(offeredEngines, declinedEngines) {
|
||||
if (offeredEngines && declinedEngines) {
|
||||
EXTRA_ENGINES.forEach(engine => {
|
||||
if (
|
||||
offeredEngines.includes(engine) &&
|
||||
!declinedEngines.includes(engine)
|
||||
) {
|
||||
// These extra engines are disabled by default.
|
||||
Services.prefs.setBoolPref(`services.sync.engine.${engine}`, true);
|
||||
}
|
||||
});
|
||||
log.debug("Received declined engines", declinedEngines);
|
||||
lazy.Weave.Service.engineManager.setDeclined(declinedEngines);
|
||||
declinedEngines.forEach(engine => {
|
||||
Services.prefs.setBoolPref(`services.sync.engine.${engine}`, false);
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* stores sync login info it in the fxaccounts service
|
||||
*
|
||||
|
@ -437,46 +487,50 @@ FxAccountsWebChannelHelpers.prototype = {
|
|||
"webchannel"
|
||||
);
|
||||
|
||||
// A sync-specific hack - we want to ensure sync has been initialized
|
||||
// before we set the signed-in user.
|
||||
// XXX - probably not true any more, especially now we have observerPreloads
|
||||
// in FxAccounts.jsm?
|
||||
let xps =
|
||||
this._weaveXPCOM ||
|
||||
Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
await xps.whenLoaded();
|
||||
const xps = await this._initializeSync();
|
||||
await this._fxAccounts._internal.setSignedInUser(accountData);
|
||||
|
||||
if (requestedServices) {
|
||||
// User has enabled Sync.
|
||||
if (requestedServices.sync) {
|
||||
const { offeredEngines, declinedEngines } = requestedServices.sync;
|
||||
if (offeredEngines && declinedEngines) {
|
||||
EXTRA_ENGINES.forEach(engine => {
|
||||
if (
|
||||
offeredEngines.includes(engine) &&
|
||||
!declinedEngines.includes(engine)
|
||||
) {
|
||||
// These extra engines are disabled by default.
|
||||
Services.prefs.setBoolPref(
|
||||
`services.sync.engine.${engine}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
log.debug("Received declined engines", declinedEngines);
|
||||
lazy.Weave.Service.engineManager.setDeclined(declinedEngines);
|
||||
declinedEngines.forEach(engine => {
|
||||
Services.prefs.setBoolPref(`services.sync.engine.${engine}`, false);
|
||||
});
|
||||
}
|
||||
this._setEnabledEngines(offeredEngines, declinedEngines);
|
||||
log.debug("Webchannel is enabling sync");
|
||||
await xps.Weave.Service.configure();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Logins in to sync by completing an OAuth flow
|
||||
* @param { Object } oauthData: The oauth code and state as returned by the server */
|
||||
async oauthLogin(oauthData) {
|
||||
log.debug("Webchannel is completing the oauth flow");
|
||||
const xps = await this._initializeSync();
|
||||
const { code, state, declinedSyncEngines, offeredSyncEngines } = oauthData;
|
||||
const { sessionToken } =
|
||||
await this._fxAccounts._internal.getUserAccountData(["sessionToken"]);
|
||||
// First we finish the ongoing oauth flow
|
||||
const { scopedKeys, refreshToken } =
|
||||
await this._fxAccounts._internal.completeOAuthFlow(
|
||||
sessionToken,
|
||||
code,
|
||||
state
|
||||
);
|
||||
|
||||
// We don't currently use the refresh token in Firefox Desktop, lets be good citizens and revoke it.
|
||||
await this._fxAccounts._internal.destroyOAuthToken({ token: refreshToken });
|
||||
|
||||
// Then, we persist the sync keys
|
||||
await this._fxAccounts._internal.setScopedKeys(scopedKeys);
|
||||
|
||||
// Now that we have the scoped keys, we set our status to verified
|
||||
await this._fxAccounts._internal.setUserVerified();
|
||||
this._setEnabledEngines(offeredSyncEngines, declinedSyncEngines);
|
||||
log.debug("Webchannel is enabling sync");
|
||||
xps.Weave.Service.configure();
|
||||
},
|
||||
|
||||
/**
|
||||
* logout the fxaccounts service
|
||||
*
|
||||
|
@ -565,14 +619,29 @@ FxAccountsWebChannelHelpers.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
const capabilities = this._getCapabilities();
|
||||
|
||||
return {
|
||||
signedInUser,
|
||||
clientId: FX_OAUTH_CLIENT_ID,
|
||||
capabilities: {
|
||||
capabilities,
|
||||
};
|
||||
},
|
||||
_getCapabilities() {
|
||||
if (
|
||||
Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled", false)
|
||||
) {
|
||||
return {
|
||||
multiService: true,
|
||||
pairing: lazy.pairingEnabled,
|
||||
engines: this._getAvailableExtraEngines(),
|
||||
},
|
||||
choose_what_to_sync: true,
|
||||
engines: CHOOSE_WHAT_TO_SYNC,
|
||||
};
|
||||
}
|
||||
return {
|
||||
multiService: true,
|
||||
pairing: lazy.pairingEnabled,
|
||||
engines: this._getAvailableExtraEngines(),
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -940,6 +940,29 @@ add_task(async function test_getScopedKeys_misconfigured_fxa_server() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function test_setScopedKeys() {
|
||||
const fxa = new MockFxAccounts();
|
||||
const user = {
|
||||
...getTestUser("foo"),
|
||||
verified: true,
|
||||
};
|
||||
await fxa.setSignedInUser(user);
|
||||
await fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys);
|
||||
const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC);
|
||||
Assert.deepEqual(key, {
|
||||
scope: SCOPE_OLD_SYNC,
|
||||
...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_setScopedKeys_user_not_signed_in() {
|
||||
const fxa = new MockFxAccounts();
|
||||
await Assert.rejects(
|
||||
fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys),
|
||||
/Cannot persist keys, no user signed in/
|
||||
);
|
||||
});
|
||||
|
||||
// _fetchAndUnwrapAndDeriveKeys with no keyFetchToken should trigger signOut
|
||||
// XXX - actually, it probably shouldn't - bug 1572313.
|
||||
add_test(function test_fetchAndUnwrapAndDeriveKeys_no_token() {
|
||||
|
|
|
@ -99,8 +99,9 @@ add_task(function test_complete_oauth_flow() {
|
|||
const oauth = new FxAccountsOAuth();
|
||||
const code = "foo";
|
||||
const state = "bar";
|
||||
const sessionToken = "01abcef12";
|
||||
try {
|
||||
await oauth.completeOAuthFlow(code, state);
|
||||
await oauth.completeOAuthFlow(sessionToken, code, state);
|
||||
Assert.fail("Should have thrown an error");
|
||||
} catch (err) {
|
||||
Assert.equal(err.message, ERROR_INVALID_STATE);
|
||||
|
@ -118,9 +119,10 @@ add_task(function test_complete_oauth_flow() {
|
|||
};
|
||||
const oauth = new FxAccountsOAuth(fxaClient);
|
||||
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC];
|
||||
const sessionToken = "01abcef12";
|
||||
const queryParams = await oauth.beginOAuthFlow(scopes);
|
||||
try {
|
||||
await oauth.completeOAuthFlow("foo", queryParams.state);
|
||||
await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state);
|
||||
Assert.fail(
|
||||
"Should have thrown an error because the sync scope was not authorized"
|
||||
);
|
||||
|
@ -140,8 +142,9 @@ add_task(function test_complete_oauth_flow() {
|
|||
};
|
||||
const oauth = new FxAccountsOAuth(fxaClient);
|
||||
const queryParams = await oauth.beginOAuthFlow(scopes);
|
||||
const sessionToken = "01abcef12";
|
||||
try {
|
||||
await oauth.completeOAuthFlow("foo", queryParams.state);
|
||||
await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state);
|
||||
Assert.fail(
|
||||
"Should have thrown an error because we didn't get back a keys_nwe"
|
||||
);
|
||||
|
@ -154,6 +157,7 @@ add_task(function test_complete_oauth_flow() {
|
|||
// from outside our system
|
||||
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC];
|
||||
const oauthCode = "fake oauth code";
|
||||
const sessionToken = "01abcef12";
|
||||
const plainTextScopedKeys = {
|
||||
kid: "fake key id",
|
||||
k: "fake key",
|
||||
|
@ -204,7 +208,8 @@ add_task(function test_complete_oauth_flow() {
|
|||
|
||||
// Now we initialize our mock of the HTTP request, it verifies we passed in all the correct
|
||||
// parameters and returns what we'd expect a healthy HTTP Response would look like
|
||||
fxaClient.oauthToken = (code, verifier, clientId) => {
|
||||
fxaClient.oauthToken = (sessionTokenHex, code, verifier, clientId) => {
|
||||
Assert.equal(sessionTokenHex, sessionToken);
|
||||
Assert.equal(code, oauthCode);
|
||||
Assert.equal(verifier, storedVerifier);
|
||||
Assert.equal(clientId, queryParams.client_id);
|
||||
|
@ -226,7 +231,7 @@ add_task(function test_complete_oauth_flow() {
|
|||
// A slow one that will start first, but finish last
|
||||
// And a fast one that will beat the slow one
|
||||
const firstCompleteOAuthFlow = oauth
|
||||
.completeOAuthFlow(oauthCode, queryParams.state)
|
||||
.completeOAuthFlow(sessionToken, oauthCode, queryParams.state)
|
||||
.then(res => {
|
||||
// To mimic the slow network connection on the slowCompleteOAuthFlow
|
||||
// We resume the slow completeOAuthFlow once this one is complete
|
||||
|
@ -234,7 +239,7 @@ add_task(function test_complete_oauth_flow() {
|
|||
return res;
|
||||
});
|
||||
const secondCompleteOAuthFlow = oauth
|
||||
.completeOAuthFlow(oauthCode, queryParams.state)
|
||||
.completeOAuthFlow(sessionToken, oauthCode, queryParams.state)
|
||||
.then(res => {
|
||||
// since we can't fully gaurentee which oauth flow finishes first, we also resolve here
|
||||
slowResolve();
|
||||
|
|
|
@ -257,6 +257,31 @@ add_test(function test_login_message() {
|
|||
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
});
|
||||
|
||||
add_test(function test_oauth_login() {
|
||||
const mockData = {
|
||||
code: "oauth code",
|
||||
state: "state parameter",
|
||||
declinedSyncEngines: ["tabs", "creditcards"],
|
||||
offeredSyncEngines: ["tabs", "creditcards", "history"],
|
||||
};
|
||||
const mockMessage = {
|
||||
command: "fxaccounts:oauth_login",
|
||||
data: mockData,
|
||||
};
|
||||
const channel = new FxAccountsWebChannel({
|
||||
channel_id: WEBCHANNEL_ID,
|
||||
content_uri: URL_STRING,
|
||||
helpers: {
|
||||
oauthLogin(data) {
|
||||
Assert.deepEqual(data, mockData);
|
||||
run_next_test();
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
});
|
||||
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
});
|
||||
|
||||
add_test(function test_logout_message() {
|
||||
let mockMessage = {
|
||||
command: "fxaccounts:logout",
|
||||
|
|
|
@ -170,7 +170,7 @@ export var makeFxAccountsInternalMock = function (config) {
|
|||
return accountState;
|
||||
},
|
||||
getOAuthToken: () => Promise.resolve("some-access-token"),
|
||||
_destroyOAuthToken: () => Promise.resolve(),
|
||||
destroyOAuthToken: () => Promise.resolve(),
|
||||
keys: {
|
||||
getScopedKeys: () =>
|
||||
Promise.resolve({
|
||||
|
|
Загрузка…
Ссылка в новой задаче