summaryrefslogtreecommitdiffstats
path: root/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs')
-rw-r--r--services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs209
1 files changed, 209 insertions, 0 deletions
diff --git a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs
new file mode 100644
index 0000000000..81c0fd578a
--- /dev/null
+++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs
@@ -0,0 +1,209 @@
+/* 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 file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { Log } from "resource://gre/modules/Log.sys.mjs";
+import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
+
+import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";
+
+const fxAccounts = getFxAccountsSingleton();
+import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs";
+import { FxAccountsConfig } from "resource://gre/modules/FxAccountsConfig.sys.mjs";
+import { Logger } from "resource://tps/logger.sys.mjs";
+
+/**
+ * Helper object for Firefox Accounts authentication
+ */
+export var Authentication = {
+ /**
+ * Check if an user has been logged in
+ */
+ async isLoggedIn() {
+ return !!(await this.getSignedInUser());
+ },
+
+ async isReady() {
+ let user = await this.getSignedInUser();
+ return user && user.verified;
+ },
+
+ _getRestmailUsername(user) {
+ const restmailSuffix = "@restmail.net";
+ if (user.toLowerCase().endsWith(restmailSuffix)) {
+ return user.slice(0, -restmailSuffix.length);
+ }
+ return null;
+ },
+
+ async shortWaitForVerification(ms) {
+ let userData = await this.getSignedInUser();
+ let timeoutID;
+ let timeoutPromise = new Promise(resolve => {
+ timeoutID = setTimeout(() => {
+ Logger.logInfo(`Warning: no verification after ${ms}ms.`);
+ resolve();
+ }, ms);
+ });
+ await Promise.race([
+ fxAccounts.whenVerified(userData).finally(() => clearTimeout(timeoutID)),
+ timeoutPromise,
+ ]);
+ userData = await this.getSignedInUser();
+ return userData && userData.verified;
+ },
+
+ async _openVerificationPage(uri) {
+ let mainWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let newtab = mainWindow.gBrowser.addWebTab(uri);
+ let win = mainWindow.gBrowser.getBrowserForTab(newtab);
+ await new Promise(resolve => {
+ win.addEventListener("loadend", resolve, { once: true });
+ });
+ let didVerify = await this.shortWaitForVerification(10000);
+ mainWindow.gBrowser.removeTab(newtab);
+ return didVerify;
+ },
+
+ async _completeVerification(user) {
+ let username = this._getRestmailUsername(user);
+ if (!username) {
+ Logger.logInfo(
+ `Username "${user}" isn't a restmail username so can't complete verification`
+ );
+ return false;
+ }
+ Logger.logInfo("Fetching mail (from restmail) for user " + username);
+ let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(
+ username
+ )}`;
+ let triedAlready = new Set();
+ const tries = 10;
+ const normalWait = 2000;
+ for (let i = 0; i < tries; ++i) {
+ let resp = await fetch(restmailURI);
+ let messages = await resp.json();
+ // Sort so that the most recent emails are first.
+ messages.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt));
+ for (let m of messages) {
+ // We look for a link that has a x-link that we haven't yet tried.
+ if (!m.headers["x-link"] || triedAlready.has(m.headers["x-link"])) {
+ continue;
+ }
+ let confirmLink = m.headers["x-link"];
+ triedAlready.add(confirmLink);
+ Logger.logInfo("Trying confirmation link " + confirmLink);
+ try {
+ if (await this._openVerificationPage(confirmLink)) {
+ return true;
+ }
+ } catch (e) {
+ Logger.logInfo(
+ "Warning: Failed to follow confirmation link: " +
+ Log.exceptionStr(e)
+ );
+ }
+ }
+ if (i === 0) {
+ // first time through after failing we'll do this.
+ await fxAccounts.resendVerificationEmail();
+ }
+ if (await this.shortWaitForVerification(normalWait)) {
+ return true;
+ }
+ }
+ // One last try.
+ return this.shortWaitForVerification(normalWait);
+ },
+
+ async deleteEmail(user) {
+ let username = this._getRestmailUsername(user);
+ if (!username) {
+ Logger.logInfo("Not a restmail username, can't delete");
+ return false;
+ }
+ Logger.logInfo("Deleting mail (from restmail) for user " + username);
+ let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent(
+ username
+ )}`;
+ try {
+ // Clean up after ourselves.
+ let deleteResult = await fetch(restmailURI, { method: "DELETE" });
+ if (!deleteResult.ok) {
+ Logger.logInfo(
+ `Warning: Got non-success status ${deleteResult.status} when deleting emails`
+ );
+ return false;
+ }
+ } catch (e) {
+ Logger.logInfo(
+ "Warning: Failed to delete old emails: " + Log.exceptionStr(e)
+ );
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Wrapper to retrieve the currently signed in user
+ *
+ * @returns Information about the currently signed in user
+ */
+ async getSignedInUser() {
+ try {
+ return await fxAccounts.getSignedInUser();
+ } catch (error) {
+ Logger.logError(
+ "getSignedInUser() failed with: " + JSON.stringify(error)
+ );
+ throw error;
+ }
+ },
+
+ /**
+ * Wrapper to synchronize the login of a user
+ *
+ * @param account
+ * Account information of the user to login
+ * @param account.username
+ * The username for the account (utf8)
+ * @param account.password
+ * The user's password
+ */
+ async signIn(account) {
+ Logger.AssertTrue(account.username, "Username has been found");
+ Logger.AssertTrue(account.password, "Password has been found");
+
+ Logger.logInfo("Login user: " + account.username);
+
+ try {
+ // Required here since we don't go through the real login page
+ await FxAccountsConfig.ensureConfigured();
+
+ let client = new FxAccountsClient();
+ let credentials = await client.signIn(
+ account.username,
+ account.password,
+ true
+ );
+ await fxAccounts._internal.setSignedInUser(credentials);
+ if (!credentials.verified) {
+ await this._completeVerification(account.username);
+ }
+
+ return true;
+ } catch (error) {
+ throw new Error("signIn() failed with: " + error.message);
+ }
+ },
+
+ /**
+ * Sign out of Firefox Accounts.
+ */
+ async signOut() {
+ if (await Authentication.isLoggedIn()) {
+ // Note: This will clean up the device ID.
+ await fxAccounts.signOut();
+ }
+ },
+};