diff options
Diffstat (limited to 'toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs')
-rw-r--r-- | toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs b/toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs new file mode 100644 index 0000000000..b5805d5a93 --- /dev/null +++ b/toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs @@ -0,0 +1,129 @@ +/* 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 { + LoginHelper, + ParentAutocompleteOption, +} from "resource://gre/modules/LoginHelper.sys.mjs"; + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "webauthnService", + "@mozilla.org/webauthn/service;1", + "nsIWebAuthnService" +); + +ChromeUtils.defineLazyGetter( + lazy, + "strings", + () => new Localization(["browser/webauthnDialog.ftl"]) +); +ChromeUtils.defineLazyGetter(lazy, "log", () => + LoginHelper.createLogger("WebAuthnFeature") +); + +if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) { + throw new Error( + "PasskeySupport.sys.mjs should only run in the parent process" + ); +} + +class WebAuthnSupport { + async *#getAutocompleteItemsAsync(browsingContextId, formOrigin) { + let transactionId = lazy.webauthnService.hasPendingConditionalGet( + browsingContextId, + formOrigin + ); + if (transactionId == 0) { + // No pending transaction + return; + } + let credentials = lazy.webauthnService.getAutoFillEntries(transactionId); + + let labels = credentials.map(x => ({ + id: "webauthn-specific-passkey-label", + args: { domain: x.rpId }, + })); + if (!credentials.length) { + labels.push({ id: "webauthn-a-passkey-label" }); + } else { + labels.push({ id: "webauthn-another-passkey-label" }); + } + const formattedLabels = await lazy.strings.formatValues(labels); + for (let i = 0; i < credentials.length; i++) { + yield new ParentAutocompleteOption( + "chrome://browser/content/logos/passkey.svg", + credentials[i].userName, + formattedLabels[i], + "PasswordManager:promptForAuthenticator", + { + selection: { + transactionId, + credentialId: credentials[i].credentialId, + }, + } + ); + } + // `getAutoFillEntries` may not return all of the credentials on the device + // (in particular it will not include credentials with a protection policy + // that forbids silent discovery), so we include a catch-all entry in the + // list. If the user selects this entry, the WebAuthn transaction will + // proceed using the modal UI. + yield new ParentAutocompleteOption( + "chrome://browser/content/logos/passkey.svg", + formattedLabels[formattedLabels.length - 1], + "", + "PasswordManager:promptForAuthenticator", + { + selection: { + transactionId, + }, + } + ); + } + + /** + * + * @param {int} browsingContextId the browsing context ID associated with this request + * @param {string} formOrigin + * @param {string} scenarioName can be "SignUpFormScenario" or undefined + * @param {string} isWebAuthn indicates whether "webauthn" was included in the input's autocomplete value + * @returns {ParentAutocompleteOption} the optional WebAuthn autocomplete item + */ + async autocompleteItemsAsync( + browsingContextId, + formOrigin, + scenarioName, + isWebAuthn + ) { + const result = []; + if (scenarioName !== "SignUpFormScenario" || isWebAuthn) { + for await (const item of this.#getAutocompleteItemsAsync( + browsingContextId, + formOrigin + )) { + result.push(item); + } + } + return result; + } + + async promptForAuthenticator(browser, selection) { + lazy.log.info("Prompting to authenticate with relying party."); + if (selection.credentialId) { + lazy.webauthnService.selectAutoFillEntry( + selection.transactionId, + selection.credentialId + ); + } else { + lazy.webauthnService.resumeConditionalGet(selection.transactionId); + } + } +} + +export const WebAuthnFeature = new WebAuthnSupport(); |