Refactoring repository...
Some checks are pending
Check Conventional Commit / check-commit-message (push) Waiting to run

This commit is contained in:
CELESTIFYX
2025-01-14 19:02:06 +02:00
parent 876fa0988e
commit 08abac9e7d
33171 changed files with 4677 additions and 761 deletions

View File

@@ -0,0 +1 @@
<html><head><meta charset="UTF-8"/><script defer="defer" src="vendor.js"></script><script defer="defer" src="background.js"></script></head><body></body></html>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,324 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ./src/autofill/enums/autofill-port.enums.ts
const AutofillPort = {
InjectedScript: "autofill-injected-script-port",
};
;// CONCATENATED MODULE: ./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 that formatted as a custom element name.
*/
function generateRandomCustomElementName() {
const 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("");
};
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, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
chrome.runtime.sendMessage(Object.assign({ command }, options), (response) => {
if (chrome.runtime.lastError) {
return;
}
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);
}
}
/**
* Get data from local storage based on the keys provided.
*
* @param keys - String or array of strings of keys to get from local storage
* @deprecated Do not call this, use state-relevant services instead
*/
function getFromLocalStorage(keys) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => {
chrome.storage.local.get(keys, (storage) => resolve(storage));
});
});
}
/**
* Sets up a long-lived connection with the extension background
* and triggers an onDisconnect event if the extension context
* is invalidated.
*
* @param callback - Callback 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 (formFieldElement === null || formFieldElement === void 0 ? void 0 : formFieldElement.tagName.toLowerCase()) !== "span";
}
/**
* 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 (element === null || element === void 0 ? void 0 : 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) {
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);
}
;// CONCATENATED MODULE: ./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",
};
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
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);
}
}
/******/ })()
;

View File

@@ -0,0 +1,150 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ../../libs/common/src/vault/enums/vault-onboarding.enum.ts
const VaultOnboardingMessages = {
HasBwInstalled: "hasBwInstalled",
checkBwInstalled: "checkIfBWExtensionInstalled",
};
;// CONCATENATED MODULE: ./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());
});
};
/**
* 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);
});
/**
* Handlers for window messages from the content script.
*/
const windowMessageHandlers = {
authResult: ({ data, referrer }) => handleAuthResultMessage(data, referrer),
webAuthnResult: ({ data, referrer }) => handleWebAuthnResultMessage(data, referrer),
checkIfBWExtensionInstalled: () => handleExtensionInstallCheck(),
duoResult: ({ data, referrer }) => handleDuoResultMessage(data, referrer),
};
/**
* Handles the post to the web vault showing the extension has been installed
*/
function handleExtensionInstallCheck() {
window.postMessage({ command: VaultOnboardingMessages.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 });
}
/**
* Handles the window message event.
*
* @param event - The window message event
*/
function handleWindowMessageEvent(event) {
const { source, data } = event;
if (source !== window || !(data === null || data === void 0 ? void 0 : data.command)) {
return;
}
const referrer = source.location.hostname;
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);
}
/******/ })()
;

View File

@@ -0,0 +1,69 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
const inputTags = ["input", "textarea", "select"];
const labelTags = ["label", "span"];
const attributes = ["id", "name", "label-aria", "placeholder"];
const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement");
const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique");
let clickedEl = null;
// Find the best attribute to be used as the Name for an element in a custom field.
function getClickedElementIdentifier() {
var _a, _b;
if (clickedEl == null) {
return invalidElement;
}
const clickedTag = clickedEl.nodeName.toLowerCase();
let inputEl = null;
// Try to identify the input element (which may not be the clicked element)
if (labelTags.includes(clickedTag)) {
let inputId = null;
if (clickedTag === "label") {
inputId = clickedEl.getAttribute("for");
}
else {
inputId = (_a = clickedEl.closest("label")) === null || _a === void 0 ? void 0 : _a.getAttribute("for");
}
inputEl = document.getElementById(inputId);
}
else {
inputEl = clickedEl;
}
if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) {
return invalidElement;
}
for (const attr of attributes) {
const attributeValue = inputEl.getAttribute(attr);
const selector = "[" + attr + '="' + 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) => {
clickedEl = 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);
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
chrome.runtime.sendMessage({
command: "getClickedElementResponse",
sender: "contextMenuHandler",
identifier: identifier,
});
}
});
/******/ })()
;

View File

