Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
import { IPPAuthProvider } from "moz-src:///toolkit/components/ipprotection/IPPAuthProvider.sys.mjs";
import { GUARDIAN_EXPERIMENT_TYPE } from "moz-src:///toolkit/components/ipprotection/GuardianClient.sys.mjs";
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () =>
ChromeUtils.importESModule(
"resource://gre/modules/FxAccounts.sys.mjs"
).getFxAccountsSingleton()
);
ChromeUtils.defineESModuleGetters(lazy, {
IPPEnrollAndEntitleManager:
"moz-src:///toolkit/components/ipprotection/fxa/IPPEnrollAndEntitleManager.sys.mjs",
IPPSignInWatcher:
"moz-src:///toolkit/components/ipprotection/fxa/IPPSignInWatcher.sys.mjs",
IPProtectionService:
"moz-src:///toolkit/components/ipprotection/IPProtectionService.sys.mjs",
});
const CLIENT_ID_MAP = {
};
const GUARDIAN_ENDPOINT_PREF = "browser.ipProtection.guardian.endpoint";
/**
* FxA implementation of IPPAuthProvider. Handles OAuth token retrieval,
* enrollment via Guardian, and FxA-specific proxy bypass rules.
*/
class IPPFxaAuthProviderSingleton extends IPPAuthProvider {
#signInWatcher = null;
#enrollAndEntitleFn = null;
/**
* @param {object} [signInWatcher] - Custom sign-in watcher. Defaults to IPPSignInWatcher.
* @param {Function} [enrollAndEntitleFn] - Custom enroll function. Defaults to the FxA hidden-window flow.
*/
constructor(signInWatcher = null, enrollAndEntitleFn = null) {
super();
this.#signInWatcher = signInWatcher;
this.#enrollAndEntitleFn =
enrollAndEntitleFn ??
IPPFxaAuthProviderSingleton.#defaultEnrollAndEntitle;
}
get signInWatcher() {
return this.#signInWatcher ?? lazy.IPPSignInWatcher;
}
/**
* @param {AbortSignal} [abortSignal]
* @returns {Promise<{isEnrolledAndEntitled: boolean, entitlement?: object, error?: string}>}
*/
async enrollAndEntitle(abortSignal) {
return this.#enrollAndEntitleFn(abortSignal);
}
static async #defaultEnrollAndEntitle(abortSignal = null) {
try {
const result = await lazy.IPProtectionService.guardian.enrollWithFxa(
GUARDIAN_EXPERIMENT_TYPE,
abortSignal
);
if (!result?.ok) {
return { isEnrolledAndEntitled: false, error: result?.error };
}
} catch (error) {
return { isEnrolledAndEntitled: false, error: error?.message };
}
const { entitlement, error } =
await IPPFxaAuthProviderSingleton.#fetchEntitlement();
if (error || !entitlement) {
return { isEnrolledAndEntitled: false, error };
}
return { isEnrolledAndEntitled: true, entitlement };
}
/**
* @param {boolean} [forceRefetch=false]
* @returns {Promise<{entitlement?: object, error?: string}>}
*/
async getEntitlement(forceRefetch = false) {
const isLinked = await this.#isLinkedToGuardian(!forceRefetch);
if (!isLinked) {
return {};
}
return IPPFxaAuthProviderSingleton.#fetchEntitlement();
}
async #isLinkedToGuardian(useCache = true) {
try {
const endpoint = Services.prefs.getCharPref(
GUARDIAN_ENDPOINT_PREF,
GUARDIAN_ENDPOINT_DEFAULT
);
const clientId = CLIENT_ID_MAP[new URL(endpoint).origin];
if (!clientId) {
return false;
}
const cached = await lazy.fxAccounts.listAttachedOAuthClients();
if (cached.some(c => c.id === clientId)) {
return true;
}
if (useCache) {
return false;
}
const refreshed = await lazy.fxAccounts.listAttachedOAuthClients(true);
return refreshed.some(c => c.id === clientId);
} catch (_) {
return false;
}
}
static async #fetchEntitlement() {
try {
const { status, entitlement, error } =
await lazy.IPProtectionService.guardian.fetchUserInfo();
if (error || !entitlement || status != 200) {
return { error: error || `Status: ${status}` };
}
return { entitlement };
} catch (error) {
return { error: error.message };
}
}
get helpers() {
return [this.signInWatcher, lazy.IPPEnrollAndEntitleManager];
}
get isReady() {
// For non authenticated users, we don't know yet their enroll state so the UI
// is shown and they have to login.
if (!this.signInWatcher.isSignedIn) {
return false;
}
// If the current account is not enrolled and entitled, the UI is shown and
// they have to opt-in.
// If they are currently enrolling, they have already opted-in.
if (
!lazy.IPPEnrollAndEntitleManager.isEnrolledAndEntitled &&
!lazy.IPPEnrollAndEntitleManager.isEnrolling
) {
return false;
}
return true;
}
/**
* Retrieves an FxA OAuth token and returns a disposable handle that revokes
* it on disposal.
*
* @param {AbortSignal} [abortSignal]
* @returns {Promise<{token: string} & Disposable>}
*/
async getToken(abortSignal = null) {
let tasks = [
lazy.fxAccounts.getOAuthToken({
}),
];
if (abortSignal) {
abortSignal.throwIfAborted();
tasks.push(
new Promise((_, rej) => {
abortSignal?.addEventListener("abort", rej, { once: true });
})
);
}
const token = await Promise.race(tasks);
if (!token) {
return null;
}
return {
token,
[Symbol.dispose]: () => {
lazy.fxAccounts.removeCachedOAuthToken({ token });
},
};
}
async aboutToStart() {
let result;
if (lazy.IPPEnrollAndEntitleManager.isEnrolling) {
result = await lazy.IPPEnrollAndEntitleManager.waitForEnrollment();
}
if (!lazy.IPPEnrollAndEntitleManager.isEnrolledAndEntitled) {
return { error: result?.error };
}
return null;
}
get excludedUrlPrefs() {
return [
"identity.fxaccounts.remote.profile.uri",
"identity.fxaccounts.auth.uri",
];
}
}
const IPPFxaAuthProvider = new IPPFxaAuthProviderSingleton();
export { IPPFxaAuthProvider, IPPFxaAuthProviderSingleton };