From 112bad03b15c5d61a77a3e8158b595bc4a3c53f1 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 16 Sep 2024 15:36:05 -0400 Subject: [PATCH 01/20] [PM-8582] Move Safari browser check to libs/platform (#11007) --- .../src/platform/browser/browser-api.ts | 6 +-- libs/platform/src/index.ts | 1 + .../src/services/browser-service.spec.ts | 41 +++++++++++++++++++ libs/platform/src/services/browser-service.ts | 7 ++++ 4 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 libs/platform/src/services/browser-service.spec.ts create mode 100644 libs/platform/src/services/browser-service.ts diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index a5ca7a65ff4..33f18ce5723 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -1,6 +1,7 @@ import { Observable } from "rxjs"; import { DeviceType } from "@bitwarden/common/enums"; +import { isBrowserSafariApi } from "@bitwarden/platform"; import { TabMessage } from "../../types/tab-messages"; import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service"; @@ -9,10 +10,7 @@ import { registerContentScriptsPolyfill } from "./browser-api.register-content-s export class BrowserApi { static isWebExtensionsApi: boolean = typeof browser !== "undefined"; - static isSafariApi: boolean = - navigator.userAgent.indexOf(" Safari/") !== -1 && - navigator.userAgent.indexOf(" Chrome/") === -1 && - navigator.userAgent.indexOf(" Chromium/") === -1; + static isSafariApi: boolean = isBrowserSafariApi(); static isChromeApi: boolean = !BrowserApi.isSafariApi && typeof chrome !== "undefined"; static isFirefoxOnAndroid: boolean = navigator.userAgent.indexOf("Firefox/") !== -1 && navigator.userAgent.indexOf("Android") !== -1; diff --git a/libs/platform/src/index.ts b/libs/platform/src/index.ts index e69de29bb2d..f11ec102845 100644 --- a/libs/platform/src/index.ts +++ b/libs/platform/src/index.ts @@ -0,0 +1 @@ +export * from "./services/browser-service"; diff --git a/libs/platform/src/services/browser-service.spec.ts b/libs/platform/src/services/browser-service.spec.ts new file mode 100644 index 00000000000..b7c824bb87f --- /dev/null +++ b/libs/platform/src/services/browser-service.spec.ts @@ -0,0 +1,41 @@ +import { isBrowserSafariApi } from "./browser-service"; + +describe("browser-service", () => { + describe("isBrowserSafariApi", () => { + it("returns true if browser is safari", () => { + jest + .spyOn(navigator, "userAgent", "get") + .mockReturnValue( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15", + ); + + const result = isBrowserSafariApi(); + + expect(result).toBe(true); + }); + + it("returns false if browser is chrome", () => { + jest + .spyOn(navigator, "userAgent", "get") + .mockReturnValue( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + ); + + const result = isBrowserSafariApi(); + + expect(result).toBe(false); + }); + + it("returns false if browser is firefox", () => { + jest + .spyOn(navigator, "userAgent", "get") + .mockReturnValue( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0", + ); + + const result = isBrowserSafariApi(); + + expect(result).toBe(false); + }); + }); +}); diff --git a/libs/platform/src/services/browser-service.ts b/libs/platform/src/services/browser-service.ts new file mode 100644 index 00000000000..00001cbb1a9 --- /dev/null +++ b/libs/platform/src/services/browser-service.ts @@ -0,0 +1,7 @@ +export function isBrowserSafariApi(): boolean { + return ( + navigator.userAgent.indexOf(" Safari/") !== -1 && + navigator.userAgent.indexOf(" Chrome/") === -1 && + navigator.userAgent.indexOf(" Chromium/") === -1 + ); +} From 1ebef296b9409a1d4c94184fd400c0a441bd9a3e Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:08:03 -0400 Subject: [PATCH 02/20] [PM-12024] Move Lock All To Happen in Background (#11047) * Move Lock All To Happen in Background - Make it done serially - Have the promise only resolve once it's complete * Unlock Active Account Last * Add Tests * Update Comment --- .../account-switcher.component.ts | 26 ++-------- .../popup/accounts/foreground-lock.service.ts | 32 ++++++++++++ .../browser/src/background/main.background.ts | 5 ++ .../src/background/runtime.background.ts | 8 +++ .../src/popup/services/services.module.ts | 8 ++- .../common/services/accounts/lock.service.ts | 49 +++++++++++++++++++ .../services/accounts/lock.services.spec.ts | 43 ++++++++++++++++ libs/auth/src/common/services/index.ts | 1 + 8 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 apps/browser/src/auth/popup/accounts/foreground-lock.service.ts create mode 100644 libs/auth/src/common/services/accounts/lock.service.ts create mode 100644 libs/auth/src/common/services/accounts/lock.services.spec.ts diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index e5c09db6428..fb636ecaf6d 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -1,9 +1,10 @@ import { CommonModule, Location } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { Subject, firstValueFrom, map, of, startWith, switchMap, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, map, of, startWith, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { LockService } from "@bitwarden/auth/common"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -70,6 +71,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { private vaultTimeoutSettingsService: VaultTimeoutSettingsService, private authService: AuthService, private configService: ConfigService, + private lockService: LockService, ) {} get accountLimit() { @@ -131,26 +133,8 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { async lockAll() { this.loading = true; - this.availableAccounts$ - .pipe( - map((accounts) => - accounts - .filter((account) => account.id !== this.specialAddAccountId) - .sort((a, b) => (a.isActive ? -1 : b.isActive ? 1 : 0)) // Log out of the active account first - .map((account) => account.id), - ), - switchMap(async (accountIds) => { - if (accountIds.length === 0) { - return; - } - - // Must lock active (first) account first, then order doesn't matter - await this.vaultTimeoutService.lock(accountIds.shift()); - await Promise.all(accountIds.map((id) => this.vaultTimeoutService.lock(id))); - }), - takeUntil(this.destroy$), - ) - .subscribe(() => this.router.navigate(["lock"])); + await this.lockService.lockAll(); + await this.router.navigate(["lock"]); } async logOut(userId: UserId) { diff --git a/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts b/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts new file mode 100644 index 00000000000..20a52a90d8b --- /dev/null +++ b/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts @@ -0,0 +1,32 @@ +import { filter, firstValueFrom } from "rxjs"; + +import { LockService } from "@bitwarden/auth/common"; +import { + CommandDefinition, + MessageListener, + MessageSender, +} from "@bitwarden/common/platform/messaging"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +const LOCK_ALL_FINISHED = new CommandDefinition<{ requestId: string }>("lockAllFinished"); +const LOCK_ALL = new CommandDefinition<{ requestId: string }>("lockAll"); + +export class ForegroundLockService implements LockService { + constructor( + private readonly messageSender: MessageSender, + private readonly messageListener: MessageListener, + ) {} + + async lockAll(): Promise { + const requestId = Utils.newGuid(); + const finishMessage = firstValueFrom( + this.messageListener + .messages$(LOCK_ALL_FINISHED) + .pipe(filter((m) => m.requestId === requestId)), + ); + + this.messageSender.send(LOCK_ALL, { requestId }); + + await finishMessage; + } +} diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 1cb615fe067..9579fb2be83 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -9,6 +9,7 @@ import { AuthRequestService, LoginEmailServiceAbstraction, LogoutReason, + DefaultLockService, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -1065,6 +1066,9 @@ export default class MainBackground { this.scriptInjectorService, this.configService, ); + + const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService); + this.runtimeBackground = new RuntimeBackground( this, this.autofillService, @@ -1079,6 +1083,7 @@ export default class MainBackground { this.fido2Background, messageListener, this.accountService, + lockService, ); this.nativeMessagingBackground = new NativeMessagingBackground( this.cryptoService, diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 424449f0b65..1ec7edcc30c 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,5 +1,6 @@ import { firstValueFrom, map, mergeMap, of, switchMap } from "rxjs"; +import { LockService } from "@bitwarden/auth/common"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillOverlayVisibility, ExtensionCommand } from "@bitwarden/common/autofill/constants"; @@ -48,6 +49,7 @@ export default class RuntimeBackground { private fido2Background: Fido2Background, private messageListener: MessageListener, private accountService: AccountService, + private readonly lockService: LockService, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor chrome.runtime.onInstalled.addListener((details: any) => { @@ -245,6 +247,12 @@ export default class RuntimeBackground { case "lockVault": await this.main.vaultTimeoutService.lock(msg.userId); break; + case "lockAll": + { + await this.lockService.lockAll(); + this.messagingService.send("lockAllFinished", { requestId: msg.requestId }); + } + break; case "logout": await this.main.logout(msg.expired, msg.userId); break; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 098c6eb91ce..efbe9ce6bf5 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -17,7 +17,7 @@ import { } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; +import { LockService, PinServiceAbstraction } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; @@ -91,6 +91,7 @@ import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +import { ForegroundLockService } from "../../auth/popup/accounts/foreground-lock.service"; import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper-data.service"; import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service"; import AutofillService from "../../autofill/services/autofill.service"; @@ -560,6 +561,11 @@ const safeProviders: SafeProvider[] = [ useClass: ExtensionAnonLayoutWrapperDataService, deps: [], }), + safeProvider({ + provide: LockService, + useClass: ForegroundLockService, + deps: [MessageSender, MessageListener], + }), ]; @NgModule({ diff --git a/libs/auth/src/common/services/accounts/lock.service.ts b/libs/auth/src/common/services/accounts/lock.service.ts new file mode 100644 index 00000000000..334a795f7bc --- /dev/null +++ b/libs/auth/src/common/services/accounts/lock.service.ts @@ -0,0 +1,49 @@ +import { combineLatest, firstValueFrom, map } from "rxjs"; + +import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +export abstract class LockService { + /** + * Locks all accounts. + */ + abstract lockAll(): Promise; +} + +export class DefaultLockService implements LockService { + constructor( + private readonly accountService: AccountService, + private readonly vaultTimeoutService: VaultTimeoutService, + ) {} + + async lockAll() { + const accounts = await firstValueFrom( + combineLatest([this.accountService.activeAccount$, this.accountService.accounts$]).pipe( + map(([activeAccount, accounts]) => { + const otherAccounts = Object.keys(accounts) as UserId[]; + + if (activeAccount == null) { + return { activeAccount: null, otherAccounts: otherAccounts }; + } + + return { + activeAccount: activeAccount.id, + otherAccounts: otherAccounts.filter((accountId) => accountId !== activeAccount.id), + }; + }), + ), + ); + + for (const otherAccount of accounts.otherAccounts) { + await this.vaultTimeoutService.lock(otherAccount); + } + + // Do the active account last in case we ever try to route the user on lock + // that way this whole operation will be complete before that routing + // could take place. + if (accounts.activeAccount != null) { + await this.vaultTimeoutService.lock(accounts.activeAccount); + } + } +} diff --git a/libs/auth/src/common/services/accounts/lock.services.spec.ts b/libs/auth/src/common/services/accounts/lock.services.spec.ts new file mode 100644 index 00000000000..eecc3dd787f --- /dev/null +++ b/libs/auth/src/common/services/accounts/lock.services.spec.ts @@ -0,0 +1,43 @@ +import { mock } from "jest-mock-extended"; + +import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { DefaultLockService } from "./lock.service"; + +describe("DefaultLockService", () => { + const mockUser1 = "user1" as UserId; + const mockUser2 = "user2" as UserId; + const mockUser3 = "user3" as UserId; + + const accountService = mockAccountServiceWith(mockUser1); + const vaultTimeoutService = mock(); + + const sut = new DefaultLockService(accountService, vaultTimeoutService); + describe("lockAll", () => { + it("locks the active account last", async () => { + await accountService.addAccount(mockUser2, { + name: "name2", + email: "email2@example.com", + emailVerified: false, + }); + + await accountService.addAccount(mockUser3, { + name: "name3", + email: "email3@example.com", + emailVerified: false, + }); + + await sut.lockAll(); + + expect(vaultTimeoutService.lock).toHaveBeenCalledTimes(3); + // Non-Active users should be called first + expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(1, mockUser2); + expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(2, mockUser3); + + // Active user should be called last + expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(3, mockUser1); + }); + }); +}); diff --git a/libs/auth/src/common/services/index.ts b/libs/auth/src/common/services/index.ts index eb4ec39ce7b..3a8df057796 100644 --- a/libs/auth/src/common/services/index.ts +++ b/libs/auth/src/common/services/index.ts @@ -4,3 +4,4 @@ export * from "./login-strategies/login-strategy.service"; export * from "./user-decryption-options/user-decryption-options.service"; export * from "./auth-request/auth-request.service"; export * from "./register-route.service"; +export * from "./accounts/lock.service"; From d52ab5e343bee8534f49ed5213541b864db7a339 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:22:30 -0400 Subject: [PATCH 03/20] [deps] Autofill: Update concurrently to v9 (#11069) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 35 +++++------------------------------ package.json | 2 +- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00a0e890bf4..6977a9600e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,7 +125,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.7.1", - "concurrently": "8.2.2", + "concurrently": "9.0.1", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", @@ -14537,18 +14537,16 @@ } }, "node_modules/concurrently": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", - "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", - "date-fns": "^2.30.0", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", - "spawn-command": "0.0.2", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" @@ -14558,7 +14556,7 @@ "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": "^14.13.0 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" @@ -15476,23 +15474,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, "node_modules/debounce-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", @@ -34665,12 +34646,6 @@ "node": ">= 0.10" } }, - "node_modules/spawn-command": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", - "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", - "dev": true - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", diff --git a/package.json b/package.json index 3783af2c61d..92f588e0cac 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "base64-loader": "1.0.0", "browserslist": "4.23.2", "chromatic": "11.7.1", - "concurrently": "8.2.2", + "concurrently": "9.0.1", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", "css-loader": "7.1.2", From 651f1e586a8e9f99aba842a0a08990820c879d9f Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:01:38 -0500 Subject: [PATCH 04/20] PM-11468: Add data-testids for View Item pages (#11043) * Add data test IDs for identity information. * Add data test IDs for custom fields. --- .../custom-fields/custom-fields-v2.component.html | 2 ++ .../view-identity-sections.component.html | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 6c1b8ee5f1d..8c6d9f96170 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -7,6 +7,7 @@ class="tw-border-secondary-300 [&_bit-form-field:last-of-type]:tw-mb-0" *ngFor="let field of cipher.fields; let last = last" [ngClass]="{ 'tw-mb-4': !last }" + data-testid="custom-field" > {{ field.name }} @@ -19,6 +20,7 @@ showToast [valueLabel]="field.name" [appA11yTitle]="'copyValue' | i18n" + data-testid="copy-custom-field" > diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html index 29ccd5daa6b..ec4580e31d3 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html @@ -15,6 +15,7 @@ [appCopyClick]="cipher.identity.fullName" showToast [valueLabel]="'name' | i18n" + data-testid="copy-name" > @@ -41,6 +42,7 @@ [appCopyClick]="cipher.identity.company" showToast [valueLabel]="'company' | i18n" + data-testid="copy-company" > @@ -70,6 +72,7 @@ [appCopyClick]="cipher.identity.ssn" showToast [valueLabel]="'ssn' | i18n" + data-testid="copy-ssn" > @@ -96,6 +99,7 @@ [appCopyClick]="cipher.identity.passportNumber" showToast [valueLabel]="'passportNumber' | i18n" + data-testid="copy-passport" > @@ -109,6 +113,7 @@ [appCopyClick]="cipher.identity.licenseNumber" showToast [valueLabel]="'licenseNumber' | i18n" + data-testid="copy-license" > @@ -131,6 +136,7 @@ [appCopyClick]="cipher.identity.email" showToast [valueLabel]="'email' | i18n" + data-testid="copy-email" > @@ -144,6 +150,7 @@ [appCopyClick]="cipher.identity.phone" showToast [valueLabel]="'phone' | i18n" + data-testid="copy-phone" > @@ -164,6 +171,7 @@ [appCopyClick]="addressFields" showToast [valueLabel]="'address' | i18n" + data-testid="copy-address" > From be2ddf784a222652bf3627bef17ec8d8bb7b600d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:53:24 -0400 Subject: [PATCH 05/20] [deps] Design System: Update chromatic to v11.10.2 (#11062) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6977a9600e3..d24d74ffb36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -124,7 +124,7 @@ "babel-loader": "9.1.3", "base64-loader": "1.0.0", "browserslist": "4.23.2", - "chromatic": "11.7.1", + "chromatic": "11.10.2", "concurrently": "9.0.1", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", @@ -13845,9 +13845,9 @@ } }, "node_modules/chromatic": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.7.1.tgz", - "integrity": "sha512-LvgPimdQdnQB07ZDxLEC2KtxgYeqTw0X71GA7fi3zhgtKLxZcE+BSZ/5I9rrQp1V8ydmfElfw0ZwnUH4fVgUAQ==", + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.10.2.tgz", + "integrity": "sha512-EbVlhmOLGdx9QRX3RMOTF3UzoyC1aaXNRjlzm1mc++2OI5+6C5Bzwt2ZUYJ3Jnf/pJa23q0y5Y3QEDcfRVqIbg==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 92f588e0cac..3e78dfb6d80 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "babel-loader": "9.1.3", "base64-loader": "1.0.0", "browserslist": "4.23.2", - "chromatic": "11.7.1", + "chromatic": "11.10.2", "concurrently": "9.0.1", "copy-webpack-plugin": "12.0.2", "cross-env": "7.0.3", From 171d798c526688a2f172b62086731aa4325ae770 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:59:48 -0400 Subject: [PATCH 06/20] [deps] Design System: Update tailwindcss to v3.4.11 (#11056) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d24d74ffb36..c28985f514c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -171,7 +171,7 @@ "sass-loader": "16.0.1", "storybook": "8.2.9", "style-loader": "3.3.4", - "tailwindcss": "3.4.10", + "tailwindcss": "3.4.11", "ts-jest": "29.2.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", @@ -35491,9 +35491,9 @@ "license": "MIT" }, "node_modules/tailwindcss": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz", - "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==", + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz", + "integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3e78dfb6d80..8e5f38c62ef 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "sass-loader": "16.0.1", "storybook": "8.2.9", "style-loader": "3.3.4", - "tailwindcss": "3.4.10", + "tailwindcss": "3.4.11", "ts-jest": "29.2.2", "ts-loader": "9.5.1", "tsconfig-paths-webpack-plugin": "4.1.0", From c05b6eb116a1c98d21a58b7f900133ff6e066e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:22:17 +0100 Subject: [PATCH 07/20] [PM-11667] Remove all code related to the outdated custom permissions 'Edit/Delete Assigned Collections' (#10904) * [PM-11667] Remove all code related to the outdated custom permissions 'Edit/Delete Assigned Collections' * Revert change made to data model in state migration --- apps/web/src/locales/en/messages.json | 6 ------ libs/common/src/admin-console/models/api/permissions.api.ts | 4 ---- libs/common/src/auth/services/key-connector.service.spec.ts | 2 -- 3 files changed, 12 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 7b518e4899d..9b7d44150e8 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4861,12 +4861,6 @@ "deleteAnyCollection": { "message": "Delete any collection" }, - "editAssignedCollections": { - "message": "Edit assigned collections" - }, - "deleteAssignedCollections": { - "message": "Delete assigned collections" - }, "manageGroups": { "message": "Manage groups" }, diff --git a/libs/common/src/admin-console/models/api/permissions.api.ts b/libs/common/src/admin-console/models/api/permissions.api.ts index 4eb055ea797..e6b435294fc 100644 --- a/libs/common/src/admin-console/models/api/permissions.api.ts +++ b/libs/common/src/admin-console/models/api/permissions.api.ts @@ -7,8 +7,6 @@ export class PermissionsApi extends BaseResponse { createNewCollections: boolean; editAnyCollection: boolean; deleteAnyCollection: boolean; - editAssignedCollections: boolean; - deleteAssignedCollections: boolean; manageCiphers: boolean; manageGroups: boolean; manageSso: boolean; @@ -29,8 +27,6 @@ export class PermissionsApi extends BaseResponse { this.createNewCollections = this.getResponseProperty("CreateNewCollections"); this.editAnyCollection = this.getResponseProperty("EditAnyCollection"); this.deleteAnyCollection = this.getResponseProperty("DeleteAnyCollection"); - this.editAssignedCollections = this.getResponseProperty("EditAssignedCollections"); - this.deleteAssignedCollections = this.getResponseProperty("DeleteAssignedCollections"); this.manageCiphers = this.getResponseProperty("ManageCiphers"); this.manageGroups = this.getResponseProperty("ManageGroups"); diff --git a/libs/common/src/auth/services/key-connector.service.spec.ts b/libs/common/src/auth/services/key-connector.service.spec.ts index 5d1aff45f60..0d36bc85959 100644 --- a/libs/common/src/auth/services/key-connector.service.spec.ts +++ b/libs/common/src/auth/services/key-connector.service.spec.ts @@ -340,8 +340,6 @@ describe("KeyConnectorService", () => { createNewCollections: false, editAnyCollection: false, deleteAnyCollection: false, - editAssignedCollections: false, - deleteAssignedCollections: false, manageGroups: false, managePolicies: false, manageSso: false, From a42006763d639e36f713ffa93ac74b4421d27e91 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:42:57 -0500 Subject: [PATCH 08/20] [PM-9455] FireFox back handling (#10867) * Refactor `POPUP_VIEW_MEMORY` to use `disk` rather than memory for the browser extension. - When FireFox opens the popup in an standalone window memory is lost, thus causing the `popup-route-history` to be lost and back navigation ceases to work * spelling * revert state definition change * add `onUpdated` event for firefox * rework observable handling * remove unneeded `from` --- .../popup-view-cache-background.service.ts | 15 +++++++++++++-- .../src/platform/state/global-state.provider.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/platform/services/popup-view-cache-background.service.ts b/apps/browser/src/platform/services/popup-view-cache-background.service.ts index c2713f70a16..35c55633c0c 100644 --- a/apps/browser/src/platform/services/popup-view-cache-background.service.ts +++ b/apps/browser/src/platform/services/popup-view-cache-background.service.ts @@ -1,4 +1,4 @@ -import { switchMap, merge, delay, filter, concatMap, map } from "rxjs"; +import { switchMap, merge, delay, filter, concatMap, map, first, of } from "rxjs"; import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; import { @@ -61,7 +61,18 @@ export class PopupViewCacheBackgroundService { merge( // on tab changed, excluding extension tabs fromChromeEvent(chrome.tabs.onActivated).pipe( - switchMap(([tabInfo]) => BrowserApi.getTab(tabInfo.tabId)), + switchMap((tabs) => BrowserApi.getTab(tabs[0].tabId)), + switchMap((tab) => { + // FireFox sets the `url` to "about:blank" and won't populate the `url` until the `onUpdated` event + if (tab.url !== "about:blank") { + return of(tab); + } + + return fromChromeEvent(chrome.tabs.onUpdated).pipe( + first(), + switchMap(([tabId]) => BrowserApi.getTab(tabId)), + ); + }), map((tab) => tab.url || tab.pendingUrl), filter((url) => !url.startsWith(chrome.runtime.getURL(""))), ), diff --git a/libs/common/src/platform/state/global-state.provider.ts b/libs/common/src/platform/state/global-state.provider.ts index 5aa2b26a5b7..a7179ba0f1d 100644 --- a/libs/common/src/platform/state/global-state.provider.ts +++ b/libs/common/src/platform/state/global-state.provider.ts @@ -2,7 +2,7 @@ import { GlobalState } from "./global-state"; import { KeyDefinition } from "./key-definition"; /** - * A provider for geting an implementation of global state scoped to the given key. + * A provider for getting an implementation of global state scoped to the given key. */ export abstract class GlobalStateProvider { /** From d68853a4a2eb12c5829636da2f9b202a1cd0e637 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:23:15 -0500 Subject: [PATCH 09/20] [PM-11131] Screen Reader Announcements for Copy (#11091) * add copy specific aria-labels for login ciphers * add copy specific aria-labels for card ciphers * add copy translations for identity to web translations --- apps/browser/src/_locales/en/messages.json | 15 +++++++ apps/web/src/locales/en/messages.json | 39 +++++++++++++++++++ .../additional-options.component.html | 2 +- .../autofill-options-view.component.html | 2 +- .../card-details-view.component.html | 4 +- .../custom-fields-v2.component.html | 4 +- .../login-credentials-view.component.html | 6 +-- 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 764b8b5611c..2a7f17e55e5 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -128,6 +128,21 @@ "copyLicenseNumber": { "message": "Copy license number" }, + "copyCustomField": { + "message": "Copy $FIELD$", + "placeholders": { + "field": { + "content": "$1", + "example": "Custom field label" + } + } + }, + "copyWebsite": { + "message": "Copy website" + }, + "copyNotes": { + "message": "Copy notes" + }, "autoFill": { "message": "Autofill" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 9b7d44150e8..cdfae904236 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -592,6 +592,45 @@ "message": "Copy URI", "description": "Copy URI to clipboard" }, + "copyCustomField": { + "message": "Copy $FIELD$", + "placeholders": { + "field": { + "content": "$1", + "example": "Custom field label" + } + } + }, + "copyWebsite": { + "message": "Copy website" + }, + "copyNotes": { + "message": "Copy notes" + }, + "copyAddress": { + "message": "Copy address" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyCompany": { + "message": "Copy company" + }, + "copySSN": { + "message": "Copy Social Security number" + }, + "copyPassportNumber": { + "message": "Copy passport number" + }, + "copyLicenseNumber": { + "message": "Copy license number" + }, + "copyName": { + "message": "Copy name" + }, "me": { "message": "Me" }, diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.html b/libs/vault/src/cipher-view/additional-options/additional-options.component.html index 0913629ad9e..dc785827767 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.html +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.html @@ -14,7 +14,7 @@ [appCopyClick]="notes" showToast [valueLabel]="'note' | i18n" - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyNotes' | i18n" > diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html index 6d4669dd43b..aa3e05b9aab 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html @@ -35,7 +35,7 @@ [appCopyClick]="login.launchUri" [valueLabel]="'website' | i18n" showToast - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyWebsite' | i18n" data-testid="copy-website" > diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index 62da2355edd..051a8128889 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -39,7 +39,7 @@ [appCopyClick]="card.number" showToast [valueLabel]="'number' | i18n" - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyNumber' | i18n" data-testid="copy-number" > @@ -79,7 +79,7 @@ [appCopyClick]="card.code" showToast [valueLabel]="'securityCode' | i18n" - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copySecurityCode' | i18n" data-testid="copy-code" (click)="logCardEvent(true, EventType.Cipher_ClientCopiedCardCode)" > diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 8c6d9f96170..bd2c6e72230 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -19,7 +19,7 @@ [appCopyClick]="field.value" showToast [valueLabel]="field.name" - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyCustomField' | i18n: field.name" data-testid="copy-custom-field" > @@ -40,7 +40,7 @@ [appCopyClick]="field.value" showToast [valueLabel]="field.name" - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyCustomField' | i18n: field.name" (click)="logCopyEvent()" > diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 1d6c81c6591..88a59d9cc42 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -22,7 +22,7 @@ [appCopyClick]="cipher.login.username" [valueLabel]="'username' | i18n" showToast - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyUsername' | i18n" data-testid="copy-username" > @@ -64,7 +64,7 @@ [appCopyClick]="cipher.login.password" [valueLabel]="'password' | i18n" showToast - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyPassword' | i18n" data-testid="copy-password" (click)="logCopyEvent()" > @@ -127,7 +127,7 @@ [appCopyClick]="totpCodeCopyObj?.totpCode" [valueLabel]="'verificationCodeTotp' | i18n" showToast - [appA11yTitle]="'copyValue' | i18n" + [appA11yTitle]="'copyVerificationCode' | i18n" data-testid="copy-totp" [disabled]="!(isPremium$ | async)" class="disabled:tw-cursor-default" From d0516f24e8b56b24c16175721731405ad59f426e Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 17 Sep 2024 14:28:39 -0400 Subject: [PATCH 10/20] [AC-2627] Empty Collection New Item Btn Permissions (#11089) * only show new item btn in empty collection for can edit perm --- apps/web/src/app/vault/org-vault/vault.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.html b/apps/web/src/app/vault/org-vault/vault.component.html index ed7f6b1e3ed..220d6ef490f 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -81,7 +81,11 @@ (click)="addCipher()" buttonType="primary" type="button" - *ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned" + *ngIf=" + filter.type !== 'trash' && + filter.collectionId !== Unassigned && + selectedCollection?.node?.canEditItems(organization) + " > {{ "newItem" | i18n }} From 80a9836cb2a76c0909d6af82beac40a529ab252b Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:00:21 -0500 Subject: [PATCH 11/20] Navigate to fault after cipher deleted. (#11042) --- apps/web/src/app/vault/individual-vault/view.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index 77019b6f32e..fe317490c1f 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -118,6 +118,7 @@ export class ViewComponent implements OnInit, OnDestroy { } this.dialogRef.close({ action: ViewCipherDialogResult.deleted }); + await this.router.navigate(["/vault"]); }; /** From 1a961ee29454e9090fbe4cc74d9839b5315fad4c Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:05:19 -0700 Subject: [PATCH 12/20] fix send list item button hover (#11109) --- .../send-list-items-container.component.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html index 5502422a029..b87c418fe58 100644 --- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html +++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html @@ -33,21 +33,22 @@ + > + > From 99ba56785d7b9587f23c3dc92db37d34d89b0688 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 17 Sep 2024 13:15:11 -0700 Subject: [PATCH 13/20] [PM-11393] Remove the need for TotpCaptureService in Autofill Options View component (#11093) --- .../components/vault-v2/view-v2/view-v2.component.ts | 7 ++----- .../services/browser-totp-capture.service.spec.ts | 11 ----------- .../popup/services/browser-totp-capture.service.ts | 4 ---- .../cipher-form/abstractions/totp-capture.service.ts | 6 ------ .../autofill-options-view.component.ts | 11 +++++------ 5 files changed, 7 insertions(+), 32 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index e8aab69dbe9..b2ef6701b42 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -20,17 +20,15 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { AsyncActionsModule, - SearchModule, ButtonModule, - IconButtonModule, DialogService, + IconButtonModule, + SearchModule, ToastService, } from "@bitwarden/components"; -import { TotpCaptureService } from "@bitwarden/vault"; import { CipherViewComponent } from "../../../../../../../../libs/vault/src/cipher-view"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; -import { BrowserTotpCaptureService } from "../../../services/browser-totp-capture.service"; import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "./../../../../../platform/popup/layout/popup-header.component"; @@ -41,7 +39,6 @@ import { VaultPopupAutofillService } from "./../../../services/vault-popup-autof selector: "app-view-v2", templateUrl: "view-v2.component.html", standalone: true, - providers: [{ provide: TotpCaptureService, useClass: BrowserTotpCaptureService }], imports: [ CommonModule, SearchModule, diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts index e790735dc52..2c9afacffd7 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.spec.ts @@ -13,15 +13,10 @@ describe("BrowserTotpCaptureService", () => { let testBed: TestBed; let service: BrowserTotpCaptureService; let mockCaptureVisibleTab: jest.SpyInstance; - let createNewTabSpy: jest.SpyInstance; const validTotpUrl = "otpauth://totp/label?secret=123"; beforeEach(() => { - const tabReturn = new Promise((resolve) => - resolve({ url: "google.com", active: true } as chrome.tabs.Tab), - ); - createNewTabSpy = jest.spyOn(BrowserApi, "createNewTab").mockReturnValue(tabReturn); mockCaptureVisibleTab = jest.spyOn(BrowserApi, "captureVisibleTab"); mockCaptureVisibleTab.mockResolvedValue("screenshot"); @@ -71,10 +66,4 @@ describe("BrowserTotpCaptureService", () => { expect(result).toBeNull(); }); - - it("should call BrowserApi.createNewTab with a given loginURI", async () => { - await service.openAutofillNewTab("www.google.com"); - - expect(createNewTabSpy).toHaveBeenCalledWith("www.google.com"); - }); }); diff --git a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts index 8f93db45c0e..3f8ba61ed36 100644 --- a/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts +++ b/apps/browser/src/vault/popup/services/browser-totp-capture.service.ts @@ -20,8 +20,4 @@ export class BrowserTotpCaptureService implements TotpCaptureService { } return null; } - - async openAutofillNewTab(loginUri: string) { - await BrowserApi.createNewTab(loginUri); - } } diff --git a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts index ad0b03be824..d6d95565869 100644 --- a/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts +++ b/libs/vault/src/cipher-form/abstractions/totp-capture.service.ts @@ -1,8 +1,3 @@ -/** - * TODO: PM-10727 - Rename and Refactor this service - * This service is being used in both CipherForm and CipherView. Update this service to reflect that - */ - /** * Service to capture TOTP secret from a client application. */ @@ -11,5 +6,4 @@ export abstract class TotpCaptureService { * Captures a TOTP secret and returns it as a string. Returns null if no TOTP secret was found. */ abstract captureTotpSecret(): Promise; - abstract openAutofillNewTab(loginUri: string): void; } diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index 84f25a146c6..b7708b5aa98 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -2,18 +2,17 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { CardComponent, FormFieldModule, + IconButtonModule, SectionComponent, SectionHeaderComponent, TypographyModule, - IconButtonModule, } from "@bitwarden/components"; -import { TotpCaptureService } from "../../cipher-form"; - @Component({ selector: "app-autofill-options-view", templateUrl: "autofill-options-view.component.html", @@ -32,9 +31,9 @@ import { TotpCaptureService } from "../../cipher-form"; export class AutofillOptionsViewComponent { @Input() loginUris: LoginUriView[]; - constructor(private totpCaptureService: TotpCaptureService) {} + constructor(private platformUtilsService: PlatformUtilsService) {} - async openWebsite(selectedUri: string) { - await this.totpCaptureService.openAutofillNewTab(selectedUri); + openWebsite(selectedUri: string) { + this.platformUtilsService.launchUri(selectedUri); } } From e8979decae48bd5304bf8bccdd41098e8c4fc015 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:58:23 -0400 Subject: [PATCH 14/20] Add Back Safari Only Handling of Process Reload (#11115) --- apps/browser/src/background/main.background.ts | 8 ++++++++ apps/browser/src/popup/app.component.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 9579fb2be83..f54f1de1dd5 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1038,6 +1038,14 @@ export default class MainBackground { const systemUtilsServiceReloadCallback = async () => { await this.taskSchedulerService.clearAllScheduledTasks(); + if (this.platformUtilsService.isSafari()) { + // If we do `chrome.runtime.reload` on safari they will send an onInstalled reason of install + // and that prompts us to show a new tab, this apparently doesn't happen on sideloaded + // extensions and only shows itself production scenarios. See: https://bitwarden.atlassian.net/browse/PM-12298 + self.location.reload(); + return; + } + BrowserApi.reloadExtension(); }; diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 477152fff85..12d5b109c20 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -127,6 +127,12 @@ export class AppComponent implements OnInit, OnDestroy { this.showNativeMessagingFingerprintDialog(msg); } else if (msg.command === "showToast") { this.toastService._showToast(msg); + } else if (msg.command === "reloadProcess") { + if (this.platformUtilsService.isSafari()) { + window.setTimeout(() => { + window.location.reload(); + }, 2000); + } } else if (msg.command === "reloadPopup") { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises From 18ef74930c4210d898cbf5b7020ed2b5f8c17cf9 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 00:39:01 +0000 Subject: [PATCH 15/20] Bumped client version(s) (#11118) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 08b8d182837..37deab411b9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.9.0", + "version": "2024.9.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index c28985f514c..e71763cde27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -246,7 +246,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.9.0" + "version": "2024.9.1" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 1b7bb014d203d23e9ac391382941a563cf0132b7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Sep 2024 12:38:35 +0200 Subject: [PATCH 16/20] [PM-11312] Add "prevent screenshot" setting to windows and mac (#10707) * Add screenshot protection to windows and mac * Update messaging of screencapture prevention feature * Set default state to false --- .../src/app/accounts/settings.component.html | 17 +++++++++++++++++ .../src/app/accounts/settings.component.ts | 6 ++++++ apps/desktop/src/locales/en/messages.json | 6 ++++++ apps/desktop/src/main/window.main.ts | 15 +++++++++++++++ .../services/desktop-settings.service.ts | 19 +++++++++++++++++++ 5 files changed, 63 insertions(+) diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index 359d856525e..891314aa568 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -419,6 +419,23 @@ "enableHardwareAccelerationDesc" | i18n }} +
+
+ +
+ {{ + "allowScreenshotsDesc" | i18n + }} +