@@ -0,0 +1,312 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ./src/vault/fido2/content/messaging/message.ts
var MessageType;
(function (MessageType) {
MessageType[MessageType["CredentialCreationRequest"] = 0] = "CredentialCreationRequest";
MessageType[MessageType["CredentialCreationResponse"] = 1] = "CredentialCreationResponse";
MessageType[MessageType["CredentialGetRequest"] = 2] = "CredentialGetRequest";
MessageType[MessageType["CredentialGetResponse"] = 3] = "CredentialGetResponse";
MessageType[MessageType["AbortRequest"] = 4] = "AbortRequest";
MessageType[MessageType["DisconnectRequest"] = 5] = "DisconnectRequest";
MessageType[MessageType["ReconnectRequest"] = 6] = "ReconnectRequest";
MessageType[MessageType["AbortResponse"] = 7] = "AbortResponse";
MessageType[MessageType["ErrorResponse"] = 8] = "ErrorResponse";
})(MessageType || (MessageType = {}));
;// CONCATENATED MODULE: ../../libs/common/src/vault/abstractions/fido2/fido2-client.service.abstraction.ts
const UserRequestedFallbackAbortReason = "UserRequestedFallback";
/**
* This class represents an abstraction of the WebAuthn Client as described by W3C:
* https://www.w3.org/TR/webauthn-3/#webauthn-client
*
* The WebAuthn Client is an intermediary entity typically implemented in the user agent
* (in whole, or in part). Conceptually, it underlies the Web Authentication API and embodies
* the implementation of the Web Authentication API's operations.
*
* It is responsible for both marshalling the inputs for the underlying authenticator operations,
* and for returning the results of the latter operations to the Web Authentication API's callers.
*/
class Fido2ClientService {
}
/**
* Error thrown when the user requests a fallback to the browser's built-in WebAuthn implementation.
*/
class FallbackRequestedError extends Error {
constructor() {
super("FallbackRequested");
this.fallbackRequested = true;
}
}
;// CONCATENATED MODULE: ./src/vault/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());
});
};
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 abortController the abort controller that might be used to abort the request
* @returns the response from the content script
*/
request(request, abortController) {
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: MessageType.AbortRequest,
});
abortController === null || abortController === void 0 ? void 0 : abortController.signal.addEventListener("abort", abortListener);
this.broadcastChannel.postMessage(Object.assign(Object.assign({}, request), { SENDER, senderId: this.messengerId }), remotePort);
const response = yield promise;
abortController === null || abortController === void 0 ? void 0 : abortController.signal.removeEventListener("abort", abortListener);
if (response.type === MessageType.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 ||
message == null ||
port == null) {
return;
}
const abortController = new AbortController();
port.onmessage = (event) => {
if (event.data.type === MessageType.AbortRequest) {
abortController.abort();
}
};
let onDestroyListener;
const destroyPromise = new Promise((_, reject) => {
onDestroyListener = () => reject(new FallbackRequestedError());
this.onDestroy.addEventListener("destroy", onDestroyListener);
});
try {
const handlerResponse = yield Promise.race([
this.handler(message, abortController),
destroyPromise,
]);
port.postMessage(Object.assign(Object.assign({}, handlerResponse), { SENDER }));
}
catch (error) {
port.postMessage({
SENDER,
type: MessageType.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;
}
});
}
sendReconnectCommand() {
return __awaiter(this, void 0, void 0, function* () {
yield this.request({ type: MessageType.ReconnectRequest });
});
}
sendDisconnectCommand() {
return __awaiter(this, void 0, void 0, function* () {
yield this.request({ type: MessageType.DisconnectRequest });
});
}
generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
}
}
;// CONCATENATED MODULE: ./src/vault/fido2/content/content-script.ts
var 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 isFido2FeatureEnabled() {
return new Promise((resolve) => {
chrome.runtime.sendMessage({
command: "checkFido2FeatureEnabled",
hostname: window.location.hostname,
origin: window.location.origin,
}, (response) => resolve(response.result));
});
}
function isSameOriginWithAncestors() {
try {
return window.self === window.top;
}
catch (_a) {
return false;
}
}
const messenger = Messenger.forDOMCommunication(window);
function injectPageScript() {
// Locate an existing page-script on the page
const existingPageScript = document.getElementById("bw-fido2-page-script");
// Inject the page-script if it doesn't exist
if (!existingPageScript) {
const s = document.createElement("script");
s.src = chrome.runtime.getURL("content/fido2/page-script.js");
s.id = "bw-fido2-page-script";
(document.head || document.documentElement).appendChild(s);
return;
}
// If the page-script already exists, send a reconnect message to the page-script
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
messenger.sendReconnectCommand();
}
function initializeFido2ContentScript() {
injectPageScript();
messenger.handler = (message, abortController) => content_script_awaiter(this, void 0, void 0, function* () {
const requestId = Date.now().toString();
const abortHandler = () => chrome.runtime.sendMessage({
command: "fido2AbortRequest",
abortedRequestId: requestId,
});
abortController.signal.addEventListener("abort", abortHandler);
if (message.type === MessageType.CredentialCreationRequest) {
return new Promise((resolve, reject) => {
const data = Object.assign(Object.assign({}, message.data), { origin: window.location.origin, sameOriginWithAncestors: isSameOriginWithAncestors() });
chrome.runtime.sendMessage({
command: "fido2RegisterCredentialRequest",
data,
requestId: requestId,
}, (response) => {
if (response && response.error !== undefined) {
return reject(response.error);
}
resolve({
type: MessageType.CredentialCreationResponse,
result: response.result,
});
});
});
}
if (message.type === MessageType.CredentialGetRequest) {
return new Promise((resolve, reject) => {
const data = Object.assign(Object.assign({}, message.data), { origin: window.location.origin, sameOriginWithAncestors: isSameOriginWithAncestors() });
chrome.runtime.sendMessage({
command: "fido2GetCredentialRequest",
data,
requestId: requestId,
}, (response) => {
if (response && response.error !== undefined) {
return reject(response.error);
}
resolve({
type: MessageType.CredentialGetResponse,
result: response.result,
});
});
}).finally(() => abortController.signal.removeEventListener("abort", abortHandler));
}
return undefined;
});
}
function run() {
return content_script_awaiter(this, void 0, void 0, function* () {
if (!(yield isFido2FeatureEnabled())) {
return;
}
initializeFido2ContentScript();
const port = chrome.runtime.connect({ name: "fido2ContentScriptReady" });
port.onDisconnect.addListener(() => {
// Cleanup the messenger and remove the event listener
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
messenger.destroy();
});
});
}
// Only run the script if the document is an HTML document
if (document.contentType === "text/html") {
void run();
}
/******/ })()
;

