{{ "noTwoStepProviders" | i18n }}
diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index 678675ba389..c0af31d25e5 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -1,25 +1,27 @@ -import { Component } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { first } from "rxjs/operators"; import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; -import { DialogServiceAbstraction, SimpleDialogType } from "@bitwarden/angular/services/dialog"; +import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AppIdService } from "@bitwarden/common/abstractions/appId.service"; -import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; -import { StateService } from "@bitwarden/common/abstractions/state.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { DialogService } from "@bitwarden/components"; -import { BrowserApi } from "../../browser/browserApi"; +import { BrowserApi } from "../../platform/browser/browser-api"; import { PopupUtilsService } from "../../popup/services/popup-utils.service"; const BroadcasterSubscriptionId = "TwoFactorComponent"; @@ -48,7 +50,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { twoFactorService: TwoFactorService, appIdService: AppIdService, loginService: LoginService, - private dialogService: DialogServiceAbstraction + configService: ConfigServiceAbstraction, + private dialogService: DialogService, + @Inject(WINDOW) protected win: Window ) { super( authService, @@ -56,19 +60,28 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { i18nService, apiService, platformUtilsService, - window, + win, environmentService, stateService, route, logService, twoFactorService, appIdService, - loginService + loginService, + configService ); - super.onSuccessfulLogin = () => { - this.loginService.clearValues(); - return syncService.fullSync(true); + super.onSuccessfulLogin = async () => { + syncService.fullSync(true); }; + + super.onSuccessfulLoginTde = async () => { + syncService.fullSync(true); + }; + + super.onSuccessfulLoginTdeNavigate = async () => { + this.win.close(); + }; + super.successRoute = "/tabs/vault"; // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe this.webAuthnNewTab = true; @@ -107,7 +120,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "warning" }, content: { key: "popup2faCloseMessage" }, - type: SimpleDialogType.WARNING, + type: "warning", }); if (confirmed) { this.popupUtilsService.popOut(window); @@ -117,11 +130,19 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.queryParams.pipe(first()).subscribe(async (qParams) => { if (qParams.sso === "true") { - super.onSuccessfulLogin = () => { - BrowserApi.reloadOpenWindows(); - const thisWindow = window.open("", "_self"); - thisWindow.close(); - return this.syncService.fullSync(true); + super.onSuccessfulLogin = async () => { + // This is not awaited so we don't pause the application while the sync is happening. + // This call is executed by the service that lives in the background script so it will continue + // the sync even if this tab closes. + this.syncService.fullSync(true); + + // Force sidebars (FF && Opera) to reload while exempting current window + // because we are just going to close the current window. + BrowserApi.reloadOpenWindows(true); + + // We don't need this window anymore because the intent is for the user to be left + // on the web vault screen which tells them to continue in the browser extension (sidebar or popup) + BrowserApi.closeBitwardenExtensionTab(); }; } }); @@ -137,7 +158,15 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } anotherMethod() { - this.router.navigate(["2fa-options"]); + const sso = this.route.snapshot.queryParamMap.get("sso") === "true"; + + if (sso) { + // We must persist this so when the user returns to the 2FA comp, the + // proper onSuccessfulLogin logic is executed. + this.router.navigate(["2fa-options"], { queryParams: { sso: true } }); + } else { + this.router.navigate(["2fa-options"]); + } } async isLinux() { diff --git a/apps/browser/src/autofill/background/context-menus.background.ts b/apps/browser/src/autofill/background/context-menus.background.ts index 9d04571a7c4..bc26353cbd9 100644 --- a/apps/browser/src/autofill/background/context-menus.background.ts +++ b/apps/browser/src/autofill/background/context-menus.background.ts @@ -1,5 +1,5 @@ import LockedVaultPendingNotificationsItem from "../../background/models/lockedVaultPendingNotificationsItem"; -import { BrowserApi } from "../../browser/browserApi"; +import { BrowserApi } from "../../platform/browser/browser-api"; import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; export default class ContextMenusBackground { @@ -30,6 +30,7 @@ export default class ContextMenusBackground { msg.data.commandToRetry.msg.data, msg.data.commandToRetry.sender.tab ); + await BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar"); } } ); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index b7cdfd97929..73bdc2cd16f 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -5,24 +5,30 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ThemeType } from "@bitwarden/common/enums"; -import { Utils } from "@bitwarden/common/misc/utils"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; 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"; import ChangePasswordRuntimeMessage from "../../background/models/changePasswordRuntimeMessage"; import LockedVaultPendingNotificationsItem from "../../background/models/lockedVaultPendingNotificationsItem"; import { NotificationQueueMessageType } from "../../background/models/notificationQueueMessageType"; -import { BrowserApi } from "../../browser/browserApi"; -import { BrowserStateService } from "../../services/abstractions/browser-state.service"; +import { BrowserApi } from "../../platform/browser/browser-api"; +import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service"; import { AutofillService } from "../services/abstractions/autofill.service"; export default class NotificationBackground { - private notificationQueue: (AddLoginQueueMessage | AddChangePasswordQueueMessage)[] = []; + private notificationQueue: ( + | AddLoginQueueMessage + | AddChangePasswordQueueMessage + | AddUnlockVaultQueueMessage + )[] = []; constructor( private autofillService: AutofillService, @@ -30,7 +36,8 @@ export default class NotificationBackground { private authService: AuthService, private policyService: PolicyService, private folderService: FolderService, - private stateService: BrowserStateService + private stateService: BrowserStateService, + private environmentService: EnvironmentService ) {} async init() { @@ -51,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); @@ -80,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", @@ -112,6 +118,9 @@ export default class NotificationBackground { break; } break; + case "promptForLogin": + await this.unlockVault(sender.tab); + break; default: break; } @@ -167,11 +176,21 @@ export default class NotificationBackground { isVaultLocked: this.notificationQueue[i].wasVaultLocked, theme: await this.getCurrentTheme(), removeIndividualVault: await this.removeIndividualVault(), + webVaultURL: await this.environmentService.getWebVaultUrl(), }, }); } else if (this.notificationQueue[i].type === NotificationQueueMessageType.ChangePassword) { BrowserApi.tabSendMessageData(tab, "openNotificationBar", { type: "change", + typeData: { + isVaultLocked: this.notificationQueue[i].wasVaultLocked, + theme: await this.getCurrentTheme(), + 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(), @@ -301,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, @@ -323,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]; @@ -417,7 +464,7 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt(); + return await cipher.decrypt(await this.cipherService.getKeyForCipherKeyDecryption(cipher)); } return null; } @@ -459,4 +506,22 @@ export default class NotificationBackground { this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership) ); } + + private async handleUnlockCompleted( + messageData: LockedVaultPendingNotificationsItem, + sender: chrome.runtime.MessageSender + ): Promise+ {{ "loginInitiated" | i18n }} +
+{{ "loginInitiated" | i18n }}
+{{ "deviceApprovalRequired" | i18n }}
+{{ "loginInitiated" | i18n }}
++ +
{{ "loggingInAs" | i18n }} {{ data.userEmail }}
+ {{ + "notYou" | i18n + }} +{{ "logInInitiated" | i18n }}
- +{{ "notificationSentDevice" | i18n }}
+{{ "loginInitiated" | i18n }}
+ +{{ "notificationSentDevice" | i18n }}
+ ++ {{ "fingerprintMatchInfo" | i18n }} +
+
+ {{ fingerprintPhrase }}
+
- {{ "fingerprintMatchInfo" | i18n }} -
+ + +
- {{ fingerprintPhrase }}
-
{{ "adminApprovalRequested" | i18n }}
- +{{ "adminApprovalRequestSentToAdmins" | i18n }}
+{{ "youWillBeNotifiedOnceApproved" | i18n }}
+
+ {{ fingerprintPhrase }}
+