diff options
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.mjs | 209 |
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(); + } + }, +}; |