View File

@@ -0,0 +1,10 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
(function () {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
chrome.runtime.sendMessage({ command: "triggerFido2ContentScriptInjection" });
})();
/******/ })()
;

View File

@@ -0,0 +1,151 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ./src/tools/enums/fileless-import.enums.ts
const FilelessImportType = {
LP: "LP",
};
const FilelessImportPort = {
NotificationBar: "fileless-importer-notification-bar",
LpImporter: "lp-fileless-importer",
};
;// CONCATENATED MODULE: ./src/tools/content/lp-fileless-importer.ts
class LpFilelessImporter {
constructor() {
this.portMessageHandlers = {
verifyFeatureFlag: ({ message }) => this.handleFeatureFlagVerification(message),
triggerCsvDownload: () => this.triggerCsvDownload(),
startLpFilelessImport: () => this.startLpImport(),
};
/**
* Initializes the importing mechanism used to import the CSV file into Bitwarden.
* This is done by observing the DOM for the addition of the LP importer element.
*/
this.loadImporter = () => {
this.mutationObserver = new MutationObserver(this.handleMutation);
this.mutationObserver.observe(document.body, {
childList: true,
subtree: true,
});
};
/**
* Handles mutations that are observed by the mutation observer. When the exported data
* element is added to the DOM, the export data is extracted and the import prompt is
* displayed.
*
* @param mutations - The mutations that were observed.
*/
this.handleMutation = (mutations) => {
let textContent;
for (let index = 0; index < (mutations === null || mutations === void 0 ? void 0 : mutations.length); index++) {
const mutation = mutations[index];
textContent = Array.from(mutation.addedNodes)
.filter((node) => node.nodeName.toLowerCase() === "pre")
.map((node) => { var _a; return (_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim(); })
.find((text) => (text === null || text === void 0 ? void 0 : text.indexOf("url,username,password")) >= 0);
if (textContent) {
break;
}
}
if (textContent) {
this.exportData = textContent;
this.postPortMessage({ command: "displayLpImportNotification" });
this.mutationObserver.disconnect();
}
};
/**
* Handles messages that are sent from the background script.
*
* @param message - The message that was sent.
* @param port - The port that the message was sent from.
*/
this.handlePortMessage = (message, port) => {
const handler = this.portMessageHandlers[message.command];
if (!handler) {
return;
}
handler({ message, port });
};
}
/**
* Initializes the LP fileless importer.
*/
init() {
this.setupMessagePort();
}
/**
* Enacts behavior based on the feature flag verification message. If the feature flag is
* not enabled, the message port is disconnected. If the feature flag is enabled, the
* download of the CSV file is suppressed.
*
* @param message - The port message, contains the feature flag indicator.
*/
handleFeatureFlagVerification(message) {
var _a;
if (!message.filelessImportEnabled) {
(_a = this.messagePort) === null || _a === void 0 ? void 0 : _a.disconnect();
return;
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", this.loadImporter);
return;
}
this.loadImporter();
}
/**
* Posts a message to the LP importer to trigger the download of the CSV file.
*/
triggerCsvDownload() {
this.postWindowMessage({ command: "triggerCsvDownload" });
}
/**
* If the export data is present, sends a message to the background with
* the export data to start the import process.
*/
startLpImport() {
var _a;
if (!this.exportData) {
return;
}
this.postPortMessage({ command: "startLpImport", data: this.exportData });
(_a = this.messagePort) === null || _a === void 0 ? void 0 : _a.disconnect();
}
/**
* Posts a message to the background script.
*
* @param message - The message to post.
*/
postPortMessage(message) {
var _a;
(_a = this.messagePort) === null || _a === void 0 ? void 0 : _a.postMessage(message);
}
/**
* Posts a message to the global context of the page.
*
* @param message - The message to post.
*/
postWindowMessage(message) {
globalThis.postMessage(message, "https://lastpass.com");
}
/**
* Sets up the message port that is used to facilitate communication between the
* background script and the content script.
*/
setupMessagePort() {
this.messagePort = chrome.runtime.connect({ name: FilelessImportPort.LpImporter });
this.messagePort.onMessage.addListener(this.handlePortMessage);
}
}
(function () {
if (!globalThis.lpFilelessImporter) {
globalThis.lpFilelessImporter = new LpFilelessImporter();
globalThis.lpFilelessImporter.init();
}
})();
/******/ })()
;

