Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions browser/app/profile/firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,9 @@ pref("browser.uiCustomization.state", "");
// A restart is mandatory after flipping that preference.
pref("identity.fxaccounts.enabled", true);

// Use of the Rust backend vs JS backend
pref("identity.fxaccounts.useRustBackend", true /* TODO: make false later */);

// The remote FxA root content URL. Must use HTTPS.
pref("identity.fxaccounts.remote.root", "https://accounts.firefox.com/");

Expand Down
13 changes: 12 additions & 1 deletion browser/base/content/browser-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ ChromeUtils.defineModuleGetter(
"resource://services-sync/main.js"
);

XPCOMUtils.defineLazyPreferenceGetter(
this,
"RUST_BACKEND",
"identity.fxaccounts.useRustBackend"
);

const MIN_STATUS_ANIMATION_DURATION = 1600;

var gSync = {
Expand Down Expand Up @@ -735,7 +741,12 @@ var gSync = {
},

async openFxAEmailFirstPage(entryPoint) {
const url = await FxAccounts.config.promiseConnectAccountURI(entryPoint);
let url;
if (RUST_BACKEND) {
url = await FxAccounts.config.promiseConnectAccountOAuthURI();
} else {
url = await FxAccounts.config.promiseConnectAccountURI(entryPoint);
}
switchToTabHavingURI(url, true, { replaceQueryString: true });
},

Expand Down
176 changes: 159 additions & 17 deletions services/fxaccounts/FxAccounts.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { PromiseUtils } = ChromeUtils.import(
"resource://gre/modules/PromiseUtils.jsm"
);
Expand Down Expand Up @@ -37,6 +40,7 @@ const {
FXA_PWDMGR_REAUTH_WHITELIST,
FXA_PWDMGR_SECURE_FIELDS,
FX_OAUTH_CLIENT_ID,
FX_OAUTH_WEBCHANNEL_REDIRECT,
KEY_LIFETIME,
ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
ONLOGIN_NOTIFICATION,
Expand Down Expand Up @@ -105,6 +109,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/FxAccountsTelemetry.jsm"
);

ChromeUtils.defineModuleGetter(
this,
"RustFxAccount",
"resource://gre/modules/RustFxAccount.js"
);

XPCOMUtils.defineLazyModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.jsm",
});
Expand All @@ -116,6 +126,21 @@ XPCOMUtils.defineLazyPreferenceGetter(
true
);

XPCOMUtils.defineLazyPreferenceGetter(
this,
"ROOT_URL",
"identity.fxaccounts.remote.root"
);

XPCOMUtils.defineLazyPreferenceGetter(
this,
"RUST_BACKEND",
"identity.fxaccounts.useRustBackend",
null,
null,
val => (AppConstants.NIGHTLY_BUILD ? val : false) // On non-nightly builds the pref shouldn't even work.
);

// 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 -
Expand Down Expand Up @@ -426,6 +451,17 @@ class FxAccounts {
return this._internal.telemetry;
}

// Help VSCode help us with some nice autocompletions :)
/**
* @returns {RustFxAccount}
*/
get _rustFxa() {
if (RUST_BACKEND) {
return this._internal.rustFxa;
}
return false;
}

