mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-8841] Passkeys script injection breaks loading of specific websites that are expecting an empty DOM on init (#10424)
* [PM-8841] Passkeys script injection breaks loading of specific websites that are expecting an empty DOM on init * [PM-8841] Implementing feature flag to allow for dynamic registration of the delayed page-script-append mv2 script
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import {
|
import {
|
||||||
AssertCredentialParams,
|
AssertCredentialParams,
|
||||||
CreateCredentialParams,
|
CreateCredentialParams,
|
||||||
@@ -54,6 +55,7 @@ describe("Fido2Background", () => {
|
|||||||
let fido2ClientService!: MockProxy<Fido2ClientService>;
|
let fido2ClientService!: MockProxy<Fido2ClientService>;
|
||||||
let vaultSettingsService!: MockProxy<VaultSettingsService>;
|
let vaultSettingsService!: MockProxy<VaultSettingsService>;
|
||||||
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
|
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
|
||||||
|
let configServiceMock!: MockProxy<ConfigService>;
|
||||||
let enablePasskeysMock$!: BehaviorSubject<boolean>;
|
let enablePasskeysMock$!: BehaviorSubject<boolean>;
|
||||||
let fido2Background!: Fido2Background;
|
let fido2Background!: Fido2Background;
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ describe("Fido2Background", () => {
|
|||||||
abortController = mock<AbortController>();
|
abortController = mock<AbortController>();
|
||||||
registeredContentScripsMock = mock<browser.contentScripts.RegisteredContentScript>();
|
registeredContentScripsMock = mock<browser.contentScripts.RegisteredContentScript>();
|
||||||
scriptInjectorServiceMock = mock<BrowserScriptInjectorService>();
|
scriptInjectorServiceMock = mock<BrowserScriptInjectorService>();
|
||||||
|
configServiceMock = mock<ConfigService>();
|
||||||
|
|
||||||
enablePasskeysMock$ = new BehaviorSubject(true);
|
enablePasskeysMock$ = new BehaviorSubject(true);
|
||||||
vaultSettingsService.enablePasskeys$ = enablePasskeysMock$;
|
vaultSettingsService.enablePasskeys$ = enablePasskeysMock$;
|
||||||
@@ -80,6 +83,7 @@ describe("Fido2Background", () => {
|
|||||||
fido2ClientService,
|
fido2ClientService,
|
||||||
vaultSettingsService,
|
vaultSettingsService,
|
||||||
scriptInjectorServiceMock,
|
scriptInjectorServiceMock,
|
||||||
|
configServiceMock,
|
||||||
);
|
);
|
||||||
fido2Background["abortManager"] = abortManagerMock;
|
fido2Background["abortManager"] = abortManagerMock;
|
||||||
abortManagerMock.runWithAbortController.mockImplementation((_requestId, runner) =>
|
abortManagerMock.runWithAbortController.mockImplementation((_requestId, runner) =>
|
||||||
@@ -110,6 +114,7 @@ describe("Fido2Background", () => {
|
|||||||
tabsQuerySpy.mockResolvedValueOnce([tabMock, secondTabMock, insecureTab, noUrlTab]);
|
tabsQuerySpy.mockResolvedValueOnce([tabMock, secondTabMock, insecureTab, noUrlTab]);
|
||||||
|
|
||||||
await fido2Background.injectFido2ContentScriptsInAllTabs();
|
await fido2Background.injectFido2ContentScriptsInAllTabs();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
|
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
|
||||||
tabId: tabMock.id,
|
tabId: tabMock.id,
|
||||||
@@ -133,6 +138,7 @@ describe("Fido2Background", () => {
|
|||||||
tabsQuerySpy.mockResolvedValueOnce([tabMock]);
|
tabsQuerySpy.mockResolvedValueOnce([tabMock]);
|
||||||
|
|
||||||
await fido2Background.injectFido2ContentScriptsInAllTabs();
|
await fido2Background.injectFido2ContentScriptsInAllTabs();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
|
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
|
||||||
tabId: tabMock.id,
|
tabId: tabMock.id,
|
||||||
@@ -206,6 +212,22 @@ describe("Fido2Background", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("registers the page-script-delay-append-mv2.js content script when the DelayFido2PageScriptInitWithinMv2 feature flag is enabled", async () => {
|
||||||
|
configServiceMock.getFeatureFlag.mockResolvedValue(true);
|
||||||
|
isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2);
|
||||||
|
|
||||||
|
enablePasskeysMock$.next(true);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(BrowserApi.registerContentScriptsMv2).toHaveBeenCalledWith({
|
||||||
|
js: [
|
||||||
|
{ file: Fido2ContentScript.PageScriptDelayAppend },
|
||||||
|
{ file: Fido2ContentScript.ContentScript },
|
||||||
|
],
|
||||||
|
...sharedRegistrationOptions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("unregisters any existing registered content scripts when the enablePasskeys setting is set to `false`", async () => {
|
it("unregisters any existing registered content scripts when the enablePasskeys setting is set to `false`", async () => {
|
||||||
isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2);
|
isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2);
|
||||||
fido2Background["registeredContentScripts"] = registeredContentScripsMock;
|
fido2Background["registeredContentScripts"] = registeredContentScripsMock;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { firstValueFrom, startWith } from "rxjs";
|
import { firstValueFrom, startWith } from "rxjs";
|
||||||
import { pairwise } from "rxjs/operators";
|
import { pairwise } from "rxjs/operators";
|
||||||
|
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import {
|
import {
|
||||||
AssertCredentialParams,
|
AssertCredentialParams,
|
||||||
AssertCredentialResult,
|
AssertCredentialResult,
|
||||||
@@ -50,6 +52,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
|
|||||||
private fido2ClientService: Fido2ClientService,
|
private fido2ClientService: Fido2ClientService,
|
||||||
private vaultSettingsService: VaultSettingsService,
|
private vaultSettingsService: VaultSettingsService,
|
||||||
private scriptInjectorService: ScriptInjectorService,
|
private scriptInjectorService: ScriptInjectorService,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,7 +135,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
|
|||||||
|
|
||||||
this.registeredContentScripts = await BrowserApi.registerContentScriptsMv2({
|
this.registeredContentScripts = await BrowserApi.registerContentScriptsMv2({
|
||||||
js: [
|
js: [
|
||||||
{ file: Fido2ContentScript.PageScriptAppend },
|
{ file: await this.getFido2PageScriptAppendFileName() },
|
||||||
{ file: Fido2ContentScript.ContentScript },
|
{ file: Fido2ContentScript.ContentScript },
|
||||||
],
|
],
|
||||||
...this.sharedRegistrationOptions,
|
...this.sharedRegistrationOptions,
|
||||||
@@ -176,7 +179,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
|
|||||||
void this.scriptInjectorService.inject({
|
void this.scriptInjectorService.inject({
|
||||||
tabId: tab.id,
|
tabId: tab.id,
|
||||||
injectDetails: { frame: "all_frames", ...this.sharedInjectionDetails },
|
injectDetails: { frame: "all_frames", ...this.sharedInjectionDetails },
|
||||||
mv2Details: { file: Fido2ContentScript.PageScriptAppend },
|
mv2Details: { file: await this.getFido2PageScriptAppendFileName() },
|
||||||
mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" },
|
mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,4 +356,20 @@ export class Fido2Background implements Fido2BackgroundInterface {
|
|||||||
|
|
||||||
this.fido2ContentScriptPortsSet.delete(port);
|
this.fido2ContentScriptPortsSet.delete(port);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file name of the page-script used within mv2. Will return the
|
||||||
|
* delayed append script if the associated feature flag is enabled.
|
||||||
|
*/
|
||||||
|
private async getFido2PageScriptAppendFileName() {
|
||||||
|
const shouldDelayInit = await this.configService.getFeatureFlag(
|
||||||
|
FeatureFlag.DelayFido2PageScriptInitWithinMv2,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldDelayInit) {
|
||||||
|
return Fido2ContentScript.PageScriptDelayAppend;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Fido2ContentScript.PageScriptAppend;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* This script handles injection of the FIDO2 override page script into the document.
|
||||||
|
* This is required for manifest v2, but will be removed when we migrate fully to manifest v3.
|
||||||
|
*/
|
||||||
|
import { Fido2ContentScript } from "../enums/fido2-content-script.enum";
|
||||||
|
|
||||||
|
(function (globalContext) {
|
||||||
|
if (globalContext.document.contentType !== "text/html") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalContext.document.readyState === "complete") {
|
||||||
|
loadScript();
|
||||||
|
} else {
|
||||||
|
globalContext.addEventListener("DOMContentLoaded", loadScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScript() {
|
||||||
|
const script = globalContext.document.createElement("script");
|
||||||
|
script.src = chrome.runtime.getURL(Fido2ContentScript.PageScript);
|
||||||
|
script.addEventListener("load", () => script.remove());
|
||||||
|
|
||||||
|
const scriptInsertionPoint =
|
||||||
|
globalContext.document.head || globalContext.document.documentElement;
|
||||||
|
scriptInsertionPoint.insertBefore(script, scriptInsertionPoint.firstChild);
|
||||||
|
}
|
||||||
|
})(globalThis);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export const Fido2ContentScript = {
|
export const Fido2ContentScript = {
|
||||||
PageScript: "content/fido2-page-script.js",
|
PageScript: "content/fido2-page-script.js",
|
||||||
PageScriptAppend: "content/fido2-page-script-append-mv2.js",
|
PageScriptAppend: "content/fido2-page-script-append-mv2.js",
|
||||||
|
PageScriptDelayAppend: "content/fido2-page-script-delay-append-mv2.js",
|
||||||
ContentScript: "content/fido2-content-script.js",
|
ContentScript: "content/fido2-content-script.js",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -1025,6 +1025,7 @@ export default class MainBackground {
|
|||||||
this.fido2ClientService,
|
this.fido2ClientService,
|
||||||
this.vaultSettingsService,
|
this.vaultSettingsService,
|
||||||
this.scriptInjectorService,
|
this.scriptInjectorService,
|
||||||
|
this.configService,
|
||||||
);
|
);
|
||||||
this.runtimeBackground = new RuntimeBackground(
|
this.runtimeBackground = new RuntimeBackground(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@@ -301,6 +301,8 @@ if (manifestVersion == 2) {
|
|||||||
"./src/tools/content/lp-suppress-import-download-script-append.mv2.ts";
|
"./src/tools/content/lp-suppress-import-download-script-append.mv2.ts";
|
||||||
mainConfig.entry["content/fido2-page-script-append-mv2"] =
|
mainConfig.entry["content/fido2-page-script-append-mv2"] =
|
||||||
"./src/autofill/fido2/content/fido2-page-script-append.mv2.ts";
|
"./src/autofill/fido2/content/fido2-page-script-append.mv2.ts";
|
||||||
|
mainConfig.entry["content/fido2-page-script-delay-append-mv2"] =
|
||||||
|
"./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts";
|
||||||
|
|
||||||
configs.push(mainConfig);
|
configs.push(mainConfig);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export enum FeatureFlag {
|
|||||||
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
|
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
|
||||||
EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub",
|
EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub",
|
||||||
GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor",
|
GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor",
|
||||||
|
DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||||
@@ -70,6 +71,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
|
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
|
||||||
[FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE,
|
[FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE,
|
||||||
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
|
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
|
||||||
|
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
|
||||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||||
|
|
||||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||||
|
|||||||
Reference in New Issue
Block a user