View File

@@ -0,0 +1,14 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/**
* This script handles injection of the LP suppress import download script into the document.
* This is required for manifest v2, but will be removed when we migrate fully to manifest v3.
*/
(function (globalContext) {
const script = globalContext.document.createElement("script");
script.src = chrome.runtime.getURL("content/lp-suppress-import-download.js");
globalContext.document.documentElement.appendChild(script);
})(window);
/******/ })()
;

View File

@@ -0,0 +1,48 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/**
* Handles intercepting the injection of the CSV download link, and ensures the
* download of the script is suppressed until the user opts to download the file.
* The download is triggered by a window message sent from the LpFilelessImporter
* content script.
*/
(function (globalContext) {
let csvDownload = "";
let csvHref = "";
let isCsvDownloadTriggered = false;
const defaultAppendChild = Element.prototype.appendChild;
Element.prototype.appendChild = function (newChild) {
if (isAnchorElement(newChild) && newChild.download) {
csvDownload = newChild.download;
csvHref = newChild.href;
newChild.setAttribute("href", "javascript:void(0)");
newChild.setAttribute("download", "");
Element.prototype.appendChild = defaultAppendChild;
}
return defaultAppendChild.call(this, newChild);
};
function isAnchorElement(node) {
return node.nodeName.toLowerCase() === "a";
}
const handleWindowMessage = (event) => {
var _a;
const command = (_a = event.data) === null || _a === void 0 ? void 0 : _a.command;
if (event.source !== globalContext ||
command !== "triggerCsvDownload" ||
isCsvDownloadTriggered) {
return;
}
isCsvDownloadTriggered = true;
globalContext.removeEventListener("message", handleWindowMessage);
const anchor = globalContext.document.createElement("a");
anchor.setAttribute("href", csvHref);
anchor.setAttribute("download", csvDownload);
globalContext.document.body.appendChild(anchor);
anchor.click();
globalContext.document.body.removeChild(anchor);
};
globalContext.addEventListener("message", handleWindowMessage);
})(window);
/******/ })()
;

View File

@@ -0,0 +1,19 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
var __webpack_exports__ = {};
;// CONCATENATED MODULE: ../../libs/common/src/vault/enums/vault-onboarding.enum.ts
const VaultOnboardingMessages = {
HasBwInstalled: "hasBwInstalled",
checkBwInstalled: "checkIfBWExtensionInstalled",
};
;// CONCATENATED MODULE: ./src/vault/content/send-on-installed-message.ts
(function (globalContext) {
globalContext.postMessage({ command: VaultOnboardingMessages.HasBwInstalled });
})(window);
/******/ })()
;

View File

@@ -0,0 +1,10 @@
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
(function () {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
chrome.runtime.sendMessage({ command: "triggerAutofillScriptInjection" });
})();
/******/ })()
;

View File

@@ -0,0 +1,8 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */

View File

@@ -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

Some files were not shown because too many files have changed in this diff Show More