@@ -0,0 +1 @@
|
||||
{"version":3,"file":"assets/635.js","mappings":"oOAKCA,WAAkCC,QAAU,KAC1C,MAAiB,I,ysOCHb,SAASC,EAAKC,IACnBC,EAAAA,EAAAA,KAAeD,EACjB,C","sources":["webpack:///./src/platform/services/sdk/wasm.ts","webpack:///../../node_modules/@bitwarden/sdk-internal/index.js"],"sourcesContent":["import * as sdk from \"@bitwarden/sdk-internal\";\nimport * as wasm from \"@bitwarden/sdk-internal/bitwarden_wasm_internal_bg.wasm\";\n\nimport { GlobalWithWasmInit } from \"./browser-sdk-load.service\";\n\n(globalThis as GlobalWithWasmInit).initSdk = () => {\n (sdk as any).init(wasm);\n};\n","import { __wbg_set_wasm } from \"./bitwarden_wasm_internal_bg.js\";\n\n// In order to support a fallback strategy for web we need to conditionally load the wasm file\nexport function init(wasm) {\n __wbg_set_wasm(wasm);\n}\n\nexport * from \"./bitwarden_wasm_internal_bg.js\";\n"],"names":["globalThis","initSdk","init","wasm","__wbg_set_wasm"],"ignoreList":[],"sourceRoot":""}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* @license
|
||||
Papa Parse
|
||||
v5.5.3
|
||||
https://github.com/mholt/PapaParse
|
||||
License: MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
||||
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
||||
<http://stuartk.com/jszip>
|
||||
|
||||
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||||
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||||
|
||||
JSZip uses the library pako released under the MIT license :
|
||||
https://github.com/nodeca/pako/blob/main/LICENSE
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.Builder
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.Index
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.Pipeline
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.Set
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.TokenSet
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.Vector
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.stemmer
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
* Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.stopWordFilter
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.tokenizer
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.trimmer
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*!
|
||||
* lunr.utils
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/**
|
||||
* lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
|
||||
* Copyright (C) 2020 Oliver Nightingale
|
||||
* @license MIT
|
||||
*/
|
||||
@@ -0,0 +1,43 @@
|
||||
@-webkit-keyframes bitwardenfill {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes bitwardenfill {
|
||||
0% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
span[data-bwautofill].com-bitwarden-browser-animated-fill {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.com-bitwarden-browser-animated-fill {
|
||||
animation: bitwardenfill 200ms ease-in-out 0ms 1;
|
||||
-webkit-animation: bitwardenfill 200ms ease-in-out 0ms 1;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.com-bitwarden-browser-animated-fill {
|
||||
animation: none;
|
||||
-webkit-animation: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ./src/autofill/enums/autofill-port.enum.ts
|
||||
const AutofillPort = {
|
||||
InjectedScript: "autofill-injected-script-port",
|
||||
};
|
||||
|
||||
|
||||
;// ./src/autofill/utils/index.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a random string of characters.
|
||||
*
|
||||
* @param length - The length of the random string to generate.
|
||||
*/
|
||||
function generateRandomChars(length) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz";
|
||||
const randomChars = [];
|
||||
const randomBytes = new Uint8Array(length);
|
||||
globalThis.crypto.getRandomValues(randomBytes);
|
||||
for (let byteIndex = 0; byteIndex < randomBytes.length; byteIndex++) {
|
||||
const byte = randomBytes[byteIndex];
|
||||
randomChars.push(chars[byte % chars.length]);
|
||||
}
|
||||
return randomChars.join("");
|
||||
}
|
||||
/**
|
||||
* Polyfills the requestIdleCallback API with a setTimeout fallback.
|
||||
*
|
||||
* @param callback - The callback function to run when the browser is idle.
|
||||
* @param options - The options to pass to the requestIdleCallback function.
|
||||
*/
|
||||
function requestIdleCallbackPolyfill(callback, options) {
|
||||
if ("requestIdleCallback" in globalThis) {
|
||||
return globalThis.requestIdleCallback(() => callback(), options);
|
||||
}
|
||||
return globalThis.setTimeout(() => callback(), 1);
|
||||
}
|
||||
/**
|
||||
* Polyfills the cancelIdleCallback API with a clearTimeout fallback.
|
||||
*
|
||||
* @param id - The ID of the idle callback to cancel.
|
||||
*/
|
||||
function cancelIdleCallbackPolyfill(id) {
|
||||
if ("cancelIdleCallback" in globalThis) {
|
||||
return globalThis.cancelIdleCallback(id);
|
||||
}
|
||||
return globalThis.clearTimeout(id);
|
||||
}
|
||||
/**
|
||||
* Generates a random string of characters that formatted as a custom element name.
|
||||
*/
|
||||
function generateRandomCustomElementName() {
|
||||
const length = Math.floor(Math.random() * 5) + 8; // Between 8 and 12 characters
|
||||
const numHyphens = Math.min(Math.max(Math.floor(Math.random() * 4), 1), length - 1); // At least 1, maximum of 3 hyphens
|
||||
const hyphenIndices = [];
|
||||
while (hyphenIndices.length < numHyphens) {
|
||||
const index = Math.floor(Math.random() * (length - 1)) + 1;
|
||||
if (!hyphenIndices.includes(index)) {
|
||||
hyphenIndices.push(index);
|
||||
}
|
||||
}
|
||||
hyphenIndices.sort((a, b) => a - b);
|
||||
let randomString = "";
|
||||
let prevIndex = 0;
|
||||
for (let index = 0; index < hyphenIndices.length; index++) {
|
||||
const hyphenIndex = hyphenIndices[index];
|
||||
randomString = randomString + generateRandomChars(hyphenIndex - prevIndex) + "-";
|
||||
prevIndex = hyphenIndex;
|
||||
}
|
||||
randomString += generateRandomChars(length - prevIndex);
|
||||
return randomString;
|
||||
}
|
||||
/**
|
||||
* Builds a DOM element from an SVG string.
|
||||
*
|
||||
* @param svgString - The SVG string to build the DOM element from.
|
||||
* @param ariaHidden - Determines whether the SVG should be hidden from screen readers.
|
||||
*/
|
||||
function buildSvgDomElement(svgString, ariaHidden = true) {
|
||||
const domParser = new DOMParser();
|
||||
const svgDom = domParser.parseFromString(svgString, "image/svg+xml");
|
||||
const domElement = svgDom.documentElement;
|
||||
domElement.setAttribute("aria-hidden", `${ariaHidden}`);
|
||||
return domElement;
|
||||
}
|
||||
/**
|
||||
* Sends a message to the extension.
|
||||
*
|
||||
* @param command - The command to send.
|
||||
* @param options - The options to send with the command.
|
||||
*/
|
||||
function sendExtensionMessage(command_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (command, options = {}) {
|
||||
if (typeof browser !== "undefined" &&
|
||||
typeof browser.runtime !== "undefined" &&
|
||||
typeof browser.runtime.sendMessage !== "undefined") {
|
||||
return browser.runtime.sendMessage(Object.assign({ command }, options));
|
||||
}
|
||||
return new Promise((resolve) => chrome.runtime.sendMessage(Object.assign({ command }, options), (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
resolve(null);
|
||||
}
|
||||
resolve(response);
|
||||
}));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets CSS styles on an element.
|
||||
*
|
||||
* @param element - The element to set the styles on.
|
||||
* @param styles - The styles to set on the element.
|
||||
* @param priority - Determines whether the styles should be set as important.
|
||||
*/
|
||||
function setElementStyles(element, styles, priority) {
|
||||
if (!element || !styles || !Object.keys(styles).length) {
|
||||
return;
|
||||
}
|
||||
for (const styleProperty in styles) {
|
||||
element.style.setProperty(styleProperty.replace(/([a-z])([A-Z])/g, "$1-$2"), // Convert camelCase to kebab-case
|
||||
styles[styleProperty], priority ? "important" : undefined);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets up a long-lived connection with the extension background
|
||||
* and triggers an onDisconnect event if the extension context
|
||||
* is invalidated.
|
||||
*
|
||||
* @param callback - Callback export function to run when the extension disconnects
|
||||
*/
|
||||
function setupExtensionDisconnectAction(callback) {
|
||||
const port = chrome.runtime.connect({ name: AutofillPort.InjectedScript });
|
||||
const onDisconnectCallback = (disconnectedPort) => {
|
||||
callback(disconnectedPort);
|
||||
port.onDisconnect.removeListener(onDisconnectCallback);
|
||||
};
|
||||
port.onDisconnect.addListener(onDisconnectCallback);
|
||||
}
|
||||
/**
|
||||
* Handles setup of the extension disconnect action for the autofill init class
|
||||
* in both instances where the overlay might or might not be initialized.
|
||||
*
|
||||
* @param windowContext - The global window context
|
||||
*/
|
||||
function setupAutofillInitDisconnectAction(windowContext) {
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
return;
|
||||
}
|
||||
const onDisconnectCallback = () => {
|
||||
windowContext.bitwardenAutofillInit.destroy();
|
||||
delete windowContext.bitwardenAutofillInit;
|
||||
};
|
||||
setupExtensionDisconnectAction(onDisconnectCallback);
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a fillable form field.
|
||||
* This is determined by whether the element is a form field and not a span.
|
||||
*
|
||||
* @param formFieldElement - The form field element to check.
|
||||
*/
|
||||
function elementIsFillableFormField(formFieldElement) {
|
||||
return !elementIsSpanElement(formFieldElement);
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is an instance of a specific tag name.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
* @param tagName - The tag name to check against.
|
||||
*/
|
||||
function elementIsInstanceOf(element, tagName) {
|
||||
return nodeIsElement(element) && element.tagName.toLowerCase() === tagName;
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a span element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsSpanElement(element) {
|
||||
return elementIsInstanceOf(element, "span");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is an input field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsInputElement(element) {
|
||||
return elementIsInstanceOf(element, "input");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a select field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsSelectElement(element) {
|
||||
return elementIsInstanceOf(element, "select");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a textarea field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsTextAreaElement(element) {
|
||||
return elementIsInstanceOf(element, "textarea");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a form element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsFormElement(element) {
|
||||
return elementIsInstanceOf(element, "form");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a label element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsLabelElement(element) {
|
||||
return elementIsInstanceOf(element, "label");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a description details `dd` element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsDescriptionDetailsElement(element) {
|
||||
return elementIsInstanceOf(element, "dd");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a description term `dt` element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsDescriptionTermElement(element) {
|
||||
return elementIsInstanceOf(element, "dt");
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is an HTML element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsElement(node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
return (node === null || node === void 0 ? void 0 : node.nodeType) === Node.ELEMENT_NODE;
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is an input element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsInputElement(node) {
|
||||
return nodeIsElement(node) && elementIsInputElement(node);
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is a form element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsFormElement(node) {
|
||||
return nodeIsElement(node) && elementIsFormElement(node);
|
||||
}
|
||||
function nodeIsTypeSubmitElement(node) {
|
||||
return nodeIsElement(node) && getPropertyOrAttribute(node, "type") === "submit";
|
||||
}
|
||||
function nodeIsButtonElement(node) {
|
||||
return (nodeIsElement(node) &&
|
||||
(elementIsInstanceOf(node, "button") ||
|
||||
getPropertyOrAttribute(node, "type") === "button"));
|
||||
}
|
||||
function nodeIsAnchorElement(node) {
|
||||
return nodeIsElement(node) && elementIsInstanceOf(node, "a");
|
||||
}
|
||||
/**
|
||||
* Returns a boolean representing the attribute value of an element.
|
||||
*
|
||||
* @param element
|
||||
* @param attributeName
|
||||
* @param checkString
|
||||
*/
|
||||
function getAttributeBoolean(element, attributeName, checkString = false) {
|
||||
if (checkString) {
|
||||
return getPropertyOrAttribute(element, attributeName) === "true";
|
||||
}
|
||||
return Boolean(getPropertyOrAttribute(element, attributeName));
|
||||
}
|
||||
/**
|
||||
* Get the value of a property or attribute from a FormFieldElement.
|
||||
*
|
||||
* @param element
|
||||
* @param attributeName
|
||||
*/
|
||||
function getPropertyOrAttribute(element, attributeName) {
|
||||
if (attributeName in element) {
|
||||
return element[attributeName];
|
||||
}
|
||||
return element.getAttribute(attributeName);
|
||||
}
|
||||
/**
|
||||
* Throttles a callback function to run at most once every `limit` milliseconds.
|
||||
*
|
||||
* @param callback - The callback function to throttle.
|
||||
* @param limit - The time in milliseconds to throttle the callback.
|
||||
*/
|
||||
function throttle(callback, limit) {
|
||||
let waitingDelay = false;
|
||||
return function (...args) {
|
||||
if (!waitingDelay) {
|
||||
callback.apply(this, args);
|
||||
waitingDelay = true;
|
||||
globalThis.setTimeout(() => (waitingDelay = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Debounces a callback function to run after a delay of `delay` milliseconds.
|
||||
*
|
||||
* @param callback - The callback function to debounce.
|
||||
* @param delay - The time in milliseconds to debounce the callback.
|
||||
* @param immediate - Determines whether the callback should run immediately.
|
||||
*/
|
||||
function debounce(callback, delay, immediate) {
|
||||
let timeout;
|
||||
return function (...args) {
|
||||
const callImmediately = !!immediate && !timeout;
|
||||
if (timeout) {
|
||||
globalThis.clearTimeout(timeout);
|
||||
}
|
||||
timeout = globalThis.setTimeout(() => {
|
||||
timeout = null;
|
||||
if (!callImmediately) {
|
||||
callback.apply(this, args);
|
||||
}
|
||||
}, delay);
|
||||
if (callImmediately) {
|
||||
callback.apply(this, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Gathers and normalizes keywords from a potential submit button element. Used
|
||||
* to verify if the element submits a login or change password form.
|
||||
*
|
||||
* @param element - The element to gather keywords from.
|
||||
*/
|
||||
function getSubmitButtonKeywordsSet(element) {
|
||||
const keywords = [
|
||||
element.textContent,
|
||||
element.getAttribute("type"),
|
||||
element.getAttribute("value"),
|
||||
element.getAttribute("aria-label"),
|
||||
element.getAttribute("aria-labelledby"),
|
||||
element.getAttribute("aria-describedby"),
|
||||
element.getAttribute("title"),
|
||||
element.getAttribute("id"),
|
||||
element.getAttribute("name"),
|
||||
element.getAttribute("class"),
|
||||
];
|
||||
const keywordsSet = new Set();
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (typeof keywords[i] === "string") {
|
||||
// Iterate over all keywords metadata and split them by non-letter characters.
|
||||
// This ensures we check against individual words and not the entire string.
|
||||
keywords[i]
|
||||
.toLowerCase()
|
||||
.replace(/[-\s]/g, "")
|
||||
.split(/[^\p{L}]+/gu)
|
||||
.forEach((keyword) => {
|
||||
if (keyword) {
|
||||
keywordsSet.add(keyword);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return keywordsSet;
|
||||
}
|
||||
/**
|
||||
* Generates the origin and subdomain match patterns for the URL.
|
||||
*
|
||||
* @param url - The URL of the tab
|
||||
*/
|
||||
function generateDomainMatchPatterns(url) {
|
||||
try {
|
||||
const extensionUrlPattern = /^(chrome|chrome-extension|moz-extension|safari-web-extension):\/\/\/?/;
|
||||
if (extensionUrlPattern.test(url)) {
|
||||
return [];
|
||||
}
|
||||
// Add protocol to URL if it is missing to allow for parsing the hostname correctly
|
||||
const urlPattern = /^(https?|file):\/\/\/?/;
|
||||
if (!urlPattern.test(url)) {
|
||||
url = `https://${url}`;
|
||||
}
|
||||
let protocolGlob = "*://";
|
||||
if (url.startsWith("file:///")) {
|
||||
protocolGlob = "*:///"; // File URLs require three slashes to be a valid match pattern
|
||||
}
|
||||
const parsedUrl = new URL(url);
|
||||
const originMatchPattern = `${protocolGlob}${parsedUrl.hostname}/*`;
|
||||
const splitHost = parsedUrl.hostname.split(".");
|
||||
const domain = splitHost.slice(-2).join(".");
|
||||
const subDomainMatchPattern = `${protocolGlob}*.${domain}/*`;
|
||||
return [originMatchPattern, subDomainMatchPattern];
|
||||
}
|
||||
catch (_a) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines if the status code of the web response is invalid. An invalid status code is
|
||||
* any status code that is not in the 200-299 range.
|
||||
*
|
||||
* @param statusCode - The status code of the web response
|
||||
*/
|
||||
function isInvalidResponseStatusCode(statusCode) {
|
||||
return statusCode < 200 || statusCode >= 300;
|
||||
}
|
||||
/**
|
||||
* Determines if the current context is within a sandboxed iframe.
|
||||
*/
|
||||
function currentlyInSandboxedIframe() {
|
||||
var _a, _b;
|
||||
if (String(self.origin).toLowerCase() === "null" || globalThis.location.hostname === "") {
|
||||
return true;
|
||||
}
|
||||
const sandbox = (_b = (_a = globalThis.frameElement) === null || _a === void 0 ? void 0 : _a.getAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, "sandbox");
|
||||
// No frameElement or sandbox attribute means not sandboxed
|
||||
if (sandbox === null || sandbox === undefined) {
|
||||
return false;
|
||||
}
|
||||
// An empty string means fully sandboxed
|
||||
if (sandbox === "") {
|
||||
return true;
|
||||
}
|
||||
const tokens = new Set(sandbox.toLowerCase().split(" "));
|
||||
return !["allow-scripts", "allow-same-origin"].every((token) => tokens.has(token));
|
||||
}
|
||||
/**
|
||||
* This object allows us to map a special character to a key name. The key name is used
|
||||
* in gathering the i18n translation of the written version of the special character.
|
||||
*/
|
||||
const specialCharacterToKeyMap = {
|
||||
" ": "spaceCharacterDescriptor",
|
||||
"~": "tildeCharacterDescriptor",
|
||||
"`": "backtickCharacterDescriptor",
|
||||
"!": "exclamationCharacterDescriptor",
|
||||
"@": "atSignCharacterDescriptor",
|
||||
"#": "hashSignCharacterDescriptor",
|
||||
$: "dollarSignCharacterDescriptor",
|
||||
"%": "percentSignCharacterDescriptor",
|
||||
"^": "caretCharacterDescriptor",
|
||||
"&": "ampersandCharacterDescriptor",
|
||||
"*": "asteriskCharacterDescriptor",
|
||||
"(": "parenLeftCharacterDescriptor",
|
||||
")": "parenRightCharacterDescriptor",
|
||||
"-": "hyphenCharacterDescriptor",
|
||||
_: "underscoreCharacterDescriptor",
|
||||
"+": "plusCharacterDescriptor",
|
||||
"=": "equalsCharacterDescriptor",
|
||||
"{": "braceLeftCharacterDescriptor",
|
||||
"}": "braceRightCharacterDescriptor",
|
||||
"[": "bracketLeftCharacterDescriptor",
|
||||
"]": "bracketRightCharacterDescriptor",
|
||||
"|": "pipeCharacterDescriptor",
|
||||
"\\": "backSlashCharacterDescriptor",
|
||||
":": "colonCharacterDescriptor",
|
||||
";": "semicolonCharacterDescriptor",
|
||||
'"': "doubleQuoteCharacterDescriptor",
|
||||
"'": "singleQuoteCharacterDescriptor",
|
||||
"<": "lessThanCharacterDescriptor",
|
||||
">": "greaterThanCharacterDescriptor",
|
||||
",": "commaCharacterDescriptor",
|
||||
".": "periodCharacterDescriptor",
|
||||
"?": "questionCharacterDescriptor",
|
||||
"/": "forwardSlashCharacterDescriptor",
|
||||
};
|
||||
/**
|
||||
* Determines if the current rect values are not all 0.
|
||||
*/
|
||||
function rectHasSize(rect) {
|
||||
if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Checks if all the values corresponding to the specified keys in an object are null.
|
||||
* If no keys are specified, checks all keys in the object.
|
||||
*
|
||||
* @param obj - The object to check.
|
||||
* @param keys - An optional array of keys to check in the object. Defaults to all keys.
|
||||
* @returns Returns true if all values for the specified keys (or all keys if none are provided) are null; otherwise, false.
|
||||
*/
|
||||
function areKeyValuesNull(obj, keys) {
|
||||
const keysToCheck = keys && keys.length > 0 ? keys : Object.keys(obj);
|
||||
return keysToCheck.every((key) => obj[key] == null);
|
||||
}
|
||||
|
||||
;// ./src/autofill/content/autofiller.ts
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", loadAutofiller);
|
||||
}
|
||||
else {
|
||||
loadAutofiller();
|
||||
}
|
||||
function loadAutofiller() {
|
||||
let pageHref = null;
|
||||
let filledThisHref = false;
|
||||
let delayFillTimeout;
|
||||
let doFillInterval;
|
||||
const handleExtensionDisconnect = () => {
|
||||
clearDoFillInterval();
|
||||
clearDelayFillTimeout();
|
||||
};
|
||||
const handleExtensionMessage = (message) => {
|
||||
if (message.command === "fillForm" && pageHref === message.url) {
|
||||
filledThisHref = true;
|
||||
}
|
||||
};
|
||||
setupExtensionEventListeners();
|
||||
triggerUserFillOnLoad();
|
||||
function triggerUserFillOnLoad() {
|
||||
clearDoFillInterval();
|
||||
doFillInterval = setInterval(() => doFillIfNeeded(), 500);
|
||||
}
|
||||
function doFillIfNeeded(force = false) {
|
||||
if (force || pageHref !== window.location.href) {
|
||||
if (!force) {
|
||||
// Some websites are slow and rendering all page content. Try to fill again later
|
||||
// if we haven't already.
|
||||
filledThisHref = false;
|
||||
clearDelayFillTimeout();
|
||||
delayFillTimeout = window.setTimeout(() => {
|
||||
if (!filledThisHref) {
|
||||
doFillIfNeeded(true);
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
pageHref = window.location.href;
|
||||
const msg = {
|
||||
command: "bgCollectPageDetails",
|
||||
sender: "autofiller",
|
||||
};
|
||||
void chrome.runtime.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
function clearDoFillInterval() {
|
||||
if (doFillInterval) {
|
||||
window.clearInterval(doFillInterval);
|
||||
}
|
||||
}
|
||||
function clearDelayFillTimeout() {
|
||||
if (delayFillTimeout) {
|
||||
window.clearTimeout(delayFillTimeout);
|
||||
}
|
||||
}
|
||||
function setupExtensionEventListeners() {
|
||||
setupExtensionDisconnectAction(handleExtensionDisconnect);
|
||||
chrome.runtime.onMessage.addListener(handleExtensionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,177 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
|
||||
const VaultMessages = {
|
||||
HasBwInstalled: "hasBwInstalled",
|
||||
checkBwInstalled: "checkIfBWExtensionInstalled",
|
||||
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
|
||||
OpenAtRiskPasswords: "openAtRiskPasswords",
|
||||
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
|
||||
PopupOpened: "popupOpened",
|
||||
};
|
||||
|
||||
|
||||
;// ./src/autofill/content/content-message-handler.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handlers for window messages from the content script.
|
||||
* NOTE: These handlers should be above the event listener to ensure they are defined before being used.
|
||||
*/
|
||||
const windowMessageHandlers = {
|
||||
authResult: ({ data, referrer }) => handleAuthResultMessage(data, referrer),
|
||||
webAuthnResult: ({ data, referrer }) => handleWebAuthnResultMessage(data, referrer),
|
||||
[VaultMessages.checkBwInstalled]: () => handleExtensionInstallCheck(),
|
||||
duoResult: ({ data, referrer }) => handleDuoResultMessage(data, referrer),
|
||||
[VaultMessages.OpenAtRiskPasswords]: () => handleOpenAtRiskPasswordsMessage(),
|
||||
[VaultMessages.OpenBrowserExtensionToUrl]: ({ data }) => handleOpenBrowserExtensionToUrlMessage(data),
|
||||
};
|
||||
/**
|
||||
* IMPORTANT: Safari seems to have a bug where it doesn't properly handle
|
||||
* window message events from content scripts when the listener these events
|
||||
* is registered within a class. This is why these listeners are registered
|
||||
* at the top level of this file.
|
||||
*/
|
||||
window.addEventListener("message", handleWindowMessageEvent, false);
|
||||
chrome.runtime.onMessage.addListener(handleExtensionMessage);
|
||||
setupExtensionDisconnectAction(() => {
|
||||
window.removeEventListener("message", handleWindowMessageEvent);
|
||||
chrome.runtime.onMessage.removeListener(handleExtensionMessage);
|
||||
});
|
||||
/**
|
||||
* Handles the post to the web vault showing the extension has been installed
|
||||
*/
|
||||
function handleExtensionInstallCheck() {
|
||||
window.postMessage({ command: VaultMessages.HasBwInstalled });
|
||||
}
|
||||
/**
|
||||
* Handles the auth result message from the window.
|
||||
*
|
||||
* @param data - Data from the window message
|
||||
* @param referrer - The referrer of the window
|
||||
*/
|
||||
function handleAuthResultMessage(data, referrer) {
|
||||
const { command, lastpass, code, state } = data;
|
||||
sendExtensionRuntimeMessage({ command, code, state, lastpass, referrer });
|
||||
}
|
||||
/**
|
||||
* Handles the Duo 2FA result message from the window.
|
||||
*
|
||||
* @param data - Data from the window message
|
||||
* @param referrer - The referrer of the window
|
||||
*/
|
||||
function handleDuoResultMessage(data, referrer) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { command, code, state } = data;
|
||||
sendExtensionRuntimeMessage({ command, code, state, referrer });
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handles the webauthn result message from the window.
|
||||
*
|
||||
* @param data - Data from the window message
|
||||
* @param referrer - The referrer of the window
|
||||
*/
|
||||
function handleWebAuthnResultMessage(data, referrer) {
|
||||
const { command, remember } = data;
|
||||
sendExtensionRuntimeMessage({ command, data: data.data, remember, referrer });
|
||||
}
|
||||
/** @deprecated use {@link handleOpenBrowserExtensionToUrlMessage} */
|
||||
function handleOpenAtRiskPasswordsMessage() {
|
||||
sendExtensionRuntimeMessage({ command: VaultMessages.OpenAtRiskPasswords });
|
||||
}
|
||||
function handleOpenBrowserExtensionToUrlMessage({ url }) {
|
||||
sendExtensionRuntimeMessage({ command: VaultMessages.OpenBrowserExtensionToUrl, url });
|
||||
}
|
||||
/**
|
||||
* Handles window message events, validating source and extracting referrer for security.
|
||||
*
|
||||
* @param event - The window message event
|
||||
*/
|
||||
function handleWindowMessageEvent(event) {
|
||||
const { source, data, origin } = event;
|
||||
if (source !== window || !(data === null || data === void 0 ? void 0 : data.command)) {
|
||||
return;
|
||||
}
|
||||
// Extract hostname from event.origin for secure referrer validation in background script
|
||||
let referrer;
|
||||
// Sandboxed iframe or opaque origin support
|
||||
if (origin === "null") {
|
||||
referrer = "null";
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const originUrl = new URL(origin);
|
||||
referrer = originUrl.hostname;
|
||||
}
|
||||
catch (_a) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const handler = windowMessageHandlers[data.command];
|
||||
if (handler) {
|
||||
handler({ data, referrer });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Commands to forward from this script to the extension background.
|
||||
*/
|
||||
const forwardCommands = new Set([
|
||||
"bgUnlockPopoutOpened",
|
||||
"addToLockedVaultPendingNotifications",
|
||||
"unlockCompleted",
|
||||
"addedCipher",
|
||||
]);
|
||||
/**
|
||||
* Handles messages from the extension. Currently, this is
|
||||
* used to forward messages from the background context to
|
||||
* other scripts within the extension.
|
||||
*
|
||||
* @param message - The message from the extension
|
||||
*/
|
||||
function handleExtensionMessage(message) {
|
||||
if (forwardCommands.has(message.command)) {
|
||||
sendExtensionRuntimeMessage(message);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sends a message to the extension runtime, and ignores
|
||||
* any potential promises that should be handled using
|
||||
* the `void` operator.
|
||||
*
|
||||
* @param message - The message to send to the extension runtime
|
||||
*/
|
||||
function sendExtensionRuntimeMessage(message) {
|
||||
void chrome.runtime.sendMessage(message);
|
||||
}
|
||||
/**
|
||||
* Duplicate implementation of the same named method within `apps/browser/src/autofill/utils/index.ts`.
|
||||
* This is done due to some strange observed compilation behavior present when importing the method from
|
||||
* the utils file.
|
||||
*
|
||||
* TODO: Investigate why webpack tree shaking is not removing other methods when importing from the utils file.
|
||||
* Possible cause can be seen below:
|
||||
* @see https://stackoverflow.com/questions/71679366/webpack5-does-not-seem-to-tree-shake-unused-exports
|
||||
*
|
||||
* @param callback - Callback function to run when the extension disconnects
|
||||
*/
|
||||
function setupExtensionDisconnectAction(callback) {
|
||||
const port = chrome.runtime.connect({ name: "autofill-injected-script-port" });
|
||||
const onDisconnectCallback = (disconnectedPort) => {
|
||||
callback(disconnectedPort);
|
||||
port.onDisconnect.removeListener(onDisconnectCallback);
|
||||
};
|
||||
port.onDisconnect.addListener(onDisconnectCallback);
|
||||
}
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,68 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
const inputTags = ["input", "textarea", "select"];
|
||||
const labelTags = ["label", "span"];
|
||||
const attributeKeys = ["id", "name", "label-aria", "placeholder"];
|
||||
const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
|
||||
const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
|
||||
let clickedElement = null;
|
||||
// Find the best attribute to be used as the Name for an element in a custom field.
|
||||
function getClickedElementIdentifier() {
|
||||
var _a, _b;
|
||||
if (clickedElement == null) {
|
||||
return invalidElement;
|
||||
}
|
||||
const clickedTag = clickedElement.nodeName.toLowerCase();
|
||||
let inputElement = null;
|
||||
// Try to identify the input element (which may not be the clicked element)
|
||||
if (labelTags.includes(clickedTag)) {
|
||||
let inputId;
|
||||
if (clickedTag === "label") {
|
||||
inputId = clickedElement.getAttribute("for");
|
||||
}
|
||||
else {
|
||||
inputId = (_a = clickedElement.closest("label")) === null || _a === void 0 ? void 0 : _a.getAttribute("for");
|
||||
}
|
||||
if (inputId) {
|
||||
inputElement = document.getElementById(inputId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
inputElement = clickedElement;
|
||||
}
|
||||
if (inputElement == null || !inputTags.includes(inputElement.nodeName.toLowerCase())) {
|
||||
return invalidElement;
|
||||
}
|
||||
for (const attributeKey of attributeKeys) {
|
||||
const attributeValue = inputElement.getAttribute(attributeKey);
|
||||
const selector = "[" + attributeKey + '="' + attributeValue + '"]';
|
||||
if (!isNullOrEmpty(attributeValue) && ((_b = document.querySelectorAll(selector)) === null || _b === void 0 ? void 0 : _b.length) === 1) {
|
||||
return attributeValue;
|
||||
}
|
||||
}
|
||||
return noUniqueIdentifier;
|
||||
}
|
||||
function isNullOrEmpty(s) {
|
||||
return s == null || s === "";
|
||||
}
|
||||
// We only have access to the element that's been clicked when the context menu is first opened.
|
||||
// Remember it for use later.
|
||||
document.addEventListener("contextmenu", (event) => {
|
||||
clickedElement = event.target;
|
||||
});
|
||||
// Runs when the 'Copy Custom Field Name' context menu item is actually clicked.
|
||||
chrome.runtime.onMessage.addListener((event, _sender, sendResponse) => {
|
||||
if (event.command === "getClickedElement") {
|
||||
const identifier = getClickedElementIdentifier();
|
||||
if (sendResponse) {
|
||||
sendResponse(identifier);
|
||||
}
|
||||
void chrome.runtime.sendMessage({
|
||||
command: "getClickedElementResponse",
|
||||
sender: "contextMenuHandler",
|
||||
identifier: identifier,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,773 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ./src/autofill/enums/autofill-port.enum.ts
|
||||
const autofill_port_enum_AutofillPort = {
|
||||
InjectedScript: "autofill-injected-script-port",
|
||||
};
|
||||
|
||||
|
||||
;// ./src/autofill/utils/index.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a random string of characters.
|
||||
*
|
||||
* @param length - The length of the random string to generate.
|
||||
*/
|
||||
function generateRandomChars(length) {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz";
|
||||
const randomChars = [];
|
||||
const randomBytes = new Uint8Array(length);
|
||||
globalThis.crypto.getRandomValues(randomBytes);
|
||||
for (let byteIndex = 0; byteIndex < randomBytes.length; byteIndex++) {
|
||||
const byte = randomBytes[byteIndex];
|
||||
randomChars.push(chars[byte % chars.length]);
|
||||
}
|
||||
return randomChars.join("");
|
||||
}
|
||||
/**
|
||||
* Polyfills the requestIdleCallback API with a setTimeout fallback.
|
||||
*
|
||||
* @param callback - The callback function to run when the browser is idle.
|
||||
* @param options - The options to pass to the requestIdleCallback function.
|
||||
*/
|
||||
function requestIdleCallbackPolyfill(callback, options) {
|
||||
if ("requestIdleCallback" in globalThis) {
|
||||
return globalThis.requestIdleCallback(() => callback(), options);
|
||||
}
|
||||
return globalThis.setTimeout(() => callback(), 1);
|
||||
}
|
||||
/**
|
||||
* Polyfills the cancelIdleCallback API with a clearTimeout fallback.
|
||||
*
|
||||
* @param id - The ID of the idle callback to cancel.
|
||||
*/
|
||||
function cancelIdleCallbackPolyfill(id) {
|
||||
if ("cancelIdleCallback" in globalThis) {
|
||||
return globalThis.cancelIdleCallback(id);
|
||||
}
|
||||
return globalThis.clearTimeout(id);
|
||||
}
|
||||
/**
|
||||
* Generates a random string of characters that formatted as a custom element name.
|
||||
*/
|
||||
function generateRandomCustomElementName() {
|
||||
const length = Math.floor(Math.random() * 5) + 8; // Between 8 and 12 characters
|
||||
const numHyphens = Math.min(Math.max(Math.floor(Math.random() * 4), 1), length - 1); // At least 1, maximum of 3 hyphens
|
||||
const hyphenIndices = [];
|
||||
while (hyphenIndices.length < numHyphens) {
|
||||
const index = Math.floor(Math.random() * (length - 1)) + 1;
|
||||
if (!hyphenIndices.includes(index)) {
|
||||
hyphenIndices.push(index);
|
||||
}
|
||||
}
|
||||
hyphenIndices.sort((a, b) => a - b);
|
||||
let randomString = "";
|
||||
let prevIndex = 0;
|
||||
for (let index = 0; index < hyphenIndices.length; index++) {
|
||||
const hyphenIndex = hyphenIndices[index];
|
||||
randomString = randomString + generateRandomChars(hyphenIndex - prevIndex) + "-";
|
||||
prevIndex = hyphenIndex;
|
||||
}
|
||||
randomString += generateRandomChars(length - prevIndex);
|
||||
return randomString;
|
||||
}
|
||||
/**
|
||||
* Builds a DOM element from an SVG string.
|
||||
*
|
||||
* @param svgString - The SVG string to build the DOM element from.
|
||||
* @param ariaHidden - Determines whether the SVG should be hidden from screen readers.
|
||||
*/
|
||||
function buildSvgDomElement(svgString, ariaHidden = true) {
|
||||
const domParser = new DOMParser();
|
||||
const svgDom = domParser.parseFromString(svgString, "image/svg+xml");
|
||||
const domElement = svgDom.documentElement;
|
||||
domElement.setAttribute("aria-hidden", `${ariaHidden}`);
|
||||
return domElement;
|
||||
}
|
||||
/**
|
||||
* Sends a message to the extension.
|
||||
*
|
||||
* @param command - The command to send.
|
||||
* @param options - The options to send with the command.
|
||||
*/
|
||||
function sendExtensionMessage(command_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (command, options = {}) {
|
||||
if (typeof browser !== "undefined" &&
|
||||
typeof browser.runtime !== "undefined" &&
|
||||
typeof browser.runtime.sendMessage !== "undefined") {
|
||||
return browser.runtime.sendMessage(Object.assign({ command }, options));
|
||||
}
|
||||
return new Promise((resolve) => chrome.runtime.sendMessage(Object.assign({ command }, options), (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
resolve(null);
|
||||
}
|
||||
resolve(response);
|
||||
}));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets CSS styles on an element.
|
||||
*
|
||||
* @param element - The element to set the styles on.
|
||||
* @param styles - The styles to set on the element.
|
||||
* @param priority - Determines whether the styles should be set as important.
|
||||
*/
|
||||
function setElementStyles(element, styles, priority) {
|
||||
if (!element || !styles || !Object.keys(styles).length) {
|
||||
return;
|
||||
}
|
||||
for (const styleProperty in styles) {
|
||||
element.style.setProperty(styleProperty.replace(/([a-z])([A-Z])/g, "$1-$2"), // Convert camelCase to kebab-case
|
||||
styles[styleProperty], priority ? "important" : undefined);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets up a long-lived connection with the extension background
|
||||
* and triggers an onDisconnect event if the extension context
|
||||
* is invalidated.
|
||||
*
|
||||
* @param callback - Callback export function to run when the extension disconnects
|
||||
*/
|
||||
function setupExtensionDisconnectAction(callback) {
|
||||
const port = chrome.runtime.connect({ name: AutofillPort.InjectedScript });
|
||||
const onDisconnectCallback = (disconnectedPort) => {
|
||||
callback(disconnectedPort);
|
||||
port.onDisconnect.removeListener(onDisconnectCallback);
|
||||
};
|
||||
port.onDisconnect.addListener(onDisconnectCallback);
|
||||
}
|
||||
/**
|
||||
* Handles setup of the extension disconnect action for the autofill init class
|
||||
* in both instances where the overlay might or might not be initialized.
|
||||
*
|
||||
* @param windowContext - The global window context
|
||||
*/
|
||||
function setupAutofillInitDisconnectAction(windowContext) {
|
||||
if (!windowContext.bitwardenAutofillInit) {
|
||||
return;
|
||||
}
|
||||
const onDisconnectCallback = () => {
|
||||
windowContext.bitwardenAutofillInit.destroy();
|
||||
delete windowContext.bitwardenAutofillInit;
|
||||
};
|
||||
setupExtensionDisconnectAction(onDisconnectCallback);
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a fillable form field.
|
||||
* This is determined by whether the element is a form field and not a span.
|
||||
*
|
||||
* @param formFieldElement - The form field element to check.
|
||||
*/
|
||||
function elementIsFillableFormField(formFieldElement) {
|
||||
return !elementIsSpanElement(formFieldElement);
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is an instance of a specific tag name.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
* @param tagName - The tag name to check against.
|
||||
*/
|
||||
function elementIsInstanceOf(element, tagName) {
|
||||
return nodeIsElement(element) && element.tagName.toLowerCase() === tagName;
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a span element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsSpanElement(element) {
|
||||
return elementIsInstanceOf(element, "span");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is an input field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsInputElement(element) {
|
||||
return elementIsInstanceOf(element, "input");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a select field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsSelectElement(element) {
|
||||
return elementIsInstanceOf(element, "select");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a textarea field.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsTextAreaElement(element) {
|
||||
return elementIsInstanceOf(element, "textarea");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a form element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsFormElement(element) {
|
||||
return elementIsInstanceOf(element, "form");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a label element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsLabelElement(element) {
|
||||
return elementIsInstanceOf(element, "label");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a description details `dd` element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsDescriptionDetailsElement(element) {
|
||||
return elementIsInstanceOf(element, "dd");
|
||||
}
|
||||
/**
|
||||
* Identifies whether an element is a description term `dt` element.
|
||||
*
|
||||
* @param element - The element to check.
|
||||
*/
|
||||
function elementIsDescriptionTermElement(element) {
|
||||
return elementIsInstanceOf(element, "dt");
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is an HTML element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsElement(node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
return (node === null || node === void 0 ? void 0 : node.nodeType) === Node.ELEMENT_NODE;
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is an input element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsInputElement(node) {
|
||||
return nodeIsElement(node) && elementIsInputElement(node);
|
||||
}
|
||||
/**
|
||||
* Identifies whether a node is a form element.
|
||||
*
|
||||
* @param node - The node to check.
|
||||
*/
|
||||
function nodeIsFormElement(node) {
|
||||
return nodeIsElement(node) && elementIsFormElement(node);
|
||||
}
|
||||
function nodeIsTypeSubmitElement(node) {
|
||||
return nodeIsElement(node) && getPropertyOrAttribute(node, "type") === "submit";
|
||||
}
|
||||
function nodeIsButtonElement(node) {
|
||||
return (nodeIsElement(node) &&
|
||||
(elementIsInstanceOf(node, "button") ||
|
||||
getPropertyOrAttribute(node, "type") === "button"));
|
||||
}
|
||||
function nodeIsAnchorElement(node) {
|
||||
return nodeIsElement(node) && elementIsInstanceOf(node, "a");
|
||||
}
|
||||
/**
|
||||
* Returns a boolean representing the attribute value of an element.
|
||||
*
|
||||
* @param element
|
||||
* @param attributeName
|
||||
* @param checkString
|
||||
*/
|
||||
function getAttributeBoolean(element, attributeName, checkString = false) {
|
||||
if (checkString) {
|
||||
return getPropertyOrAttribute(element, attributeName) === "true";
|
||||
}
|
||||
return Boolean(getPropertyOrAttribute(element, attributeName));
|
||||
}
|
||||
/**
|
||||
* Get the value of a property or attribute from a FormFieldElement.
|
||||
*
|
||||
* @param element
|
||||
* @param attributeName
|
||||
*/
|
||||
function getPropertyOrAttribute(element, attributeName) {
|
||||
if (attributeName in element) {
|
||||
return element[attributeName];
|
||||
}
|
||||
return element.getAttribute(attributeName);
|
||||
}
|
||||
/**
|
||||
* Throttles a callback function to run at most once every `limit` milliseconds.
|
||||
*
|
||||
* @param callback - The callback function to throttle.
|
||||
* @param limit - The time in milliseconds to throttle the callback.
|
||||
*/
|
||||
function throttle(callback, limit) {
|
||||
let waitingDelay = false;
|
||||
return function (...args) {
|
||||
if (!waitingDelay) {
|
||||
callback.apply(this, args);
|
||||
waitingDelay = true;
|
||||
globalThis.setTimeout(() => (waitingDelay = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Debounces a callback function to run after a delay of `delay` milliseconds.
|
||||
*
|
||||
* @param callback - The callback function to debounce.
|
||||
* @param delay - The time in milliseconds to debounce the callback.
|
||||
* @param immediate - Determines whether the callback should run immediately.
|
||||
*/
|
||||
function debounce(callback, delay, immediate) {
|
||||
let timeout;
|
||||
return function (...args) {
|
||||
const callImmediately = !!immediate && !timeout;
|
||||
if (timeout) {
|
||||
globalThis.clearTimeout(timeout);
|
||||
}
|
||||
timeout = globalThis.setTimeout(() => {
|
||||
timeout = null;
|
||||
if (!callImmediately) {
|
||||
callback.apply(this, args);
|
||||
}
|
||||
}, delay);
|
||||
if (callImmediately) {
|
||||
callback.apply(this, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Gathers and normalizes keywords from a potential submit button element. Used
|
||||
* to verify if the element submits a login or change password form.
|
||||
*
|
||||
* @param element - The element to gather keywords from.
|
||||
*/
|
||||
function getSubmitButtonKeywordsSet(element) {
|
||||
const keywords = [
|
||||
element.textContent,
|
||||
element.getAttribute("type"),
|
||||
element.getAttribute("value"),
|
||||
element.getAttribute("aria-label"),
|
||||
element.getAttribute("aria-labelledby"),
|
||||
element.getAttribute("aria-describedby"),
|
||||
element.getAttribute("title"),
|
||||
element.getAttribute("id"),
|
||||
element.getAttribute("name"),
|
||||
element.getAttribute("class"),
|
||||
];
|
||||
const keywordsSet = new Set();
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (typeof keywords[i] === "string") {
|
||||
// Iterate over all keywords metadata and split them by non-letter characters.
|
||||
// This ensures we check against individual words and not the entire string.
|
||||
keywords[i]
|
||||
.toLowerCase()
|
||||
.replace(/[-\s]/g, "")
|
||||
.split(/[^\p{L}]+/gu)
|
||||
.forEach((keyword) => {
|
||||
if (keyword) {
|
||||
keywordsSet.add(keyword);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return keywordsSet;
|
||||
}
|
||||
/**
|
||||
* Generates the origin and subdomain match patterns for the URL.
|
||||
*
|
||||
* @param url - The URL of the tab
|
||||
*/
|
||||
function generateDomainMatchPatterns(url) {
|
||||
try {
|
||||
const extensionUrlPattern = /^(chrome|chrome-extension|moz-extension|safari-web-extension):\/\/\/?/;
|
||||
if (extensionUrlPattern.test(url)) {
|
||||
return [];
|
||||
}
|
||||
// Add protocol to URL if it is missing to allow for parsing the hostname correctly
|
||||
const urlPattern = /^(https?|file):\/\/\/?/;
|
||||
if (!urlPattern.test(url)) {
|
||||
url = `https://${url}`;
|
||||
}
|
||||
let protocolGlob = "*://";
|
||||
if (url.startsWith("file:///")) {
|
||||
protocolGlob = "*:///"; // File URLs require three slashes to be a valid match pattern
|
||||
}
|
||||
const parsedUrl = new URL(url);
|
||||
const originMatchPattern = `${protocolGlob}${parsedUrl.hostname}/*`;
|
||||
const splitHost = parsedUrl.hostname.split(".");
|
||||
const domain = splitHost.slice(-2).join(".");
|
||||
const subDomainMatchPattern = `${protocolGlob}*.${domain}/*`;
|
||||
return [originMatchPattern, subDomainMatchPattern];
|
||||
}
|
||||
catch (_a) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determines if the status code of the web response is invalid. An invalid status code is
|
||||
* any status code that is not in the 200-299 range.
|
||||
*
|
||||
* @param statusCode - The status code of the web response
|
||||
*/
|
||||
function isInvalidResponseStatusCode(statusCode) {
|
||||
return statusCode < 200 || statusCode >= 300;
|
||||
}
|
||||
/**
|
||||
* Determines if the current context is within a sandboxed iframe.
|
||||
*/
|
||||
function currentlyInSandboxedIframe() {
|
||||
var _a, _b;
|
||||
if (String(self.origin).toLowerCase() === "null" || globalThis.location.hostname === "") {
|
||||
return true;
|
||||
}
|
||||
const sandbox = (_b = (_a = globalThis.frameElement) === null || _a === void 0 ? void 0 : _a.getAttribute) === null || _b === void 0 ? void 0 : _b.call(_a, "sandbox");
|
||||
// No frameElement or sandbox attribute means not sandboxed
|
||||
if (sandbox === null || sandbox === undefined) {
|
||||
return false;
|
||||
}
|
||||
// An empty string means fully sandboxed
|
||||
if (sandbox === "") {
|
||||
return true;
|
||||
}
|
||||
const tokens = new Set(sandbox.toLowerCase().split(" "));
|
||||
return !["allow-scripts", "allow-same-origin"].every((token) => tokens.has(token));
|
||||
}
|
||||
/**
|
||||
* This object allows us to map a special character to a key name. The key name is used
|
||||
* in gathering the i18n translation of the written version of the special character.
|
||||
*/
|
||||
const specialCharacterToKeyMap = {
|
||||
" ": "spaceCharacterDescriptor",
|
||||
"~": "tildeCharacterDescriptor",
|
||||
"`": "backtickCharacterDescriptor",
|
||||
"!": "exclamationCharacterDescriptor",
|
||||
"@": "atSignCharacterDescriptor",
|
||||
"#": "hashSignCharacterDescriptor",
|
||||
$: "dollarSignCharacterDescriptor",
|
||||
"%": "percentSignCharacterDescriptor",
|
||||
"^": "caretCharacterDescriptor",
|
||||
"&": "ampersandCharacterDescriptor",
|
||||
"*": "asteriskCharacterDescriptor",
|
||||
"(": "parenLeftCharacterDescriptor",
|
||||
")": "parenRightCharacterDescriptor",
|
||||
"-": "hyphenCharacterDescriptor",
|
||||
_: "underscoreCharacterDescriptor",
|
||||
"+": "plusCharacterDescriptor",
|
||||
"=": "equalsCharacterDescriptor",
|
||||
"{": "braceLeftCharacterDescriptor",
|
||||
"}": "braceRightCharacterDescriptor",
|
||||
"[": "bracketLeftCharacterDescriptor",
|
||||
"]": "bracketRightCharacterDescriptor",
|
||||
"|": "pipeCharacterDescriptor",
|
||||
"\\": "backSlashCharacterDescriptor",
|
||||
":": "colonCharacterDescriptor",
|
||||
";": "semicolonCharacterDescriptor",
|
||||
'"': "doubleQuoteCharacterDescriptor",
|
||||
"'": "singleQuoteCharacterDescriptor",
|
||||
"<": "lessThanCharacterDescriptor",
|
||||
">": "greaterThanCharacterDescriptor",
|
||||
",": "commaCharacterDescriptor",
|
||||
".": "periodCharacterDescriptor",
|
||||
"?": "questionCharacterDescriptor",
|
||||
"/": "forwardSlashCharacterDescriptor",
|
||||
};
|
||||
/**
|
||||
* Determines if the current rect values are not all 0.
|
||||
*/
|
||||
function rectHasSize(rect) {
|
||||
if (rect.right > 0 && rect.left > 0 && rect.top > 0 && rect.bottom > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Checks if all the values corresponding to the specified keys in an object are null.
|
||||
* If no keys are specified, checks all keys in the object.
|
||||
*
|
||||
* @param obj - The object to check.
|
||||
* @param keys - An optional array of keys to check in the object. Defaults to all keys.
|
||||
* @returns Returns true if all values for the specified keys (or all keys if none are provided) are null; otherwise, false.
|
||||
*/
|
||||
function areKeyValuesNull(obj, keys) {
|
||||
const keysToCheck = keys && keys.length > 0 ? keys : Object.keys(obj);
|
||||
return keysToCheck.every((key) => obj[key] == null);
|
||||
}
|
||||
|
||||
;// ./src/autofill/fido2/enums/fido2-port-name.enum.ts
|
||||
const Fido2PortName = {
|
||||
InjectedScript: "fido2-injected-content-script-port",
|
||||
};
|
||||
|
||||
;// ./src/autofill/fido2/content/messaging/message.ts
|
||||
const MessageTypes = {
|
||||
CredentialCreationRequest: 0,
|
||||
CredentialCreationResponse: 1,
|
||||
CredentialGetRequest: 2,
|
||||
CredentialGetResponse: 3,
|
||||
AbortRequest: 4,
|
||||
DisconnectRequest: 5,
|
||||
ReconnectRequest: 6,
|
||||
AbortResponse: 7,
|
||||
ErrorResponse: 8,
|
||||
};
|
||||
|
||||
;// ./src/autofill/fido2/content/messaging/messenger.ts
|
||||
var messenger_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
const SENDER = "bitwarden-webauthn";
|
||||
/**
|
||||
* A class that handles communication between the page and content script. It converts
|
||||
* the browser's broadcasting API into a request/response API with support for seamlessly
|
||||
* handling aborts and exceptions across separate execution contexts.
|
||||
*/
|
||||
class Messenger {
|
||||
/**
|
||||
* Creates a messenger that uses the browser's `window.postMessage` API to initiate
|
||||
* requests in the content script. Every request will then create it's own
|
||||
* `MessageChannel` through which all subsequent communication will be sent through.
|
||||
*
|
||||
* @param window the window object to use for communication
|
||||
* @returns a `Messenger` instance
|
||||
*/
|
||||
static forDOMCommunication(window) {
|
||||
const windowOrigin = window.location.origin;
|
||||
return new Messenger({
|
||||
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => window.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => window.removeEventListener("message", listener),
|
||||
});
|
||||
}
|
||||
constructor(broadcastChannel) {
|
||||
this.broadcastChannel = broadcastChannel;
|
||||
this.messageEventListener = null;
|
||||
this.onDestroy = new EventTarget();
|
||||
this.messengerId = this.generateUniqueId();
|
||||
this.messageEventListener = this.createMessageEventListener();
|
||||
this.broadcastChannel.addEventListener(this.messageEventListener);
|
||||
}
|
||||
/**
|
||||
* Sends a request to the content script and returns the response.
|
||||
* AbortController signals will be forwarded to the content script.
|
||||
*
|
||||
* @param request data to send to the content script
|
||||
* @param abortSignal the abort controller that might be used to abort the request
|
||||
* @returns the response from the content script
|
||||
*/
|
||||
request(request, abortSignal) {
|
||||
return messenger_awaiter(this, void 0, void 0, function* () {
|
||||
const requestChannel = new MessageChannel();
|
||||
const { port1: localPort, port2: remotePort } = requestChannel;
|
||||
try {
|
||||
const promise = new Promise((resolve) => {
|
||||
localPort.onmessage = (event) => resolve(event.data);
|
||||
});
|
||||
const abortListener = () => localPort.postMessage({
|
||||
metadata: { SENDER },
|
||||
type: MessageTypes.AbortRequest,
|
||||
});
|
||||
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", abortListener);
|
||||
this.broadcastChannel.postMessage(Object.assign(Object.assign({}, request), { SENDER, senderId: this.messengerId }), remotePort);
|
||||
const response = yield promise;
|
||||
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener("abort", abortListener);
|
||||
if (response.type === MessageTypes.ErrorResponse) {
|
||||
const error = new Error();
|
||||
Object.assign(error, JSON.parse(response.error));
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
finally {
|
||||
localPort.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
createMessageEventListener() {
|
||||
return (event) => messenger_awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
const windowOrigin = window.location.origin;
|
||||
if (event.origin !== windowOrigin || !this.handler) {
|
||||
return;
|
||||
}
|
||||
const message = event.data;
|
||||
const port = (_a = event.ports) === null || _a === void 0 ? void 0 : _a[0];
|
||||
if ((message === null || message === void 0 ? void 0 : message.SENDER) !== SENDER || message.senderId == this.messengerId || port == null) {
|
||||
return;
|
||||
}
|
||||
const abortController = new AbortController();
|
||||
port.onmessage = (event) => {
|
||||
if (event.data.type === MessageTypes.AbortRequest) {
|
||||
abortController.abort();
|
||||
}
|
||||
};
|
||||
const onDestroyListener = () => abortController.abort();
|
||||
this.onDestroy.addEventListener("destroy", onDestroyListener);
|
||||
try {
|
||||
const handlerResponse = yield this.handler(message, abortController);
|
||||
port.postMessage(Object.assign(Object.assign({}, handlerResponse), { SENDER }));
|
||||
}
|
||||
catch (error) {
|
||||
port.postMessage({
|
||||
SENDER,
|
||||
type: MessageTypes.ErrorResponse,
|
||||
error: JSON.stringify(error, Object.getOwnPropertyNames(error)),
|
||||
});
|
||||
}
|
||||
finally {
|
||||
this.onDestroy.removeEventListener("destroy", onDestroyListener);
|
||||
port.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Cleans up the messenger by removing the message event listener
|
||||
*/
|
||||
destroy() {
|
||||
return messenger_awaiter(this, void 0, void 0, function* () {
|
||||
this.onDestroy.dispatchEvent(new Event("destroy"));
|
||||
if (this.messageEventListener) {
|
||||
yield this.sendDisconnectCommand();
|
||||
this.broadcastChannel.removeEventListener(this.messageEventListener);
|
||||
this.messageEventListener = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
sendDisconnectCommand() {
|
||||
return messenger_awaiter(this, void 0, void 0, function* () {
|
||||
yield this.request({ type: MessageTypes.DisconnectRequest });
|
||||
});
|
||||
}
|
||||
generateUniqueId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
;// ./src/autofill/fido2/content/fido2-content-script.ts
|
||||
var fido2_content_script_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
(function (globalContext) {
|
||||
const shouldExecuteContentScript = globalContext.document.contentType === "text/html" &&
|
||||
(globalContext.document.location.protocol === "https:" ||
|
||||
(globalContext.document.location.protocol === "http:" &&
|
||||
globalContext.document.location.hostname === "localhost"));
|
||||
if (!shouldExecuteContentScript) {
|
||||
return;
|
||||
}
|
||||
// Initialization logic, set up the messenger and connect a port to the background script.
|
||||
const messenger = Messenger.forDOMCommunication(globalContext.window);
|
||||
messenger.handler = handleFido2Message;
|
||||
const port = chrome.runtime.connect({ name: Fido2PortName.InjectedScript });
|
||||
port.onDisconnect.addListener(handlePortOnDisconnect);
|
||||
/**
|
||||
* Handles FIDO2 credential requests and returns the result.
|
||||
*
|
||||
* @param message - The message to handle.
|
||||
* @param abortController - The abort controller used to handle exit conditions from the FIDO2 request.
|
||||
*/
|
||||
function handleFido2Message(message, abortController) {
|
||||
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
|
||||
const requestId = Date.now().toString();
|
||||
const abortHandler = () => sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId });
|
||||
abortController.signal.addEventListener("abort", abortHandler);
|
||||
try {
|
||||
if (message.type === MessageTypes.CredentialCreationRequest) {
|
||||
return handleCredentialCreationRequestMessage(requestId, message.data);
|
||||
}
|
||||
if (message.type === MessageTypes.CredentialGetRequest) {
|
||||
return handleCredentialGetRequestMessage(requestId, message.data);
|
||||
}
|
||||
if (message.type === MessageTypes.AbortRequest) {
|
||||
return sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId });
|
||||
}
|
||||
}
|
||||
finally {
|
||||
abortController.signal.removeEventListener("abort", abortHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handles the credential creation request message and returns the result.
|
||||
*
|
||||
* @param requestId - The request ID of the message.
|
||||
* @param data - Data associated with the credential request.
|
||||
*/
|
||||
function handleCredentialCreationRequestMessage(requestId, data) {
|
||||
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
|
||||
return respondToCredentialRequest("fido2RegisterCredentialRequest", MessageTypes.CredentialCreationResponse, requestId, data);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handles the credential get request message and returns the result.
|
||||
*
|
||||
* @param requestId - The request ID of the message.
|
||||
* @param data - Data associated with the credential request.
|
||||
*/
|
||||
function handleCredentialGetRequestMessage(requestId, data) {
|
||||
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
|
||||
return respondToCredentialRequest("fido2GetCredentialRequest", MessageTypes.CredentialGetResponse, requestId, data);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sends a message to the extension to handle the
|
||||
* credential request and returns the result.
|
||||
*
|
||||
* @param command - The command to send to the extension.
|
||||
* @param type - The type of message, either CredentialCreationResponse or CredentialGetResponse.
|
||||
* @param requestId - The request ID of the message.
|
||||
* @param messageData - Data associated with the credential request.
|
||||
*/
|
||||
function respondToCredentialRequest(command, type, requestId, messageData) {
|
||||
return fido2_content_script_awaiter(this, void 0, void 0, function* () {
|
||||
const data = Object.assign(Object.assign({}, messageData), { origin: globalContext.location.origin, sameOriginWithAncestors: globalContext.self === globalContext.top });
|
||||
const result = yield sendExtensionMessage(command, { data, requestId });
|
||||
if (result && result.error !== undefined) {
|
||||
return Promise.reject(result.error);
|
||||
}
|
||||
return Promise.resolve({ type, result });
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handles the disconnect event of the port. Calls
|
||||
* to the messenger to destroy and tear down the
|
||||
* implemented page-script.js logic.
|
||||
*/
|
||||
function handlePortOnDisconnect() {
|
||||
void messenger.destroy();
|
||||
}
|
||||
})(globalThis);
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,640 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ../../libs/common/src/platform/services/fido2/fido2-utils.ts
|
||||
// @ts-strict-ignore
|
||||
class Fido2Utils {
|
||||
static createResultToJson(result) {
|
||||
return {
|
||||
id: result.credentialId,
|
||||
rawId: result.credentialId,
|
||||
response: {
|
||||
clientDataJSON: result.clientDataJSON,
|
||||
authenticatorData: result.authData,
|
||||
transports: result.transports,
|
||||
publicKey: result.publicKey,
|
||||
publicKeyAlgorithm: result.publicKeyAlgorithm,
|
||||
attestationObject: result.attestationObject,
|
||||
},
|
||||
authenticatorAttachment: "platform",
|
||||
clientExtensionResults: result.extensions,
|
||||
type: "public-key",
|
||||
};
|
||||
}
|
||||
static getResultToJson(result) {
|
||||
return {
|
||||
id: result.credentialId,
|
||||
rawId: result.credentialId,
|
||||
response: {
|
||||
clientDataJSON: result.clientDataJSON,
|
||||
authenticatorData: result.authenticatorData,
|
||||
signature: result.signature,
|
||||
userHandle: result.userHandle,
|
||||
},
|
||||
authenticatorAttachment: "platform",
|
||||
clientExtensionResults: {},
|
||||
type: "public-key",
|
||||
};
|
||||
}
|
||||
static bufferToString(bufferSource) {
|
||||
return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource))
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
}
|
||||
static stringToBuffer(str) {
|
||||
return Fido2Utils.fromB64ToArray(Fido2Utils.fromUrlB64ToB64(str)).buffer;
|
||||
}
|
||||
static bufferSourceToUint8Array(bufferSource) {
|
||||
if (Fido2Utils.isArrayBuffer(bufferSource)) {
|
||||
return new Uint8Array(bufferSource);
|
||||
}
|
||||
else {
|
||||
return new Uint8Array(bufferSource.buffer, bufferSource.byteOffset, bufferSource.byteLength);
|
||||
}
|
||||
}
|
||||
/** Utility function to identify type of bufferSource. Necessary because of differences between runtimes */
|
||||
static isArrayBuffer(bufferSource) {
|
||||
return bufferSource instanceof ArrayBuffer || bufferSource.buffer === undefined;
|
||||
}
|
||||
static fromB64toUrlB64(b64Str) {
|
||||
return b64Str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||
}
|
||||
static fromBufferToB64(buffer) {
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
let binary = "";
|
||||
const bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return globalThis.btoa(binary);
|
||||
}
|
||||
static fromB64ToArray(str) {
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
const binaryString = globalThis.atob(str);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
static fromUrlB64ToB64(urlB64Str) {
|
||||
let output = urlB64Str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
switch (output.length % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 2:
|
||||
output += "==";
|
||||
break;
|
||||
case 3:
|
||||
output += "=";
|
||||
break;
|
||||
default:
|
||||
throw new Error("Illegal base64url string!");
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle
|
||||
* @param userHandle
|
||||
*/
|
||||
static cipherHasNoOtherPasskeys(cipher, userHandle) {
|
||||
if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle);
|
||||
}
|
||||
}
|
||||
|
||||
;// ./src/autofill/fido2/utils/webauthn-utils.ts
|
||||
|
||||
class WebauthnUtils {
|
||||
static mapCredentialCreationOptions(options, fallbackSupported) {
|
||||
var _a, _b, _c, _d, _e;
|
||||
const keyOptions = options.publicKey;
|
||||
if (keyOptions == undefined) {
|
||||
throw new Error("Public-key options not found");
|
||||
}
|
||||
return {
|
||||
attestation: keyOptions.attestation,
|
||||
authenticatorSelection: {
|
||||
requireResidentKey: (_a = keyOptions.authenticatorSelection) === null || _a === void 0 ? void 0 : _a.requireResidentKey,
|
||||
residentKey: (_b = keyOptions.authenticatorSelection) === null || _b === void 0 ? void 0 : _b.residentKey,
|
||||
userVerification: (_c = keyOptions.authenticatorSelection) === null || _c === void 0 ? void 0 : _c.userVerification,
|
||||
},
|
||||
challenge: Fido2Utils.bufferToString(keyOptions.challenge),
|
||||
excludeCredentials: (_d = keyOptions.excludeCredentials) === null || _d === void 0 ? void 0 : _d.map((credential) => ({
|
||||
id: Fido2Utils.bufferToString(credential.id),
|
||||
transports: credential.transports,
|
||||
type: credential.type,
|
||||
})),
|
||||
extensions: {
|
||||
credProps: (_e = keyOptions.extensions) === null || _e === void 0 ? void 0 : _e.credProps,
|
||||
},
|
||||
pubKeyCredParams: keyOptions.pubKeyCredParams
|
||||
.map((params) => ({
|
||||
// Fix for spec-deviation: Sites using KeycloakJS send `kp.alg` as a string
|
||||
alg: Number(params.alg),
|
||||
type: params.type,
|
||||
}))
|
||||
.filter((params) => !isNaN(params.alg)),
|
||||
rp: {
|
||||
id: keyOptions.rp.id,
|
||||
name: keyOptions.rp.name,
|
||||
},
|
||||
user: {
|
||||
id: Fido2Utils.bufferToString(keyOptions.user.id),
|
||||
displayName: keyOptions.user.displayName,
|
||||
name: keyOptions.user.name,
|
||||
},
|
||||
timeout: keyOptions.timeout,
|
||||
fallbackSupported,
|
||||
};
|
||||
}
|
||||
static mapCredentialRegistrationResult(result) {
|
||||
const credential = {
|
||||
id: result.credentialId,
|
||||
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
||||
type: "public-key",
|
||||
authenticatorAttachment: "platform",
|
||||
response: {
|
||||
clientDataJSON: Fido2Utils.stringToBuffer(result.clientDataJSON),
|
||||
attestationObject: Fido2Utils.stringToBuffer(result.attestationObject),
|
||||
getAuthenticatorData() {
|
||||
return Fido2Utils.stringToBuffer(result.authData);
|
||||
},
|
||||
getPublicKey() {
|
||||
return Fido2Utils.stringToBuffer(result.publicKey);
|
||||
},
|
||||
getPublicKeyAlgorithm() {
|
||||
return result.publicKeyAlgorithm;
|
||||
},
|
||||
getTransports() {
|
||||
return result.transports;
|
||||
},
|
||||
},
|
||||
getClientExtensionResults: () => ({
|
||||
credProps: result.extensions.credProps,
|
||||
}),
|
||||
toJSON: () => Fido2Utils.createResultToJson(result),
|
||||
};
|
||||
// Modify prototype chains to fix `instanceof` calls.
|
||||
// This makes these objects indistinguishable from the native classes.
|
||||
// Unfortunately PublicKeyCredential does not have a javascript constructor so `extends` does not work here.
|
||||
Object.setPrototypeOf(credential.response, AuthenticatorAttestationResponse.prototype);
|
||||
Object.setPrototypeOf(credential, PublicKeyCredential.prototype);
|
||||
return credential;
|
||||
}
|
||||
static mapCredentialRequestOptions(options, fallbackSupported) {
|
||||
var _a, _b;
|
||||
const keyOptions = options.publicKey;
|
||||
if (keyOptions == undefined) {
|
||||
throw new Error("Public-key options not found");
|
||||
}
|
||||
return {
|
||||
allowedCredentialIds: (_b = (_a = keyOptions.allowCredentials) === null || _a === void 0 ? void 0 : _a.map((c) => Fido2Utils.bufferToString(c.id))) !== null && _b !== void 0 ? _b : [],
|
||||
challenge: Fido2Utils.bufferToString(keyOptions.challenge),
|
||||
rpId: keyOptions.rpId,
|
||||
userVerification: keyOptions.userVerification,
|
||||
timeout: keyOptions.timeout,
|
||||
mediation: options.mediation,
|
||||
fallbackSupported,
|
||||
};
|
||||
}
|
||||
static mapCredentialAssertResult(result) {
|
||||
const credential = {
|
||||
id: result.credentialId,
|
||||
rawId: Fido2Utils.stringToBuffer(result.credentialId),
|
||||
type: "public-key",
|
||||
response: {
|
||||
authenticatorData: Fido2Utils.stringToBuffer(result.authenticatorData),
|
||||
clientDataJSON: Fido2Utils.stringToBuffer(result.clientDataJSON),
|
||||
signature: Fido2Utils.stringToBuffer(result.signature),
|
||||
userHandle: Fido2Utils.stringToBuffer(result.userHandle),
|
||||
},
|
||||
getClientExtensionResults: () => ({}),
|
||||
authenticatorAttachment: "platform",
|
||||
toJSON: () => Fido2Utils.getResultToJson(result),
|
||||
};
|
||||
// Modify prototype chains to fix `instanceof` calls.
|
||||
// This makes these objects indistinguishable from the native classes.
|
||||
// Unfortunately PublicKeyCredential does not have a javascript constructor so `extends` does not work here.
|
||||
Object.setPrototypeOf(credential.response, AuthenticatorAssertionResponse.prototype);
|
||||
Object.setPrototypeOf(credential, PublicKeyCredential.prototype);
|
||||
return credential;
|
||||
}
|
||||
}
|
||||
|
||||
;// ./src/autofill/fido2/content/messaging/message.ts
|
||||
const MessageTypes = {
|
||||
CredentialCreationRequest: 0,
|
||||
CredentialCreationResponse: 1,
|
||||
CredentialGetRequest: 2,
|
||||
CredentialGetResponse: 3,
|
||||
AbortRequest: 4,
|
||||
DisconnectRequest: 5,
|
||||
ReconnectRequest: 6,
|
||||
AbortResponse: 7,
|
||||
ErrorResponse: 8,
|
||||
};
|
||||
|
||||
;// ./src/autofill/fido2/content/messaging/messenger.ts
|
||||
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
const SENDER = "bitwarden-webauthn";
|
||||
/**
|
||||
* A class that handles communication between the page and content script. It converts
|
||||
* the browser's broadcasting API into a request/response API with support for seamlessly
|
||||
* handling aborts and exceptions across separate execution contexts.
|
||||
*/
|
||||
class Messenger {
|
||||
/**
|
||||
* Creates a messenger that uses the browser's `window.postMessage` API to initiate
|
||||
* requests in the content script. Every request will then create it's own
|
||||
* `MessageChannel` through which all subsequent communication will be sent through.
|
||||
*
|
||||
* @param window the window object to use for communication
|
||||
* @returns a `Messenger` instance
|
||||
*/
|
||||
static forDOMCommunication(window) {
|
||||
const windowOrigin = window.location.origin;
|
||||
return new Messenger({
|
||||
postMessage: (message, port) => window.postMessage(message, windowOrigin, [port]),
|
||||
addEventListener: (listener) => window.addEventListener("message", listener),
|
||||
removeEventListener: (listener) => window.removeEventListener("message", listener),
|
||||
});
|
||||
}
|
||||
constructor(broadcastChannel) {
|
||||
this.broadcastChannel = broadcastChannel;
|
||||
this.messageEventListener = null;
|
||||
this.onDestroy = new EventTarget();
|
||||
this.messengerId = this.generateUniqueId();
|
||||
this.messageEventListener = this.createMessageEventListener();
|
||||
this.broadcastChannel.addEventListener(this.messageEventListener);
|
||||
}
|
||||
/**
|
||||
* Sends a request to the content script and returns the response.
|
||||
* AbortController signals will be forwarded to the content script.
|
||||
*
|
||||
* @param request data to send to the content script
|
||||
* @param abortSignal the abort controller that might be used to abort the request
|
||||
* @returns the response from the content script
|
||||
*/
|
||||
request(request, abortSignal) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const requestChannel = new MessageChannel();
|
||||
const { port1: localPort, port2: remotePort } = requestChannel;
|
||||
try {
|
||||
const promise = new Promise((resolve) => {
|
||||
localPort.onmessage = (event) => resolve(event.data);
|
||||
});
|
||||
const abortListener = () => localPort.postMessage({
|
||||
metadata: { SENDER },
|
||||
type: MessageTypes.AbortRequest,
|
||||
});
|
||||
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener("abort", abortListener);
|
||||
this.broadcastChannel.postMessage(Object.assign(Object.assign({}, request), { SENDER, senderId: this.messengerId }), remotePort);
|
||||
const response = yield promise;
|
||||
abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener("abort", abortListener);
|
||||
if (response.type === MessageTypes.ErrorResponse) {
|
||||
const error = new Error();
|
||||
Object.assign(error, JSON.parse(response.error));
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
finally {
|
||||
localPort.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
createMessageEventListener() {
|
||||
return (event) => __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
const windowOrigin = window.location.origin;
|
||||
if (event.origin !== windowOrigin || !this.handler) {
|
||||
return;
|
||||
}
|
||||
const message = event.data;
|
||||
const port = (_a = event.ports) === null || _a === void 0 ? void 0 : _a[0];
|
||||
if ((message === null || message === void 0 ? void 0 : message.SENDER) !== SENDER || message.senderId == this.messengerId || port == null) {
|
||||
return;
|
||||
}
|
||||
const abortController = new AbortController();
|
||||
port.onmessage = (event) => {
|
||||
if (event.data.type === MessageTypes.AbortRequest) {
|
||||
abortController.abort();
|
||||
}
|
||||
};
|
||||
const onDestroyListener = () => abortController.abort();
|
||||
this.onDestroy.addEventListener("destroy", onDestroyListener);
|
||||
try {
|
||||
const handlerResponse = yield this.handler(message, abortController);
|
||||
port.postMessage(Object.assign(Object.assign({}, handlerResponse), { SENDER }));
|
||||
}
|
||||
catch (error) {
|
||||
port.postMessage({
|
||||
SENDER,
|
||||
type: MessageTypes.ErrorResponse,
|
||||
error: JSON.stringify(error, Object.getOwnPropertyNames(error)),
|
||||
});
|
||||
}
|
||||
finally {
|
||||
this.onDestroy.removeEventListener("destroy", onDestroyListener);
|
||||
port.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Cleans up the messenger by removing the message event listener
|
||||
*/
|
||||
destroy() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.onDestroy.dispatchEvent(new Event("destroy"));
|
||||
if (this.messageEventListener) {
|
||||
yield this.sendDisconnectCommand();
|
||||
this.broadcastChannel.removeEventListener(this.messageEventListener);
|
||||
this.messageEventListener = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
sendDisconnectCommand() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.request({ type: MessageTypes.DisconnectRequest });
|
||||
});
|
||||
}
|
||||
generateUniqueId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
;// ./src/autofill/fido2/content/fido2-page-script.ts
|
||||
var fido2_page_script_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
|
||||
|
||||
(function (globalContext) {
|
||||
if (globalContext.document.currentScript) {
|
||||
globalContext.document.currentScript.parentNode.removeChild(globalContext.document.currentScript);
|
||||
}
|
||||
const shouldExecuteContentScript = globalContext.document.contentType === "text/html" &&
|
||||
(globalContext.document.location.protocol === "https:" ||
|
||||
(globalContext.document.location.protocol === "http:" &&
|
||||
globalContext.document.location.hostname === "localhost"));
|
||||
if (!shouldExecuteContentScript) {
|
||||
return;
|
||||
}
|
||||
const BrowserPublicKeyCredential = globalContext.PublicKeyCredential;
|
||||
const BrowserNavigatorCredentials = navigator.credentials;
|
||||
const BrowserAuthenticatorAttestationResponse = globalContext.AuthenticatorAttestationResponse;
|
||||
const browserNativeWebauthnSupport = globalContext.PublicKeyCredential != undefined;
|
||||
let browserNativeWebauthnPlatformAuthenticatorSupport = false;
|
||||
if (!browserNativeWebauthnSupport) {
|
||||
// Polyfill webauthn support
|
||||
try {
|
||||
// credentials are read-only if supported, use type-casting to force assignment
|
||||
navigator.credentials = {
|
||||
create() {
|
||||
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
|
||||
throw new Error("Webauthn not supported in this browser.");
|
||||
});
|
||||
},
|
||||
get() {
|
||||
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
|
||||
throw new Error("Webauthn not supported in this browser.");
|
||||
});
|
||||
},
|
||||
};
|
||||
globalContext.PublicKeyCredential = class PolyfillPublicKeyCredential {
|
||||
static isUserVerifyingPlatformAuthenticatorAvailable() {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
};
|
||||
globalContext.AuthenticatorAttestationResponse =
|
||||
class PolyfillAuthenticatorAttestationResponse {
|
||||
};
|
||||
}
|
||||
catch (_a) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
else {
|
||||
void BrowserPublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable().then((available) => {
|
||||
browserNativeWebauthnPlatformAuthenticatorSupport = available;
|
||||
if (!available) {
|
||||
// Polyfill platform authenticator support
|
||||
globalContext.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = () => Promise.resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
const browserCredentials = {
|
||||
create: navigator.credentials.create.bind(navigator.credentials),
|
||||
get: navigator.credentials.get.bind(navigator.credentials),
|
||||
};
|
||||
const messenger = Messenger.forDOMCommunication(window);
|
||||
let waitForFocusTimeout;
|
||||
let focusListenerHandler;
|
||||
navigator.credentials.create = createWebAuthnCredential;
|
||||
navigator.credentials.get = getWebAuthnCredential;
|
||||
/**
|
||||
* Creates a new webauthn credential.
|
||||
*
|
||||
* @param options Options for creating new credentials.
|
||||
* @returns Promise that resolves to the new credential object.
|
||||
*/
|
||||
function createWebAuthnCredential(options) {
|
||||
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
|
||||
var _a, _b;
|
||||
if (!isWebauthnCall(options)) {
|
||||
return yield browserCredentials.create(options);
|
||||
}
|
||||
const authenticatorAttachmentIsPlatform = ((_b = (_a = options === null || options === void 0 ? void 0 : options.publicKey) === null || _a === void 0 ? void 0 : _a.authenticatorSelection) === null || _b === void 0 ? void 0 : _b.authenticatorAttachment) === "platform";
|
||||
const fallbackSupported = (authenticatorAttachmentIsPlatform && browserNativeWebauthnPlatformAuthenticatorSupport) ||
|
||||
(!authenticatorAttachmentIsPlatform && browserNativeWebauthnSupport);
|
||||
try {
|
||||
const response = yield messenger.request({
|
||||
type: MessageTypes.CredentialCreationRequest,
|
||||
data: WebauthnUtils.mapCredentialCreationOptions(options, fallbackSupported),
|
||||
}, options === null || options === void 0 ? void 0 : options.signal);
|
||||
if (response.type !== MessageTypes.CredentialCreationResponse) {
|
||||
throw new Error("Something went wrong.");
|
||||
}
|
||||
return WebauthnUtils.mapCredentialRegistrationResult(response.result);
|
||||
}
|
||||
catch (error) {
|
||||
if (error && error.fallbackRequested && fallbackSupported) {
|
||||
yield waitForFocus();
|
||||
return yield browserCredentials.create(options);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Retrieves a webauthn credential.
|
||||
*
|
||||
* @param options Options for creating new credentials.
|
||||
* @returns Promise that resolves to the new credential object.
|
||||
*/
|
||||
function getWebAuthnCredential(options) {
|
||||
return fido2_page_script_awaiter(this, void 0, void 0, function* () {
|
||||
if (!isWebauthnCall(options)) {
|
||||
return yield browserCredentials.get(options);
|
||||
}
|
||||
const abortSignal = (options === null || options === void 0 ? void 0 : options.signal) || new AbortController().signal;
|
||||
const fallbackSupported = browserNativeWebauthnSupport;
|
||||
if ((options === null || options === void 0 ? void 0 : options.mediation) && options.mediation === "conditional") {
|
||||
const internalAbortControllers = [new AbortController(), new AbortController()];
|
||||
const bitwardenResponse = (internalAbortController) => fido2_page_script_awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const abortListener = () => messenger.request({
|
||||
type: MessageTypes.AbortRequest,
|
||||
abortedRequestId: abortSignal.toString(),
|
||||
});
|
||||
internalAbortController.signal.addEventListener("abort", abortListener);
|
||||
const response = yield messenger.request({
|
||||
type: MessageTypes.CredentialGetRequest,
|
||||
data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported),
|
||||
}, internalAbortController.signal);
|
||||
internalAbortController.signal.removeEventListener("abort", abortListener);
|
||||
if (response.type !== MessageTypes.CredentialGetResponse) {
|
||||
throw new Error("Something went wrong.");
|
||||
}
|
||||
return WebauthnUtils.mapCredentialAssertResult(response.result);
|
||||
}
|
||||
catch (_a) {
|
||||
// Ignoring error
|
||||
}
|
||||
});
|
||||
const browserResponse = (internalAbortController) => browserCredentials.get(Object.assign(Object.assign({}, options), { signal: internalAbortController.signal }));
|
||||
const abortListener = () => {
|
||||
internalAbortControllers.forEach((controller) => controller.abort());
|
||||
};
|
||||
abortSignal.addEventListener("abort", abortListener);
|
||||
const response = yield Promise.race([
|
||||
bitwardenResponse(internalAbortControllers[0]),
|
||||
browserResponse(internalAbortControllers[1]),
|
||||
]);
|
||||
abortSignal.removeEventListener("abort", abortListener);
|
||||
internalAbortControllers.forEach((controller) => controller.abort());
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
const response = yield messenger.request({
|
||||
type: MessageTypes.CredentialGetRequest,
|
||||
data: WebauthnUtils.mapCredentialRequestOptions(options, fallbackSupported),
|
||||
}, options === null || options === void 0 ? void 0 : options.signal);
|
||||
if (response.type !== MessageTypes.CredentialGetResponse) {
|
||||
throw new Error("Something went wrong.");
|
||||
}
|
||||
return WebauthnUtils.mapCredentialAssertResult(response.result);
|
||||
}
|
||||
catch (error) {
|
||||
if (error && error.fallbackRequested && fallbackSupported) {
|
||||
yield waitForFocus();
|
||||
return yield browserCredentials.get(options);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
function isWebauthnCall(options) {
|
||||
return options && "publicKey" in options;
|
||||
}
|
||||
/**
|
||||
* Wait for window to be focused.
|
||||
* Safari doesn't allow scripts to trigger webauthn when window is not focused.
|
||||
*
|
||||
* @param fallbackWait How long to wait when the script is not able to add event listeners to `window.top`. Defaults to 500ms.
|
||||
* @param timeout Maximum time to wait for focus in milliseconds. Defaults to 5 minutes.
|
||||
* @returns Promise that resolves when window is focused, or rejects if timeout is reached.
|
||||
*/
|
||||
function waitForFocus() {
|
||||
return fido2_page_script_awaiter(this, arguments, void 0, function* (fallbackWait = 500, timeout = 5 * 60 * 1000) {
|
||||
try {
|
||||
if (globalContext.top.document.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (_a) {
|
||||
// Cannot access window.top due to cross-origin frame, fallback to waiting
|
||||
return yield new Promise((resolve) => globalContext.setTimeout(resolve, fallbackWait));
|
||||
}
|
||||
const focusPromise = new Promise((resolve) => {
|
||||
focusListenerHandler = () => resolve();
|
||||
globalContext.top.addEventListener("focus", focusListenerHandler);
|
||||
});
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
waitForFocusTimeout = globalContext.setTimeout(() => reject(new DOMException("The operation either timed out or was not allowed.", "AbortError")), timeout);
|
||||
});
|
||||
try {
|
||||
yield Promise.race([focusPromise, timeoutPromise]);
|
||||
}
|
||||
finally {
|
||||
clearWaitForFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
function clearWaitForFocus() {
|
||||
globalContext.top.removeEventListener("focus", focusListenerHandler);
|
||||
if (waitForFocusTimeout) {
|
||||
globalContext.clearTimeout(waitForFocusTimeout);
|
||||
}
|
||||
}
|
||||
function destroy() {
|
||||
try {
|
||||
if (browserNativeWebauthnSupport) {
|
||||
navigator.credentials.create = browserCredentials.create;
|
||||
navigator.credentials.get = browserCredentials.get;
|
||||
}
|
||||
else {
|
||||
navigator.credentials = BrowserNavigatorCredentials;
|
||||
globalContext.PublicKeyCredential = BrowserPublicKeyCredential;
|
||||
globalContext.AuthenticatorAttestationResponse = BrowserAuthenticatorAttestationResponse;
|
||||
}
|
||||
clearWaitForFocus();
|
||||
void messenger.destroy();
|
||||
}
|
||||
catch (_a) {
|
||||
/** empty */
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets up a listener to handle cleanup or reconnection when the extension's
|
||||
* context changes due to being reloaded or unloaded.
|
||||
*/
|
||||
messenger.handler = (message) => {
|
||||
const type = message.type;
|
||||
// Handle cleanup for disconnect request
|
||||
if (type === MessageTypes.DisconnectRequest) {
|
||||
destroy();
|
||||
}
|
||||
};
|
||||
})(globalThis);
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,52 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
// UNUSED EXPORTS: sendExtensionMessage
|
||||
|
||||
;// ../../libs/common/src/platform/ipc/ipc-message.ts
|
||||
function isIpcMessage(message) {
|
||||
return message.type === "bitwarden-ipc-message";
|
||||
}
|
||||
|
||||
;// ./src/platform/ipc/content/ipc-content-script.ts
|
||||
// TODO: This content script should be dynamically reloaded when the extension is updated,
|
||||
// to avoid "Extension context invalidated." errors.
|
||||
|
||||
// Web -> Background
|
||||
function sendExtensionMessage(message) {
|
||||
if (typeof browser !== "undefined" &&
|
||||
typeof browser.runtime !== "undefined" &&
|
||||
typeof browser.runtime.sendMessage !== "undefined") {
|
||||
void browser.runtime.sendMessage(message);
|
||||
return;
|
||||
}
|
||||
void chrome.runtime.sendMessage(message);
|
||||
}
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.origin !== window.origin) {
|
||||
return;
|
||||
}
|
||||
if (isIpcMessage(event.data)) {
|
||||
sendExtensionMessage(event.data);
|
||||
}
|
||||
});
|
||||
// Background -> Web
|
||||
function setupMessageListener() {
|
||||
function listener(message) {
|
||||
if (isIpcMessage(message)) {
|
||||
void window.postMessage(message);
|
||||
}
|
||||
}
|
||||
if (typeof browser !== "undefined" &&
|
||||
typeof browser.runtime !== "undefined" &&
|
||||
typeof browser.runtime.onMessage !== "undefined") {
|
||||
browser.runtime.onMessage.addListener(listener);
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax -- This doesn't run in the popup but in the content script
|
||||
chrome.runtime.onMessage.addListener(listener);
|
||||
}
|
||||
setupMessageListener();
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,22 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
|
||||
const VaultMessages = {
|
||||
HasBwInstalled: "hasBwInstalled",
|
||||
checkBwInstalled: "checkIfBWExtensionInstalled",
|
||||
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
|
||||
OpenAtRiskPasswords: "openAtRiskPasswords",
|
||||
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
|
||||
PopupOpened: "popupOpened",
|
||||
};
|
||||
|
||||
|
||||
;// ./src/vault/content/send-on-installed-message.ts
|
||||
|
||||
(function (globalContext) {
|
||||
globalContext.postMessage({ command: VaultMessages.HasBwInstalled });
|
||||
})(window);
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,23 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
/******/ "use strict";
|
||||
|
||||
;// ../../libs/common/src/vault/enums/vault-messages.enum.ts
|
||||
const VaultMessages = {
|
||||
HasBwInstalled: "hasBwInstalled",
|
||||
checkBwInstalled: "checkIfBWExtensionInstalled",
|
||||
/** @deprecated use {@link OpenBrowserExtensionToUrl} */
|
||||
OpenAtRiskPasswords: "openAtRiskPasswords",
|
||||
OpenBrowserExtensionToUrl: "openBrowserExtensionToUrl",
|
||||
PopupOpened: "popupOpened",
|
||||
};
|
||||
|
||||
|
||||
;// ./src/vault/content/send-popup-open-message.ts
|
||||
|
||||
(function (globalContext) {
|
||||
// Send a message to the window that the popup opened
|
||||
globalContext.postMessage({ command: VaultMessages.PopupOpened });
|
||||
})(window);
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
@@ -0,0 +1,7 @@
|
||||
/******/ (function() { // webpackBootstrap
|
||||
(function () {
|
||||
void chrome.runtime.sendMessage({ command: "triggerAutofillScriptInjection" });
|
||||
})();
|
||||
|
||||
/******/ })()
|
||||
;
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlns:v="https://vecta.io/nano"><path d="M497.72 429.63l-169-174.82L498.11 82.24c6.956-7.168 6.956-18.85 0-26.018L449.934 6.31C446.585 2.859 442.076 1 437.31 1s-9.275 1.991-12.624 5.31l-168.62 172.04L87.196 6.45c-3.349-3.451-7.858-5.31-12.624-5.31s-9.275 1.991-12.624 5.31L13.9 56.362c-6.956 7.168-6.956 18.85 0 26.018l169.39 172.57L14.42 429.64c-3.349 3.451-5.281 8.097-5.281 13.009s1.803 9.558 5.281 13.009l48.176 49.912c3.478 3.584 7.987 5.442 12.624 5.442 4.508 0 9.146-1.726 12.624-5.442l168.23-174.16 168.36 174.03c3.478 3.584 7.986 5.442 12.624 5.442 4.509 0 9.146-1.726 12.624-5.442l48.176-49.912c3.349-3.451 5.281-8.097 5.281-13.009a19.32 19.32 0 0 0-5.41-12.876z"/></svg>
|
||||
|
After Width: | Height: | Size: 743 B |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 416 B |