summaryrefslogtreecommitdiffstats
path: root/toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs')
-rw-r--r--toolkit/components/satchel/integrations/WebAuthnFeature.sys.mjs129
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();