mirror of
https://github.com/bitwarden/browser
synced 2024-12-25 09:32:06 +01:00
[PM-2147] [BEEEP] Open login form used to unlock extension in a separate window instead of a tab (#5384)
* [PM-1796] The autofill keyboard shortcut does not prompt a user to unlock a locked extension within an incongito browsing session * [PM-1796] Implementing fixes for how we handle focus redirection when logging a user in and attempting to autofill within the Firefox Workspaces addon * [PM-1796] Removing the `openerTab` value from the createNewTab method within brwoserApi.ts * [PM-1796] Removing async declaration from createNewTab * [PM-1796] Removing unnecessary param from the call to openBitwardenExtrensionTab * [PM-2147] [BEEEP] Open login form used to unlock extension in a separate window instead of a tab * [PM-2147] [BEEEP] Open login form used to unlock extension in a separate window instead of a tab * [PM-2147] [BEEEP] Modifying the position where the window opens and starting cleanup of comments within implementation * [PM-2147] [BEEEP] Cleaning up comments within implementation * [PM-2147] [BEEEP] Removing unnecessary method * [PM-2147] [BEEEP] Removing package-lock changes * [PM-2147] [BEEEP] Cleaning up implementation * [PM-2147] [BEEEP] Reverting addition to the whitelist-capital-letters filter and updating named file * [PM-2147] [BEEEP] Reverting addition to the whitelist-capital-letters filter and updating named file * [PM-2147] [BEEEP] Adjusting implementation of notifications bar to trigger presentation on lock only when not adding a new vault item * [PM-2147] [BEEEP] Adjusting implementation of how we open a login prompt window to ensure we are showing the address bar to the user * [PM-2147] [BEEEP] Modifying the method closeBitwardenLoginPromptWindow to not check for a popup type window * [PM-2147] [BEEEP] Fixing bug where notification bar does not close when unlocking vault * [PM-2147] [BEEEP] Adjusting placement of method BrowserApi.getWindow to have it present closer to getTab * [PM-2147] [BEEEP] Implementing a sepearate service BrowserPopoutService that will maintain the most recently created popouts and selectively remove those when re-opening the login prompt * [PM-2147] [BEEEP] Modifying position of BrowserPopoutWindowService * [PM-2147] [BEEEP] Modifying position of BrowserPopoutWindowService * [PM-2147] [BEEEP] Modifying how we handle identifying a single use popout
This commit is contained in:
parent
a05b4fd094
commit
50b3e40a05
@ -630,6 +630,12 @@
|
||||
"notificationChangeSave": {
|
||||
"message": "Update"
|
||||
},
|
||||
"notificationUnlockDesc": {
|
||||
"message": "Unlock your Bitwarden vault to complete the auto-fill request."
|
||||
},
|
||||
"notificationUnlock": {
|
||||
"message": "Unlock"
|
||||
},
|
||||
"enableContextMenuItem": {
|
||||
"message": "Show context menu options"
|
||||
},
|
||||
|
@ -30,6 +30,7 @@ export default class ContextMenusBackground {
|
||||
msg.data.commandToRetry.msg.data,
|
||||
msg.data.commandToRetry.sender.tab
|
||||
);
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
|
||||
import AddUnlockVaultQueueMessage from "../../background/models/add-unlock-vault-queue-message";
|
||||
import AddChangePasswordQueueMessage from "../../background/models/addChangePasswordQueueMessage";
|
||||
import AddLoginQueueMessage from "../../background/models/addLoginQueueMessage";
|
||||
import AddLoginRuntimeMessage from "../../background/models/addLoginRuntimeMessage";
|
||||
@ -23,7 +24,11 @@ import { BrowserStateService } from "../../platform/services/abstractions/browse
|
||||
import { AutofillService } from "../services/abstractions/autofill.service";
|
||||
|
||||
export default class NotificationBackground {
|
||||
private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = [];
|
||||
private notificationQueue: (
|
||||
| AddLoginQueueMessage
|
||||
| AddChangePasswordQueueMessage
|
||||
| AddUnlockVaultQueueMessage
|
||||
)[] = [];
|
||||
|
||||
constructor(
|
||||
private autofillService: AutofillService,
|
||||
@ -53,10 +58,7 @@ export default class NotificationBackground {
|
||||
async processMessage(msg: any, sender: chrome.runtime.MessageSender) {
|
||||
switch (msg.command) {
|
||||
case "unlockCompleted":
|
||||
if (msg.data.target !== "notification.background") {
|
||||
return;
|
||||
}
|
||||
await this.processMessage(msg.data.commandToRetry.msg, msg.data.commandToRetry.sender);
|
||||
await this.handleUnlockCompleted(msg.data, sender);
|
||||
break;
|
||||
case "bgGetDataForTab":
|
||||
await this.getDataForTab(sender.tab, msg.responseCommand);
|
||||
@ -82,7 +84,9 @@ export default class NotificationBackground {
|
||||
if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) {
|
||||
const retryMessage: LockedVaultPendingNotificationsItem = {
|
||||
commandToRetry: {
|
||||
msg: msg,
|
||||
msg: {
|
||||
command: msg,
|
||||
},
|
||||
sender: sender,
|
||||
},
|
||||
target: "notification.background",
|
||||
@ -114,6 +118,9 @@ export default class NotificationBackground {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "promptForLogin":
|
||||
await this.unlockVault(sender.tab);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -181,6 +188,14 @@ export default class NotificationBackground {
|
||||
webVaultURL: await this.environmentService.getWebVaultUrl(),
|
||||
},
|
||||
});
|
||||
} else if (this.notificationQueue[i].type === NotificationQueueMessageType.UnlockVault) {
|
||||
BrowserApi.tabSendMessageData(tab, "openNotificationBar", {
|
||||
type: "unlock",
|
||||
typeData: {
|
||||
isVaultLocked: this.notificationQueue[i].wasVaultLocked,
|
||||
theme: await this.getCurrentTheme(),
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -305,6 +320,20 @@ export default class NotificationBackground {
|
||||
}
|
||||
}
|
||||
|
||||
private async unlockVault(tab: chrome.tabs.Tab) {
|
||||
const currentAuthStatus = await this.authService.getAuthStatus();
|
||||
if (currentAuthStatus !== AuthenticationStatus.Locked || this.notificationQueue.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loginDomain = Utils.getDomain(tab.url);
|
||||
if (!loginDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pushUnlockVaultToQueue(loginDomain, tab);
|
||||
}
|
||||
|
||||
private async pushChangePasswordToQueue(
|
||||
cipherId: string,
|
||||
loginDomain: string,
|
||||
@ -327,6 +356,20 @@ export default class NotificationBackground {
|
||||
await this.checkNotificationQueue(tab);
|
||||
}
|
||||
|
||||
private async pushUnlockVaultToQueue(loginDomain: string, tab: chrome.tabs.Tab) {
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
const message: AddUnlockVaultQueueMessage = {
|
||||
type: NotificationQueueMessageType.UnlockVault,
|
||||
domain: loginDomain,
|
||||
tabId: tab.id,
|
||||
expires: new Date(new Date().getTime() + 0.5 * 60000), // 30 seconds
|
||||
wasVaultLocked: true,
|
||||
};
|
||||
this.notificationQueue.push(message);
|
||||
await this.checkNotificationQueue(tab);
|
||||
this.removeTabFromNotificationQueue(tab);
|
||||
}
|
||||
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
@ -463,4 +506,22 @@ export default class NotificationBackground {
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership)
|
||||
);
|
||||
}
|
||||
|
||||
private async handleUnlockCompleted(
|
||||
messageData: LockedVaultPendingNotificationsItem,
|
||||
sender: chrome.runtime.MessageSender
|
||||
): Promise<void> {
|
||||
if (messageData.commandToRetry.msg.command === "autofill_login") {
|
||||
await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar");
|
||||
}
|
||||
|
||||
if (messageData.target !== "notification.background") {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.processMessage(
|
||||
messageData.commandToRetry.msg.command,
|
||||
messageData.commandToRetry.sender
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="template-unlock">
|
||||
<div class="inner-wrapper">
|
||||
<div id="unlock-text"></div>
|
||||
<div>
|
||||
<button type="button" id="unlock-vault" class="primary"></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</html>
|
||||
|
@ -28,6 +28,8 @@ function load() {
|
||||
notificationEdit: chrome.i18n.getMessage("edit"),
|
||||
notificationChangeSave: chrome.i18n.getMessage("notificationChangeSave"),
|
||||
notificationChangeDesc: chrome.i18n.getMessage("notificationChangeDesc"),
|
||||
notificationUnlock: chrome.i18n.getMessage("notificationUnlock"),
|
||||
notificationUnlockDesc: chrome.i18n.getMessage("notificationUnlockDesc"),
|
||||
};
|
||||
|
||||
const logoLink = document.getElementById("logo-link") as HTMLAnchorElement;
|
||||
@ -72,6 +74,13 @@ function load() {
|
||||
|
||||
changeTemplate.content.getElementById("change-text").textContent = i18n.notificationChangeDesc;
|
||||
|
||||
const unlockTemplate = document.getElementById("template-unlock") as HTMLTemplateElement;
|
||||
|
||||
const unlockButton = unlockTemplate.content.getElementById("unlock-vault");
|
||||
unlockButton.textContent = i18n.notificationUnlock;
|
||||
|
||||
unlockTemplate.content.getElementById("unlock-text").textContent = i18n.notificationUnlockDesc;
|
||||
|
||||
// i18n for body content
|
||||
const closeButton = document.getElementById("close-button");
|
||||
closeButton.title = i18n.close;
|
||||
@ -80,6 +89,8 @@ function load() {
|
||||
handleTypeAdd();
|
||||
} else if (getQueryVariable("type") === "change") {
|
||||
handleTypeChange();
|
||||
} else if (getQueryVariable("type") === "unlock") {
|
||||
handleTypeUnlock();
|
||||
}
|
||||
|
||||
closeButton.addEventListener("click", (e) => {
|
||||
@ -172,6 +183,17 @@ function handleTypeChange() {
|
||||
});
|
||||
}
|
||||
|
||||
function handleTypeUnlock() {
|
||||
setContent(document.getElementById("template-unlock") as HTMLTemplateElement);
|
||||
|
||||
const unlockButton = document.getElementById("unlock-vault");
|
||||
unlockButton.addEventListener("click", (e) => {
|
||||
sendPlatformMessage({
|
||||
command: "bgReopenPromptForLogin",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setContent(template: HTMLTemplateElement) {
|
||||
const content = document.getElementById("content");
|
||||
while (content.firstChild) {
|
||||
|
@ -114,6 +114,7 @@ import { Account } from "../models/account";
|
||||
import { BrowserApi } from "../platform/browser/browser-api";
|
||||
import { flagEnabled } from "../platform/flags";
|
||||
import { UpdateBadge } from "../platform/listeners/update-badge";
|
||||
import BrowserPopoutWindowService from "../platform/popup/browser-popout-window.service";
|
||||
import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service";
|
||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||
@ -195,6 +196,7 @@ export default class MainBackground {
|
||||
cipherContextMenuHandler: CipherContextMenuHandler;
|
||||
configService: ConfigServiceAbstraction;
|
||||
configApiService: ConfigApiServiceAbstraction;
|
||||
browserPopoutWindowService: BrowserPopoutWindowService;
|
||||
|
||||
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
|
||||
backgroundWindow = window;
|
||||
@ -512,6 +514,7 @@ export default class MainBackground {
|
||||
this.authService,
|
||||
this.environmentService
|
||||
);
|
||||
this.browserPopoutWindowService = new BrowserPopoutWindowService();
|
||||
|
||||
const systemUtilsServiceReloadCallback = () => {
|
||||
const forceWindowReload =
|
||||
@ -543,7 +546,8 @@ export default class MainBackground {
|
||||
this.environmentService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.configService
|
||||
this.configService,
|
||||
this.browserPopoutWindowService
|
||||
);
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground(
|
||||
this.cryptoService,
|
||||
|
@ -0,0 +1,6 @@
|
||||
import NotificationQueueMessage from "./notificationQueueMessage";
|
||||
import { NotificationQueueMessageType } from "./notificationQueueMessageType";
|
||||
|
||||
export default class AddUnlockVaultQueueMessage extends NotificationQueueMessage {
|
||||
type: NotificationQueueMessageType.UnlockVault;
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
export default class LockedVaultPendingNotificationsItem {
|
||||
commandToRetry: {
|
||||
msg: any;
|
||||
msg: {
|
||||
command: string;
|
||||
data?: any;
|
||||
};
|
||||
sender: chrome.runtime.MessageSender;
|
||||
};
|
||||
target: string;
|
||||
|
@ -1,4 +1,5 @@
|
||||
export enum NotificationQueueMessageType {
|
||||
AddLogin = 0,
|
||||
ChangePassword = 1,
|
||||
UnlockVault = 2,
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { AutofillService } from "../autofill/services/abstractions/autofill.service";
|
||||
import { BrowserApi } from "../platform/browser/browser-api";
|
||||
import { BrowserPopoutWindowService } from "../platform/popup/abstractions/browser-popout-window.service";
|
||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
||||
|
||||
@ -30,7 +31,8 @@ export default class RuntimeBackground {
|
||||
private environmentService: BrowserEnvironmentService,
|
||||
private messagingService: MessagingService,
|
||||
private logService: LogService,
|
||||
private configService: ConfigServiceAbstraction
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private browserPopoutWindowService: BrowserPopoutWindowService
|
||||
) {
|
||||
// onInstalled listener must be wired up before anything else, so we do it in the ctor
|
||||
chrome.runtime.onInstalled.addListener((details: any) => {
|
||||
@ -66,7 +68,7 @@ export default class RuntimeBackground {
|
||||
|
||||
if (this.lockedVaultPendingNotifications?.length > 0) {
|
||||
item = this.lockedVaultPendingNotifications.pop();
|
||||
BrowserApi.closeBitwardenExtensionTab();
|
||||
await this.browserPopoutWindowService.closeLoginPrompt();
|
||||
}
|
||||
|
||||
await this.main.refreshBadge();
|
||||
@ -105,7 +107,8 @@ export default class RuntimeBackground {
|
||||
await this.main.openPopup();
|
||||
break;
|
||||
case "promptForLogin":
|
||||
BrowserApi.openBitwardenExtensionTab("popup/index.html", true);
|
||||
case "bgReopenPromptForLogin":
|
||||
await this.browserPopoutWindowService.openLoginPrompt(sender.tab?.windowId);
|
||||
break;
|
||||
case "openAddEditCipher": {
|
||||
const addEditCipherUrl =
|
||||
|
@ -17,6 +17,24 @@ export class BrowserApi {
|
||||
return chrome.runtime.getManifest().manifest_version;
|
||||
}
|
||||
|
||||
static getWindow(windowId?: number): Promise<chrome.windows.Window> | void {
|
||||
if (!windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) =>
|
||||
chrome.windows.get(windowId, { populate: true }, (window) => resolve(window))
|
||||
);
|
||||
}
|
||||
|
||||
static async createWindow(options: chrome.windows.CreateData): Promise<chrome.windows.Window> {
|
||||
return new Promise((resolve) =>
|
||||
chrome.windows.create(options, (window) => {
|
||||
resolve(window);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
static async getTabFromCurrentWindowId(): Promise<chrome.tabs.Tab> | null {
|
||||
return await BrowserApi.tabsQueryFirst({
|
||||
active: true,
|
||||
@ -105,6 +123,10 @@ export class BrowserApi {
|
||||
chrome.tabs.sendMessage<TabMessage, T>(tabId, message, options, responseCallback);
|
||||
}
|
||||
|
||||
static async removeTab(tabId: number) {
|
||||
await chrome.tabs.remove(tabId);
|
||||
}
|
||||
|
||||
static async getPrivateModeWindows(): Promise<browser.windows.Window[]> {
|
||||
return (await browser.windows.getAll()).filter((win) => win.incognito);
|
||||
}
|
||||
@ -165,7 +187,7 @@ export class BrowserApi {
|
||||
}
|
||||
|
||||
const tabToClose = tabs[tabs.length - 1];
|
||||
chrome.tabs.remove(tabToClose.id);
|
||||
BrowserApi.removeTab(tabToClose.id);
|
||||
}
|
||||
|
||||
private static registeredMessageListeners: any[] = [];
|
||||
|
@ -0,0 +1,6 @@
|
||||
interface BrowserPopoutWindowService {
|
||||
openLoginPrompt(senderWindowId: number): Promise<void>;
|
||||
closeLoginPrompt(): Promise<void>;
|
||||
}
|
||||
|
||||
export { BrowserPopoutWindowService };
|
@ -0,0 +1,64 @@
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
|
||||
import { BrowserPopoutWindowService as BrowserPopupWindowServiceInterface } from "./abstractions/browser-popout-window.service";
|
||||
|
||||
class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface {
|
||||
private singleActionPopoutTabIds: Record<string, number> = {};
|
||||
private defaultPopoutWindowOptions: chrome.windows.CreateData = {
|
||||
type: "normal",
|
||||
focused: true,
|
||||
width: 500,
|
||||
height: 800,
|
||||
};
|
||||
|
||||
async openLoginPrompt(senderWindowId: number) {
|
||||
await this.closeLoginPrompt();
|
||||
await this.openPopoutWindow(
|
||||
senderWindowId,
|
||||
"popup/index.html?uilocation=popout",
|
||||
"loginPrompt"
|
||||
);
|
||||
}
|
||||
|
||||
async closeLoginPrompt() {
|
||||
await this.closeSingleActionPopout("loginPrompt");
|
||||
}
|
||||
|
||||
private async openPopoutWindow(
|
||||
senderWindowId: number,
|
||||
popupWindowURL: string,
|
||||
singleActionPopoutKey: string
|
||||
) {
|
||||
const senderWindow = senderWindowId && (await BrowserApi.getWindow(senderWindowId));
|
||||
const url = chrome.extension.getURL(popupWindowURL);
|
||||
const offsetRight = 15;
|
||||
const offsetTop = 90;
|
||||
const popupWidth = this.defaultPopoutWindowOptions.width;
|
||||
const windowOptions = senderWindow
|
||||
? {
|
||||
...this.defaultPopoutWindowOptions,
|
||||
url,
|
||||
left: senderWindow.left + senderWindow.width - popupWidth - offsetRight,
|
||||
top: senderWindow.top + offsetTop,
|
||||
}
|
||||
: { ...this.defaultPopoutWindowOptions, url };
|
||||
|
||||
const popupWindow = await BrowserApi.createWindow(windowOptions);
|
||||
|
||||
if (!singleActionPopoutKey) {
|
||||
return;
|
||||
}
|
||||
this.singleActionPopoutTabIds[singleActionPopoutKey] = popupWindow?.tabs[0].id;
|
||||
}
|
||||
|
||||
private async closeSingleActionPopout(popoutKey: string) {
|
||||
const tabId = this.singleActionPopoutTabIds[popoutKey];
|
||||
if (!tabId) {
|
||||
return;
|
||||
}
|
||||
await BrowserApi.removeTab(tabId);
|
||||
this.singleActionPopoutTabIds[popoutKey] = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default BrowserPopoutWindowService;
|
Loading…
Reference in New Issue
Block a user