Source code

Revision control

Other Tools

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/. */
4
"use strict";
5
var EXPORTED_SYMBOLS = ["FxAccountsConfig"];
6
7
const { RESTRequest } = ChromeUtils.import(
9
);
10
const { log } = ChromeUtils.import(
12
);
13
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
14
const { XPCOMUtils } = ChromeUtils.import(
16
);
17
18
ChromeUtils.defineModuleGetter(
19
this,
20
"fxAccounts",
22
);
23
24
ChromeUtils.defineModuleGetter(
25
this,
26
"EnsureFxAccountsWebChannel",
28
);
29
30
XPCOMUtils.defineLazyPreferenceGetter(
31
this,
32
"ROOT_URL",
33
"identity.fxaccounts.remote.root"
34
);
35
XPCOMUtils.defineLazyPreferenceGetter(
36
this,
37
"CONTEXT_PARAM",
38
"identity.fxaccounts.contextParam"
39
);
40
XPCOMUtils.defineLazyPreferenceGetter(
41
this,
42
"REQUIRES_HTTPS",
43
// Also used in FxAccountsOAuthGrantClient.jsm.
44
"identity.fxaccounts.allowHttp",
45
false,
46
null,
47
val => !val
48
);
49
50
const CONFIG_PREFS = [
51
"identity.fxaccounts.remote.root",
52
"identity.fxaccounts.auth.uri",
53
"identity.fxaccounts.remote.oauth.uri",
54
"identity.fxaccounts.remote.profile.uri",
55
"identity.fxaccounts.remote.pairing.uri",
56
"identity.sync.tokenserver.uri",
57
];
58
59
var FxAccountsConfig = {
60
async promiseSignUpURI(entrypoint) {
61
return this._buildURL("signup", {
62
extraParams: { entrypoint },
63
});
64
},
65
66
async promiseSignInURI(entrypoint) {
67
return this._buildURL("signin", {
68
extraParams: { entrypoint },
69
});
70
},
71
72
async promiseEmailURI(email, entrypoint) {
73
return this._buildURL("", {
74
extraParams: { entrypoint, email },
75
});
76
},
77
78
async promiseEmailFirstURI(entrypoint) {
79
return this._buildURL("", {
80
extraParams: { entrypoint, action: "email" },
81
});
82
},
83
84
async promiseForceSigninURI(entrypoint) {
85
return this._buildURL("force_auth", {
86
extraParams: { entrypoint },
87
addAccountIdentifiers: true,
88
});
89
},
90
91
async promiseManageURI(entrypoint) {
92
return this._buildURL("settings", {
93
extraParams: { entrypoint },
94
addAccountIdentifiers: true,
95
});
96
},
97
98
async promiseChangeAvatarURI(entrypoint) {
99
return this._buildURL("settings/avatar/change", {
100
extraParams: { entrypoint },
101
addAccountIdentifiers: true,
102
});
103
},
104
105
async promiseManageDevicesURI(entrypoint) {
106
return this._buildURL("settings/clients", {
107
extraParams: { entrypoint },
108
addAccountIdentifiers: true,
109
});
110
},
111
112
async promiseConnectDeviceURI(entrypoint) {
113
return this._buildURL("connect_another_device", {
114
extraParams: { entrypoint },
115
addAccountIdentifiers: true,
116
});
117
},
118
119
async promisePairingURI() {
120
return this._buildURL("pair", {
121
includeDefaultParams: false,
122
});
123
},
124
125
async promiseOAuthURI() {
126
return this._buildURL("oauth", {
127
includeDefaultParams: false,
128
});
129
},
130
131
get defaultParams() {
132
return { service: "sync", context: CONTEXT_PARAM };
133
},
134
135
/**
136
* @param path should be parsable by the URL constructor first parameter.
137
* @param {bool} [options.includeDefaultParams] If true include the default search params.
138
* @param {Object.<string, string>} [options.extraParams] Additionnal search params.
139
* @param {bool} [options.addAccountIdentifiers] if true we add the current logged-in user uid and email to the search params.
140
*/
141
async _buildURL(
142
path,
143
{
144
includeDefaultParams = true,
145
extraParams = {},
146
addAccountIdentifiers = false,
147
}
148
) {
149
await this.ensureConfigured();
150
const url = new URL(path, ROOT_URL);
151
if (REQUIRES_HTTPS && url.protocol != "https:") {
152
throw new Error("Firefox Accounts server must use HTTPS");
153
}
154
const params = {
155
...(includeDefaultParams ? this.defaultParams : null),
156
...extraParams,
157
};
158
for (let [k, v] of Object.entries(params)) {
159
url.searchParams.append(k, v);
160
}
161
if (addAccountIdentifiers) {
162
const accountData = await this.getSignedInUser();
163
if (!accountData) {
164
return null;
165
}
166
url.searchParams.append("uid", accountData.uid);
167
url.searchParams.append("email", accountData.email);
168
}
169
return url.href;
170
},
171
172
resetConfigURLs() {
173
let autoconfigURL = this.getAutoConfigURL();
174
if (!autoconfigURL) {
175
return;
176
}
177
// They have the autoconfig uri pref set, so we clear all the prefs that we
178
// will have initialized, which will leave them pointing at production.
179
for (let pref of CONFIG_PREFS) {
180
Services.prefs.clearUserPref(pref);
181
}
182
// Reset the webchannel.
183
EnsureFxAccountsWebChannel();
184
},
185
186
getAutoConfigURL() {
187
let pref = Services.prefs.getCharPref(
188
"identity.fxaccounts.autoconfig.uri",
189
""
190
);
191
if (!pref) {
192
// no pref / empty pref means we don't bother here.
193
return "";
194
}
195
let rootURL = Services.urlFormatter.formatURL(pref);
196
if (rootURL.endsWith("/")) {
197
rootURL = rootURL.slice(0, -1);
198
}
199
return rootURL;
200
},
201
202
async ensureConfigured() {
203
await this.tryPrefsMigration();
204
let isSignedIn = !!(await this.getSignedInUser());
205
if (!isSignedIn) {
206
await this.fetchConfigURLs();
207
}
208
},
209
210
// In bug 1427674 we migrated a set of preferences with a shared origin
211
// to a single preference (identity.fxaccounts.remote.root).
212
// This whole function should be removed in version 65 or later once
213
// everyone had a chance to migrate.
214
async tryPrefsMigration() {
215
// If this pref is set, there is a very good chance the user is running
216
// a custom FxA content server.
217
if (
218
!Services.prefs.prefHasUserValue("identity.fxaccounts.remote.signin.uri")
219
) {
220
return;
221
}
222
223
if (Services.prefs.prefHasUserValue("identity.fxaccounts.autoconfig.uri")) {
224
await this.fetchConfigURLs();
225
} else {
226
// Best effort.
227
const signinURI = Services.prefs.getCharPref(
228
"identity.fxaccounts.remote.signin.uri"
229
);
230
Services.prefs.setCharPref(
231
"identity.fxaccounts.remote.root",
232
signinURI.slice(0, signinURI.lastIndexOf("/signin")) + "/"
233
);
234
}
235
236
const migratedPrefs = [
237
"identity.fxaccounts.remote.webchannel.uri",
238
"identity.fxaccounts.settings.uri",
239
"identity.fxaccounts.settings.devices.uri",
240
"identity.fxaccounts.remote.signup.uri",
241
"identity.fxaccounts.remote.signin.uri",
242
"identity.fxaccounts.remote.email.uri",
243
"identity.fxaccounts.remote.connectdevice.uri",
244
"identity.fxaccounts.remote.force_auth.uri",
245
];
246
for (const pref of migratedPrefs) {
247
Services.prefs.clearUserPref(pref);
248
}
249
},
250
251
// Read expected client configuration from the fxa auth server
252
// (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
253
// and replace all the relevant our prefs with the information found there.
254
// This is only done before sign-in and sign-up, and even then only if the
255
// `identity.fxaccounts.autoconfig.uri` preference is set.
256
async fetchConfigURLs() {
257
let rootURL = this.getAutoConfigURL();
258
if (!rootURL) {
259
return;
260
}
261
let configURL = rootURL + "/.well-known/fxa-client-configuration";
262
let request = new RESTRequest(configURL);
263
request.setHeader("Accept", "application/json");
264
265
// Catch and rethrow the error inline.
266
let resp = await request.get().catch(e => {
267
log.error(`Failed to get configuration object from "${configURL}"`, e);
268
throw e;
269
});
270
if (!resp.success) {
271
log.error(
272
`Received HTTP response code ${
273
resp.status
274
} from configuration object request`
275
);
276
if (resp.body) {
277
log.debug("Got error response", resp.body);
278
}
279
throw new Error(
280
`HTTP status ${resp.status} from configuration object request`
281
);
282
}
283
284
log.debug("Got successful configuration response", resp.body);
285
try {
286
// Update the prefs directly specified by the config.
287
let config = JSON.parse(resp.body);
288
let authServerBase = config.auth_server_base_url;
289
if (!authServerBase.endsWith("/v1")) {
290
authServerBase += "/v1";
291
}
292
Services.prefs.setCharPref(
293
"identity.fxaccounts.auth.uri",
294
authServerBase
295
);
296
Services.prefs.setCharPref(
297
"identity.fxaccounts.remote.oauth.uri",
298
config.oauth_server_base_url + "/v1"
299
);
300
// At the time of landing this, our servers didn't yet answer with pairing_server_base_uri.
301
// Remove this condition check once Firefox 68 is stable.
302
if (config.pairing_server_base_uri) {
303
Services.prefs.setCharPref(
304
"identity.fxaccounts.remote.pairing.uri",
305
config.pairing_server_base_uri
306
);
307
}
308
Services.prefs.setCharPref(
309
"identity.fxaccounts.remote.profile.uri",
310
config.profile_server_base_url + "/v1"
311
);
312
Services.prefs.setCharPref(
313
"identity.sync.tokenserver.uri",
314
config.sync_tokenserver_base_url + "/1.0/sync/1.5"
315
);
316
Services.prefs.setCharPref("identity.fxaccounts.remote.root", rootURL);
317
318
// Ensure the webchannel is pointed at the correct uri
319
EnsureFxAccountsWebChannel();
320
} catch (e) {
321
log.error(
322
"Failed to initialize configuration preferences from autoconfig object",
323
e
324
);
325
throw e;
326
}
327
},
328
329
// For test purposes, returns a Promise.
330
getSignedInUser() {
331
return fxAccounts.getSignedInUser();
332
},
333
};