_withCurrentAccountState(func) {
return this._internal.withCurrentAccountState(func);
}
Expand Down Expand Up @@ -454,21 +490,25 @@ class FxAccounts {
// We expose last accessed times in 'days ago'
const ONE_DAY = 24 * 60 * 60 * 1000;

return this._withSessionToken(async sessionToken => {
const attachedClients = await this._internal.fxAccountsClient.attachedClients(
sessionToken
);
// We should use the server timestamp here - bug 1595635
let now = Date.now();
return attachedClients.map(client => {
const daysAgo = client.lastAccessTime
? Math.max(Math.floor((now - client.lastAccessTime) / ONE_DAY), 0)
: null;
return {
id: client.clientId,
lastAccessedDaysAgo: daysAgo,
};
let attachedClients;

if (this._rustFxa) {
attachedClients = await this._rustFxa.getAttachedClients();
} else {
attachedClients = await this._withSessionToken(async sessionToken => {
return this._internal.fxAccountsClient.attachedClients(sessionToken);
});
}
// We should use the server timestamp here - bug 1595635
let now = Date.now();
return attachedClients.map(client => {
const daysAgo = client.lastAccessTime
? Math.max(Math.floor((now - client.lastAccessTime) / ONE_DAY), 0)
: null;
return {
id: client.clientId,
lastAccessedDaysAgo: daysAgo,
};
});
}

Expand All @@ -486,8 +526,10 @@ class FxAccounts {
* @returns {Promise<Object>} Object containing "code" and "state" properties.
*/
authorizeOAuthCode(options) {
return this._withVerifiedAccountState(async state => {
const { sessionToken } = await state.getUserAccountData(["sessionToken"]);
// TODO: here we'd just grab the session token from fxa because we can't do all rust:
// https://github.com/mozilla/application-services/issues/3122

const oauthAuthorize = async sessionToken => {
const params = { ...options };
if (params.keys_jwk) {
const jwk = JSON.parse(
Expand All @@ -510,6 +552,16 @@ class FxAccounts {
} catch (err) {
throw this._internal._errorToErrorClass(err);
}
};

if (this._rustFxa) {
const sessionToken = this._rustFxa.getSessionToken();
return oauthAuthorize(sessionToken);
}

return this._withVerifiedAccountState(async state => {
const { sessionToken } = await state.getUserAccountData(["sessionToken"]);
return oauthAuthorize(sessionToken);
});
}

Expand All @@ -536,6 +588,15 @@ class FxAccounts {
* UNKNOWN_ERROR
*/
async getOAuthToken(options = {}) {
if (this._rustFxa) {
if (Array.isArray(options.scope)) {
throw new Error(
"The Rust backend does not support requesting multiple scopes at once."
);
}
const accessToken = await this._rustFxa.getAccessToken(options.scope);
return accessToken.token;
}
try {
return await this._internal.getOAuthToken(options);
} catch (err) {
Expand Down Expand Up @@ -598,6 +659,10 @@ class FxAccounts {
* an unknown token is passed.
*/
removeCachedOAuthToken(options) {
if (this._rustFxa) {
// Well we don't have a method clears tokens for 1 scope, so...
return this._rustFxa.clearAccessTokenCache();
}
return this._internal.removeCachedOAuthToken(options);
}

Expand All @@ -622,6 +687,37 @@ class FxAccounts {
* in pathological cases (eg, file-system errors, etc)
*/
getSignedInUser() {
if (this._rustFxa) {
return (async () => {
let sessionToken;
try {
sessionToken = await this._rustFxa.getSessionToken();
} catch {
// TODO: Throwing if sessionToken is None is a bit... savage,
// but maybe in M5 we won't need to answer "do we have a session token".
}

if (!sessionToken) {
// This likely means we're logged-out.
return null;
}
const {
email,
uid,
avatarDefault,
avatar,
displayName,
} = await this._rustFxa.getProfile();
return {
email,
uid,
verified: true /* there's no such thing as "unverified state" in the OAuth world */,
displayName,
avatar,
avatarDefault,
};
})();
}
// Note we don't return the session token, but use it to see if we
// should fetch the profile.
const ACCT_DATA_FIELDS = ["email", "uid", "verified", "sessionToken"];
Expand Down Expand Up @@ -685,6 +781,10 @@ class FxAccounts {
* you saw an auth related exception from a remote service.)
*/
checkAccountStatus() {
if (this._rustFxa) {
// Noop, coz that function is a bit complex, sorry.
return Promise.resolve(true);
}
// Note that we don't use _withCurrentAccountState here because that will
// cause an exception to be thrown if we end up signing out due to the
// account not existing, which isn't what we want here.
Expand All @@ -706,7 +806,14 @@ class FxAccounts {
* considered the canonical, albiet expensive, way to determine the
* status of the account.
*/
hasLocalSession() {
async hasLocalSession() {
if (this._rustFxa) {
try {
return !!(await this._rustFxa.getSessionToken());
} catch {
return false;
}
}
return this._withCurrentAccountState(async state => {
let data = await state.getUserAccountData(["sessionToken"]);
return !!(data && data.sessionToken);
Expand All @@ -731,6 +838,9 @@ class FxAccounts {
// "an object"), but to be useful across devices, the payload really needs
// formalizing. We should try and do something better here.
notifyDevices(deviceIds, excludedIds, payload, TTL) {
if (this._rustFxa) {
return Promise.resolve(true); // noop, will get removed anyway.
}
return this._internal.notifyDevices(deviceIds, excludedIds, payload, TTL);
}

Expand All @@ -739,13 +849,19 @@ class FxAccounts {
*
*/
resendVerificationEmail() {
if (this._rustFxa) {
return Promise.resolve(true);
}
return this._withSessionToken((token, currentState) => {
this._internal.startPollEmailStatus(currentState, token, "start");
return this._internal.fxAccountsClient.resendVerificationEmail(token);
}, false);
}

async signOut(localOnly) {
if (this._rustFxa) {
return this._rustFxa.disconnect();
}
// Note that we do not use _withCurrentAccountState here, otherwise we
// end up with an exception due to the user signing out before the call is
// complete - but that's the entire point of this method :)
Expand All @@ -756,13 +872,20 @@ class FxAccounts {
// so that sync can change it when it notices the device name being changed,
// and that could probably be replaced with a pref observer.
updateDeviceRegistration() {
if (this._rustFxa) {
// Short-circuit the thing.
return this.device.updateDeviceRegistration();
}
return this._withCurrentAccountState(_ => {
return this._internal.updateDeviceRegistration();
});
}

// we should try and kill this too.
whenVerified(data) {
if (this._rustFxa) {
return Promise.resolve(true);
}
return this._withCurrentAccountState(_ => {
return this._internal.whenVerified(data);
});
Expand Down Expand Up @@ -793,6 +916,25 @@ FxAccountsInternal.prototype = {
// All significant initialization should be done in this initialize() method
// to help with our mocking story.
initialize() {
if (RUST_BACKEND) {
let logins = Services.logins.findLogins(
"chrome://fxarust",
null,
"FxA Rust state"
);
if (logins.length == 1) {
let stateJSON = logins[0];
this.rustFxa = new RustFxAccount(stateJSON.password);
} else {
this.rustFxa = new RustFxAccount({
fxaServer: "https://accounts.firefox.com",
clientId: "3c49430b43dfba77",
redirectUri:
"https://accounts.firefox.com/oauth/success/3c49430b43dfba77",
});
}
}

XPCOMUtils.defineLazyGetter(this, "fxaPushService", function() {
return Cc["@mozilla.org/fxaccounts/push;1"].getService(
Ci.nsISupports
Expand Down
4 changes: 4 additions & 0 deletions services/fxaccounts/FxAccountsCommon.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,11 @@ exports.COMMAND_SENDTAB = exports.COMMAND_PREFIX + exports.COMMAND_SENDTAB_TAIL;

// OAuth
exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
exports.FX_OAUTH_WEBCHANNEL_REDIRECT =
"urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel";
exports.SCOPE_PROFILE = "profile";
exports.SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync";
exports.SCOPE_SESSION_TOKEN = "https://identity.mozilla.com/tokens/session";

// OAuth metadata for other Firefox-related services that we might need to know about
// in order to provide an enhanced user experience.
Expand All @@ -112,6 +115,7 @@ exports.COMMAND_PAIR_COMPLETE = "fxaccounts:pair_complete";
exports.COMMAND_PROFILE_CHANGE = "profile:change";
exports.COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
exports.COMMAND_LOGIN = "fxaccounts:login";
exports.COMMAND_OAUTH_LOGIN = "fxaccounts:oauth_login";
exports.COMMAND_LOGOUT = "fxaccounts:logout";
exports.COMMAND_DELETE = "fxaccounts:delete";
exports.COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
Expand Down
6 changes: 5 additions & 1 deletion services/fxaccounts/FxAccountsConfig.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var EXPORTED_SYMBOLS = ["FxAccountsConfig"];
const { RESTRequest } = ChromeUtils.import(
"resource://services-common/rest.js"
);
const { log } = ChromeUtils.import(
const { SCOPE_PROFILE, SCOPE_OLD_SYNC, SCOPE_SESSION_TOKEN, log } = ChromeUtils.import(
"resource://gre/modules/FxAccountsCommon.js"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
Expand Down Expand Up @@ -74,6 +74,10 @@ var FxAccountsConfig = {
});
},

async promiseConnectAccountOAuthURI() {
return fxAccounts._internal.rustFxa.beginOAuthFlow([SCOPE_PROFILE, SCOPE_OLD_SYNC, SCOPE_SESSION_TOKEN]);
},

async promiseForceSigninURI(entrypoint, extraParams = {}) {
return this._buildURL("force_auth", {
extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams },
Expand Down
Loading