[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:
Cesar Gonzalez 2024-08-07 10:41:30 -05:00 committed by GitHub
parent 66d9ab5dc0
commit f51d1ba101
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 76 additions and 2 deletions

View File

@ -1,6 +1,7 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import {
AssertCredentialParams,
CreateCredentialParams,
@ -54,6 +55,7 @@ describe("Fido2Background", () => {
let fido2ClientService!: MockProxy<Fido2ClientService>;
let vaultSettingsService!: MockProxy<VaultSettingsService>;
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
let configServiceMock!: MockProxy<ConfigService>;
let enablePasskeysMock$!: BehaviorSubject<boolean>;
let fido2Background!: Fido2Background;
@ -71,6 +73,7 @@ describe("Fido2Background", () => {
abortController = mock<AbortController>();
registeredContentScripsMock = mock<browser.contentScripts.RegisteredContentScript>();
scriptInjectorServiceMock = mock<BrowserScriptInjectorService>();
configServiceMock = mock<ConfigService>();
enablePasskeysMock$ = new BehaviorSubject(true);
vaultSettingsService.enablePasskeys$ = enablePasskeysMock$;
@ -80,6 +83,7 @@ describe("Fido2Background", () => {
fido2ClientService,
vaultSettingsService,
scriptInjectorServiceMock,
configServiceMock,
);
fido2Background["abortManager"] = abortManagerMock;
abortManagerMock.runWithAbortController.mockImplementation((_requestId, runner) =>
@ -110,6 +114,7 @@ describe("Fido2Background", () => {
tabsQuerySpy.mockResolvedValueOnce([tabMock, secondTabMock, insecureTab, noUrlTab]);
await fido2Background.injectFido2ContentScriptsInAllTabs();
await flushPromises();
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
tabId: tabMock.id,
@ -133,6 +138,7 @@ describe("Fido2Background", () => {
tabsQuerySpy.mockResolvedValueOnce([tabMock]);
await fido2Background.injectFido2ContentScriptsInAllTabs();
await flushPromises();
expect(scriptInjectorServiceMock.inject).toHaveBeenCalledWith({
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 () => {
isManifestVersionSpy.mockImplementation((manifestVersion) => manifestVersion === 2);
fido2Background["registeredContentScripts"] = registeredContentScripsMock;

View File

@ -1,6 +1,8 @@
import { firstValueFrom, startWith } from "rxjs";
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 {
AssertCredentialParams,
AssertCredentialResult,
@ -50,6 +52,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
private fido2ClientService: Fido2ClientService,
private vaultSettingsService: VaultSettingsService,
private scriptInjectorService: ScriptInjectorService,
private configService: ConfigService,
) {}
/**
@ -132,7 +135,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
this.registeredContentScripts = await BrowserApi.registerContentScriptsMv2({
js: [
{ file: Fido2ContentScript.PageScriptAppend },
{ file: await this.getFido2PageScriptAppendFileName() },
{ file: Fido2ContentScript.ContentScript },
],
...this.sharedRegistrationOptions,
@ -176,7 +179,7 @@ export class Fido2Background implements Fido2BackgroundInterface {
void this.scriptInjectorService.inject({
tabId: tab.id,
injectDetails: { frame: "all_frames", ...this.sharedInjectionDetails },
mv2Details: { file: Fido2ContentScript.PageScriptAppend },
mv2Details: { file: await this.getFido2PageScriptAppendFileName() },
mv3Details: { file: Fido2ContentScript.PageScript, world: "MAIN" },
});
@ -353,4 +356,20 @@ export class Fido2Background implements Fido2BackgroundInterface {
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;
}
}

View File

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

View File

@ -1,6 +1,7 @@
export const Fido2ContentScript = {
PageScript: "content/fido2-page-script.js",
PageScriptAppend: "content/fido2-page-script-append-mv2.js",
PageScriptDelayAppend: "content/fido2-page-script-delay-append-mv2.js",
ContentScript: "content/fido2-content-script.js",
} as const;

View File

@ -1025,6 +1025,7 @@ export default class MainBackground {
this.fido2ClientService,
this.vaultSettingsService,
this.scriptInjectorService,
this.configService,
);
this.runtimeBackground = new RuntimeBackground(
this,

View File

@ -301,6 +301,8 @@ if (manifestVersion == 2) {
"./src/tools/content/lp-suppress-import-download-script-append.mv2.ts";
mainConfig.entry["content/fido2-page-script-append-mv2"] =
"./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);
} else {

View File

@ -30,6 +30,7 @@ export enum FeatureFlag {
UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub",
GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor",
DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@ -70,6 +71,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
[FeatureFlag.EnableUpgradePasswordManagerSub]: FALSE,
[FeatureFlag.GenerateIdentityFillScriptRefactor]: FALSE,
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;