mirror of
https://github.com/bitwarden/browser
synced 2026-02-18 18:33:50 +00:00
Extension token URL service.
This commit is contained in:
@@ -53,6 +53,7 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import { ExtensionUrlTokenService } from "../../platform/services/extension-url-token.service";
|
||||
// FIXME (PM-22628): Popup imports are forbidden in background
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
@@ -236,6 +237,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
private accountService: AccountService,
|
||||
private generatePasswordCallback: () => Promise<string>,
|
||||
private addPasswordCallback: (password: string) => Promise<void>,
|
||||
private extensionUrlTokenService: ExtensionUrlTokenService,
|
||||
) {
|
||||
this.initOverlayEventObservables();
|
||||
}
|
||||
@@ -2947,7 +2949,7 @@ export class OverlayBackground implements OverlayBackgroundInterface {
|
||||
|
||||
this.postMessageToPort(port, {
|
||||
command: `initAutofillInlineMenu${isInlineMenuListPort ? "List" : "Button"}`,
|
||||
iframeUrl: chrome.runtime.getURL(
|
||||
iframeUrl: this.extensionUrlTokenService.createTokenUrl(
|
||||
`overlay/menu-${isInlineMenuListPort ? "list" : "button"}.html`,
|
||||
),
|
||||
pageTitle: chrome.i18n.getMessage(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { validateExtensionUrl } from "@bitwarden/browser/platform/utils/extension-url-token.utils";
|
||||
import { EVENTS } from "@bitwarden/common/autofill/constants";
|
||||
|
||||
import { setElementStyles } from "../../../../utils";
|
||||
@@ -60,7 +61,14 @@ export class AutofillInlineMenuContainer {
|
||||
*
|
||||
* @param message - The message containing the iframe url and page title.
|
||||
*/
|
||||
private handleInitInlineMenuIframe(message: InitAutofillInlineMenuElementMessage) {
|
||||
private async handleInitInlineMenuIframe(message: InitAutofillInlineMenuElementMessage) {
|
||||
const isValidUrl = await validateExtensionUrl(message.iframeUrl);
|
||||
if (!isValidUrl) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Token not found");
|
||||
return;
|
||||
}
|
||||
|
||||
this.defaultIframeAttributes.src = message.iframeUrl;
|
||||
this.defaultIframeAttributes.title = message.pageTitle;
|
||||
this.portName = message.portName;
|
||||
|
||||
@@ -317,6 +317,7 @@ import BrowserInitialInstallService from "../platform/services/browser-initial-i
|
||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
|
||||
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
|
||||
import { ExtensionUrlTokenService } from "../platform/services/extension-url-token.service";
|
||||
import I18nService from "../platform/services/i18n.service";
|
||||
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
|
||||
import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service";
|
||||
@@ -452,6 +453,7 @@ export default class MainBackground {
|
||||
syncServiceListener: SyncServiceListener;
|
||||
browserInitialInstallService: BrowserInitialInstallService;
|
||||
backgroundSyncService: BackgroundSyncService;
|
||||
extensionUrlTokenService: ExtensionUrlTokenService;
|
||||
|
||||
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
|
||||
themeStateService: DefaultThemeStateService;
|
||||
@@ -1140,6 +1142,8 @@ export default class MainBackground {
|
||||
|
||||
this.browserInitialInstallService = new BrowserInitialInstallService(this.stateProvider);
|
||||
|
||||
this.extensionUrlTokenService = new ExtensionUrlTokenService();
|
||||
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
const registration = (self as unknown as { registration: ServiceWorkerRegistration })
|
||||
?.registration;
|
||||
@@ -1962,6 +1966,7 @@ export default class MainBackground {
|
||||
this.accountService,
|
||||
() => this.generatePassword(),
|
||||
(password) => this.addPasswordToHistory(password),
|
||||
this.extensionUrlTokenService,
|
||||
);
|
||||
|
||||
this.autofillBadgeUpdaterService = new AutofillBadgeUpdaterService(
|
||||
|
||||
@@ -80,6 +80,9 @@ export default class RuntimeBackground {
|
||||
BiometricsCommands.GetBiometricsStatusForUser,
|
||||
BiometricsCommands.CanEnableBiometricUnlock,
|
||||
"getUserPremiumStatus",
|
||||
"createTokenExtensionUrl",
|
||||
"validateExtensionUrl",
|
||||
"revokeExtensionUrlToken",
|
||||
];
|
||||
|
||||
if (messagesWithResponse.includes(msg.command)) {
|
||||
@@ -213,6 +216,16 @@ export default class RuntimeBackground {
|
||||
);
|
||||
return result;
|
||||
}
|
||||
case "createTokenExtensionUrl": {
|
||||
return this.main.extensionUrlTokenService.createTokenUrl(msg.path);
|
||||
}
|
||||
case "validateExtensionUrl": {
|
||||
return this.main.extensionUrlTokenService.validateUrl(msg.url);
|
||||
}
|
||||
case "revokeExtensionUrlToken": {
|
||||
this.main.extensionUrlTokenService.revokeToken(msg.url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export abstract class ExtensionUrlTokenService {
|
||||
abstract generateToken(): string;
|
||||
|
||||
abstract createTokenUrl(path: string): string;
|
||||
|
||||
abstract validateUrl(url: string): boolean;
|
||||
|
||||
abstract revokeToken(url: string): void;
|
||||
|
||||
abstract extractToken(url: string): string | null;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ExtensionUrlTokenService as ExtensionUrlTokenServiceAbstraction } from "./abstractions/extension-url-token.service";
|
||||
|
||||
export class ExtensionUrlTokenService implements ExtensionUrlTokenServiceAbstraction {
|
||||
private validTokens = new Set<string>();
|
||||
|
||||
generateToken(): string {
|
||||
const token = crypto.randomUUID();
|
||||
this.validTokens.add(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
createTokenUrl(path: string): string {
|
||||
const token = this.generateToken();
|
||||
const baseUrl = chrome.runtime.getURL(path);
|
||||
const url = new URL(baseUrl);
|
||||
url.searchParams.set("token", token);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
validateUrl(url: string): boolean {
|
||||
const token = this.extractToken(url);
|
||||
return token !== null && this.validTokens.has(token);
|
||||
}
|
||||
|
||||
revokeToken(url: string): void {
|
||||
const token = this.extractToken(url);
|
||||
if (token) {
|
||||
this.validTokens.delete(token);
|
||||
}
|
||||
}
|
||||
|
||||
extractToken(url: string): string | null {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.searchParams.get("token");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
apps/browser/src/platform/utils/extension-url-token.utils.ts
Normal file
36
apps/browser/src/platform/utils/extension-url-token.utils.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export async function createTokenExtensionUrl(path: string): Promise<string> {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
command: "createTokenExtensionUrl",
|
||||
path: path,
|
||||
});
|
||||
|
||||
if (response?.error) {
|
||||
throw new Error(response.error.message || "Failed to create token URL");
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export async function validateExtensionUrl(url: string): Promise<boolean> {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
command: "validateExtensionUrl",
|
||||
url: url,
|
||||
});
|
||||
|
||||
if (response?.error) {
|
||||
throw new Error(response.error.message || "Failed to validate URL");
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export async function revokeExtensionUrlToken(url: string): Promise<void> {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
command: "revokeExtensionUrlToken",
|
||||
url: url,
|
||||
});
|
||||
|
||||
if (response?.error) {
|
||||
throw new Error(response.error.message || "Failed to revoke token");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user