Bug 1814091 - Move CanvasContext.getPreferredFormat to GPU.getPreferredCanvasFormat...
[gecko.git] / services / fxaccounts / FxAccountsConfig.sys.mjs
blobaf213a1e90f475b5d03e508372a535f884f892f0
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { RESTRequest } from "resource://services-common/rest.sys.mjs";
7 const { log } = ChromeUtils.import(
8   "resource://gre/modules/FxAccountsCommon.js"
9 );
10 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
12 const lazy = {};
14 XPCOMUtils.defineLazyGetter(lazy, "fxAccounts", () => {
15   return ChromeUtils.importESModule(
16     "resource://gre/modules/FxAccounts.sys.mjs"
17   ).getFxAccountsSingleton();
18 });
20 ChromeUtils.defineESModuleGetters(lazy, {
21   EnsureFxAccountsWebChannel:
22     "resource://gre/modules/FxAccountsWebChannel.sys.mjs",
23 });
25 XPCOMUtils.defineLazyPreferenceGetter(
26   lazy,
27   "ROOT_URL",
28   "identity.fxaccounts.remote.root"
30 XPCOMUtils.defineLazyPreferenceGetter(
31   lazy,
32   "CONTEXT_PARAM",
33   "identity.fxaccounts.contextParam"
35 XPCOMUtils.defineLazyPreferenceGetter(
36   lazy,
37   "REQUIRES_HTTPS",
38   "identity.fxaccounts.allowHttp",
39   false,
40   null,
41   val => !val
44 const CONFIG_PREFS = [
45   "identity.fxaccounts.remote.root",
46   "identity.fxaccounts.auth.uri",
47   "identity.fxaccounts.remote.oauth.uri",
48   "identity.fxaccounts.remote.profile.uri",
49   "identity.fxaccounts.remote.pairing.uri",
50   "identity.sync.tokenserver.uri",
52 const SYNC_PARAM = "sync";
54 export var FxAccountsConfig = {
55   async promiseEmailURI(email, entrypoint, extraParams = {}) {
56     return this._buildURL("", {
57       extraParams: { entrypoint, email, service: SYNC_PARAM, ...extraParams },
58     });
59   },
61   async promiseConnectAccountURI(entrypoint, extraParams = {}) {
62     return this._buildURL("", {
63       extraParams: {
64         entrypoint,
65         action: "email",
66         service: SYNC_PARAM,
67         ...extraParams,
68       },
69     });
70   },
72   async promiseForceSigninURI(entrypoint, extraParams = {}) {
73     return this._buildURL("force_auth", {
74       extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams },
75       addAccountIdentifiers: true,
76     });
77   },
79   async promiseManageURI(entrypoint, extraParams = {}) {
80     return this._buildURL("settings", {
81       extraParams: { entrypoint, ...extraParams },
82       addAccountIdentifiers: true,
83     });
84   },
86   async promiseChangeAvatarURI(entrypoint, extraParams = {}) {
87     return this._buildURL("settings/avatar/change", {
88       extraParams: { entrypoint, ...extraParams },
89       addAccountIdentifiers: true,
90     });
91   },
93   async promiseManageDevicesURI(entrypoint, extraParams = {}) {
94     return this._buildURL("settings/clients", {
95       extraParams: { entrypoint, ...extraParams },
96       addAccountIdentifiers: true,
97     });
98   },
100   async promiseConnectDeviceURI(entrypoint, extraParams = {}) {
101     return this._buildURL("connect_another_device", {
102       extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams },
103       addAccountIdentifiers: true,
104     });
105   },
107   async promisePairingURI(extraParams = {}) {
108     return this._buildURL("pair", {
109       extraParams,
110       includeDefaultParams: false,
111     });
112   },
114   async promiseOAuthURI(extraParams = {}) {
115     return this._buildURL("oauth", {
116       extraParams,
117       includeDefaultParams: false,
118     });
119   },
121   async promiseMetricsFlowURI(entrypoint, extraParams = {}) {
122     return this._buildURL("metrics-flow", {
123       extraParams: { entrypoint, ...extraParams },
124       includeDefaultParams: false,
125     });
126   },
128   get defaultParams() {
129     return { context: lazy.CONTEXT_PARAM };
130   },
132   /**
133    * @param path should be parsable by the URL constructor first parameter.
134    * @param {bool} [options.includeDefaultParams] If true include the default search params.
135    * @param {Object.<string, string>} [options.extraParams] Additionnal search params.
136    * @param {bool} [options.addAccountIdentifiers] if true we add the current logged-in user uid and email to the search params.
137    */
138   async _buildURL(
139     path,
140     {
141       includeDefaultParams = true,
142       extraParams = {},
143       addAccountIdentifiers = false,
144     }
145   ) {
146     await this.ensureConfigured();
147     const url = new URL(path, lazy.ROOT_URL);
148     if (lazy.REQUIRES_HTTPS && url.protocol != "https:") {
149       throw new Error("Firefox Accounts server must use HTTPS");
150     }
151     const params = {
152       ...(includeDefaultParams ? this.defaultParams : null),
153       ...extraParams,
154     };
155     for (let [k, v] of Object.entries(params)) {
156       url.searchParams.append(k, v);
157     }
158     if (addAccountIdentifiers) {
159       const accountData = await this.getSignedInUser();
160       if (!accountData) {
161         return null;
162       }
163       url.searchParams.append("uid", accountData.uid);
164       url.searchParams.append("email", accountData.email);
165     }
166     return url.href;
167   },
169   async _buildURLFromString(href, extraParams = {}) {
170     const url = new URL(href);
171     for (let [k, v] of Object.entries(extraParams)) {
172       url.searchParams.append(k, v);
173     }
174     return url.href;
175   },
177   resetConfigURLs() {
178     let autoconfigURL = this.getAutoConfigURL();
179     if (!autoconfigURL) {
180       return;
181     }
182     // They have the autoconfig uri pref set, so we clear all the prefs that we
183     // will have initialized, which will leave them pointing at production.
184     for (let pref of CONFIG_PREFS) {
185       Services.prefs.clearUserPref(pref);
186     }
187     // Reset the webchannel.
188     lazy.EnsureFxAccountsWebChannel();
189   },
191   getAutoConfigURL() {
192     let pref = Services.prefs.getCharPref(
193       "identity.fxaccounts.autoconfig.uri",
194       ""
195     );
196     if (!pref) {
197       // no pref / empty pref means we don't bother here.
198       return "";
199     }
200     let rootURL = Services.urlFormatter.formatURL(pref);
201     if (rootURL.endsWith("/")) {
202       rootURL = rootURL.slice(0, -1);
203     }
204     return rootURL;
205   },
207   async ensureConfigured() {
208     let isSignedIn = !!(await this.getSignedInUser());
209     if (!isSignedIn) {
210       await this.updateConfigURLs();
211     }
212   },
214   // Returns true if this user is using the FxA "production" systems, false
215   // if using any other configuration, including self-hosting or the FxA
216   // non-production systems such as "dev" or "staging".
217   // It's typically used as a proxy for "is this likely to be a self-hosted
218   // user?", but it's named this way to make the implementation less
219   // surprising. As a result, it's fairly conservative and would prefer to have
220   // a false-negative than a false-position as it determines things which users
221   // might consider sensitive (notably, telemetry).
222   // Note also that while it's possible to self-host just sync and not FxA, we
223   // don't make that distinction - that's a self-hoster from the POV of this
224   // function.
225   isProductionConfig() {
226     // Specifically, if the autoconfig URLs, or *any* of the URLs that
227     // we consider configurable are modified, we assume self-hosted.
228     if (this.getAutoConfigURL()) {
229       return false;
230     }
231     for (let pref of CONFIG_PREFS) {
232       if (Services.prefs.prefHasUserValue(pref)) {
233         return false;
234       }
235     }
236     return true;
237   },
239   // Read expected client configuration from the fxa auth server
240   // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
241   // and replace all the relevant our prefs with the information found there.
242   // This is only done before sign-in and sign-up, and even then only if the
243   // `identity.fxaccounts.autoconfig.uri` preference is set.
244   async updateConfigURLs() {
245     let rootURL = this.getAutoConfigURL();
246     if (!rootURL) {
247       return;
248     }
249     const config = await this.fetchConfigDocument(rootURL);
250     try {
251       // Update the prefs directly specified by the config.
252       let authServerBase = config.auth_server_base_url;
253       if (!authServerBase.endsWith("/v1")) {
254         authServerBase += "/v1";
255       }
256       Services.prefs.setCharPref(
257         "identity.fxaccounts.auth.uri",
258         authServerBase
259       );
260       Services.prefs.setCharPref(
261         "identity.fxaccounts.remote.oauth.uri",
262         config.oauth_server_base_url + "/v1"
263       );
264       // At the time of landing this, our servers didn't yet answer with pairing_server_base_uri.
265       // Remove this condition check once Firefox 68 is stable.
266       if (config.pairing_server_base_uri) {
267         Services.prefs.setCharPref(
268           "identity.fxaccounts.remote.pairing.uri",
269           config.pairing_server_base_uri
270         );
271       }
272       Services.prefs.setCharPref(
273         "identity.fxaccounts.remote.profile.uri",
274         config.profile_server_base_url + "/v1"
275       );
276       Services.prefs.setCharPref(
277         "identity.sync.tokenserver.uri",
278         config.sync_tokenserver_base_url + "/1.0/sync/1.5"
279       );
280       Services.prefs.setCharPref("identity.fxaccounts.remote.root", rootURL);
282       // Ensure the webchannel is pointed at the correct uri
283       lazy.EnsureFxAccountsWebChannel();
284     } catch (e) {
285       log.error(
286         "Failed to initialize configuration preferences from autoconfig object",
287         e
288       );
289       throw e;
290     }
291   },
293   // Read expected client configuration from the fxa auth server
294   // (or from the provided rootURL, if present) and return it as an object.
295   async fetchConfigDocument(rootURL = null) {
296     if (!rootURL) {
297       rootURL = lazy.ROOT_URL;
298     }
299     let configURL = rootURL + "/.well-known/fxa-client-configuration";
300     let request = new RESTRequest(configURL);
301     request.setHeader("Accept", "application/json");
303     // Catch and rethrow the error inline.
304     let resp = await request.get().catch(e => {
305       log.error(`Failed to get configuration object from "${configURL}"`, e);
306       throw e;
307     });
308     if (!resp.success) {
309       // Note: 'resp.body' is included with the error log below as we are not concerned
310       // that the body will contain PII, but if that changes it should be excluded.
311       log.error(
312         `Received HTTP response code ${resp.status} from configuration object request:
313         ${resp.body}`
314       );
315       throw new Error(
316         `HTTP status ${resp.status} from configuration object request`
317       );
318     }
319     log.debug("Got successful configuration response", resp.body);
320     try {
321       return JSON.parse(resp.body);
322     } catch (e) {
323       log.error(
324         `Failed to parse configuration preferences from ${configURL}`,
325         e
326       );
327       throw e;
328     }
329   },
331   // For test purposes, returns a Promise.
332   getSignedInUser() {
333     return lazy.fxAccounts.getSignedInUser();
334   },