From 02a63d4a38605707d36a3124beeee62715156081 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 18 Jun 2025 10:16:25 -0400 Subject: [PATCH 01/17] [PM-22725] [Defect]Title and Username are removed when editing Identity items (#15221) * map sdk identity type back to null when undefined * refactored views to have consistent pattern with other fromSdk methods --- .../common/src/vault/models/view/card.view.ts | 11 ++++++++- .../src/vault/models/view/identity.view.ts | 23 ++++++++++++++++++- .../src/vault/models/view/login.view.ts | 19 +++++++++------ .../src/vault/models/view/secure-note.view.ts | 5 +++- .../src/vault/models/view/ssh-key.view.ts | 10 ++++---- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index 2adfbb39e89..dd7f5d6be57 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -157,6 +157,15 @@ export class CardView extends ItemView { return undefined; } - return Object.assign(new CardView(), obj); + const cardView = new CardView(); + + cardView.cardholderName = obj.cardholderName ?? null; + cardView.brand = obj.brand ?? null; + cardView.number = obj.number ?? null; + cardView.expMonth = obj.expMonth ?? null; + cardView.expYear = obj.expYear ?? null; + cardView.code = obj.code ?? null; + + return cardView; } } diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index a75d11efd95..877940e4aea 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -169,6 +169,27 @@ export class IdentityView extends ItemView { return undefined; } - return Object.assign(new IdentityView(), obj); + const identityView = new IdentityView(); + + identityView.title = obj.title ?? null; + identityView.firstName = obj.firstName ?? null; + identityView.middleName = obj.middleName ?? null; + identityView.lastName = obj.lastName ?? null; + identityView.address1 = obj.address1 ?? null; + identityView.address2 = obj.address2 ?? null; + identityView.address3 = obj.address3 ?? null; + identityView.city = obj.city ?? null; + identityView.state = obj.state ?? null; + identityView.postalCode = obj.postalCode ?? null; + identityView.country = obj.country ?? null; + identityView.company = obj.company ?? null; + identityView.email = obj.email ?? null; + identityView.phone = obj.phone ?? null; + identityView.ssn = obj.ssn ?? null; + identityView.username = obj.username ?? null; + identityView.passportNumber = obj.passportNumber ?? null; + identityView.licenseNumber = obj.licenseNumber ?? null; + + return identityView; } } diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 6bdc23f42b1..c6e6ca001e4 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -116,13 +116,18 @@ export class LoginView extends ItemView { return undefined; } - const passwordRevisionDate = - obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; + const loginView = new LoginView(); - return Object.assign(new LoginView(), obj, { - passwordRevisionDate, - uris, - }); + loginView.username = obj.username ?? null; + loginView.password = obj.password ?? null; + loginView.passwordRevisionDate = + obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); + loginView.totp = obj.totp ?? null; + loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null; + loginView.uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; + // FIDO2 credentials are not decrypted here, they remain encrypted + loginView.fido2Credentials = null; + + return loginView; } } diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index 075e4dfc520..8e7a6b4652d 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -37,6 +37,9 @@ export class SecureNoteView extends ItemView { return undefined; } - return Object.assign(new SecureNoteView(), obj); + const secureNoteView = new SecureNoteView(); + secureNoteView.type = obj.type ?? null; + + return secureNoteView; } } diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index a3d091e4c07..a83793678dc 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -55,10 +55,12 @@ export class SshKeyView extends ItemView { return undefined; } - const keyFingerprint = obj.fingerprint; + const sshKeyView = new SshKeyView(); - return Object.assign(new SshKeyView(), obj, { - keyFingerprint, - }); + sshKeyView.privateKey = obj.privateKey ?? null; + sshKeyView.publicKey = obj.publicKey ?? null; + sshKeyView.keyFingerprint = obj.fingerprint ?? null; + + return sshKeyView; } } From 97417b6949e75b5235ac4d5bc40396bccb47040e Mon Sep 17 00:00:00 2001 From: Ketan Mehta <45426198+ketanMehtaa@users.noreply.github.com> Date: Wed, 18 Jun 2025 20:13:00 +0530 Subject: [PATCH 02/17] [PM-22253] fixed white background in darkmode (#15020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed white background in darkmode * removed tw-apperance-none typo * changed both Permission from simple to bit-select * Update apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html Co-authored-by: Vicki League * ui change for permission * added SelectModule in Test file * added selectModule in access stories --------- Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> Co-authored-by: Vicki League --- .../access-selector.component.html | 47 ++++++++----------- .../access-selector.component.spec.ts | 2 + .../access-selector.stories.ts | 2 + 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html index 4226862fde7..088b5051fb1 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html @@ -1,22 +1,21 @@
- + {{ "permission" | i18n }} - + + + - + {{ selectorLabelText }} - +
@@ -79,28 +78,22 @@ - -
- - -
+ + + +
diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.spec.ts index 86c348f0326..a5a632678c9 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.spec.ts @@ -14,6 +14,7 @@ import { ButtonModule, FormFieldModule, IconButtonModule, + SelectModule, TableModule, TabsModule, } from "@bitwarden/components"; @@ -71,6 +72,7 @@ describe("AccessSelectorComponent", () => { PreloadedEnglishI18nModule, JslibModule, IconButtonModule, + SelectModule, ], declarations: [TestableAccessSelectorComponent, UserTypePipe], providers: [], diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts index 095be1df966..e98160d78d0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts @@ -10,6 +10,7 @@ import { DialogModule, FormFieldModule, IconButtonModule, + SelectModule, TableModule, TabsModule, } from "@bitwarden/components"; @@ -47,6 +48,7 @@ export default { TableModule, JslibModule, IconButtonModule, + SelectModule, ], providers: [], }), From a659c0a32da1577d6af2235f48cf403883554e5b Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 18 Jun 2025 08:32:00 -0700 Subject: [PATCH 03/17] [PM-22734] Patch the cipher form after attachments are modified on Desktop (#15227) --- .../src/vault/app/vault/vault-v2.component.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 5ca4d929809..a84a868f4ca 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -64,6 +64,7 @@ import { DefaultChangeLoginPasswordService, DefaultCipherFormConfigService, PasswordRepromptService, + CipherFormComponent, } from "@bitwarden/vault"; import { NavComponent } from "../../../app/layout/nav.component"; @@ -123,6 +124,8 @@ export class VaultV2Component implements OnInit, OnDestroy { vaultFilterComponent: VaultFilterComponent | null = null; @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) folderAddEditModalRef: ViewContainerRef | null = null; + @ViewChild(CipherFormComponent) + cipherFormComponent: CipherFormComponent | null = null; action: CipherFormMode | "view" | null = null; cipherId: string | null = null; @@ -410,6 +413,26 @@ export class VaultV2Component implements OnInit, OnDestroy { result?.action === AttachmentDialogResult.Uploaded ) { await this.vaultItemsComponent?.refresh().catch(() => {}); + + if (this.cipherFormComponent == null) { + return; + } + + const updatedCipher = await this.cipherService.get( + this.cipherId as CipherId, + this.activeUserId as UserId, + ); + const updatedCipherView = await this.cipherService.decrypt( + updatedCipher, + this.activeUserId as UserId, + ); + + this.cipherFormComponent.patchCipher((currentCipher) => { + currentCipher.attachments = updatedCipherView.attachments; + currentCipher.revisionDate = updatedCipherView.revisionDate; + + return currentCipher; + }); } } From 9e764481888f82652e01d8350d6e24cd5ce31584 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:12:21 -0700 Subject: [PATCH 04/17] docs(redirect-guard): [BEEEP] Document redirectGuard (#15196) --- libs/angular/src/auth/guards/index.ts | 2 +- .../src/auth/guards/redirect/README.md | 53 +++++++++++++++++++ .../guards/{ => redirect}/redirect.guard.ts | 14 +++-- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 libs/angular/src/auth/guards/redirect/README.md rename libs/angular/src/auth/guards/{ => redirect}/redirect.guard.ts (86%) diff --git a/libs/angular/src/auth/guards/index.ts b/libs/angular/src/auth/guards/index.ts index 026848c4b08..8a4d0be8167 100644 --- a/libs/angular/src/auth/guards/index.ts +++ b/libs/angular/src/auth/guards/index.ts @@ -1,6 +1,6 @@ export * from "./auth.guard"; export * from "./active-auth.guard"; export * from "./lock.guard"; -export * from "./redirect.guard"; +export * from "./redirect/redirect.guard"; export * from "./tde-decryption-required.guard"; export * from "./unauth.guard"; diff --git a/libs/angular/src/auth/guards/redirect/README.md b/libs/angular/src/auth/guards/redirect/README.md new file mode 100644 index 00000000000..b7977a553b1 --- /dev/null +++ b/libs/angular/src/auth/guards/redirect/README.md @@ -0,0 +1,53 @@ +# Redirect Guard + +The `redirectGuard` redirects the user based on their `AuthenticationStatus`. It is applied to the root route (`/`). + +
+ +### Order of Operations + +The `redirectGuard` will redirect the user based on the following checks, _in order_: + +- **`AuthenticationStatus.LoggedOut`** → redirect to `/login` +- **`AuthenticationStatus.Unlocked`** → redirect to `/vault` +- **`AuthenticationStatus.Locked`** + - **TDE Locked State** → redirect to `/login-initiated` + - A user is in a TDE Locked State if they meet all 3 of the following conditions + 1. Auth status is `Locked` + 2. TDE is enabled + 3. User has never had a user key (that is, user has not unlocked/decrypted yet) + - **Standard Locked State** → redirect to `/lock` + +
+ +| Order | AuthenticationStatus | Redirect To | +| ----- | ------------------------------------------------------------------------------- | ------------------ | +| 1 | `LoggedOut` | `/login` | +| 2 | `Unlocked` | `/vault` | +| 3 | **TDE Locked State**
`Locked` +
`tdeEnabled` +
`!everHadUserKey` | `/login-initiated` | +| 4 | **Standard Locked State**
`Locked` | `/lock` | + +
+ +### Default Routes and Route Overrides + +The default redirect routes are mapped to object properties: + +```typescript +const defaultRoutes: RedirectRoutes = { + loggedIn: "/vault", + loggedOut: "/login", + locked: "/lock", + notDecrypted: "/login-initiated", +}; +``` + +But when applying the guard to the root route, the developer can override specific redirect routes by passing in a custom object. This is useful for subtle differences in client-specific routing: + +```typescript +// app-routing.module.ts (Browser Extension) +{ + path: "", + canActivate: [redirectGuard({ loggedIn: "/tabs/current"})], +} +``` diff --git a/libs/angular/src/auth/guards/redirect.guard.ts b/libs/angular/src/auth/guards/redirect/redirect.guard.ts similarity index 86% rename from libs/angular/src/auth/guards/redirect.guard.ts rename to libs/angular/src/auth/guards/redirect/redirect.guard.ts index b893614b405..45e552639c8 100644 --- a/libs/angular/src/auth/guards/redirect.guard.ts +++ b/libs/angular/src/auth/guards/redirect/redirect.guard.ts @@ -25,12 +25,14 @@ const defaultRoutes: RedirectRoutes = { }; /** - * Guard that consolidates all redirection logic, should be applied to root route. + * Redirects the user to the appropriate route based on their `AuthenticationStatus`. + * This guard should be applied to the root route. * * TODO: This should return Observable once we can get rid of all the promises */ export function redirectGuard(overrides: Partial = {}): CanActivateFn { const routes = { ...defaultRoutes, ...overrides }; + return async (route) => { const authService = inject(AuthService); const keyService = inject(KeyService); @@ -41,16 +43,21 @@ export function redirectGuard(overrides: Partial = {}): CanActiv const authStatus = await authService.getAuthStatus(); + // Logged Out if (authStatus === AuthenticationStatus.LoggedOut) { return router.createUrlTree([routes.loggedOut], { queryParams: route.queryParams }); } + // Unlocked if (authStatus === AuthenticationStatus.Unlocked) { return router.createUrlTree([routes.loggedIn], { queryParams: route.queryParams }); } - // If locked, TDE is enabled, and the user hasn't decrypted yet, then redirect to the - // login decryption options component. + // Locked: TDE Locked State + // - If user meets all 3 of the following conditions: + // 1. Auth status is Locked + // 2. TDE is enabled + // 3. User has never had a user key (has not decrypted yet) const tdeEnabled = await firstValueFrom(deviceTrustService.supportsDeviceTrust$); const userId = await firstValueFrom(accountService.activeAccount$.pipe(getUserId)); const everHadUserKey = await firstValueFrom(keyService.everHadUserKey$(userId)); @@ -64,6 +71,7 @@ export function redirectGuard(overrides: Partial = {}): CanActiv return router.createUrlTree([routes.notDecrypted], { queryParams: route.queryParams }); } + // Locked: Standard Locked State if (authStatus === AuthenticationStatus.Locked) { return router.createUrlTree([routes.locked], { queryParams: route.queryParams }); } From a3d870c6aa84346fe8b118dbd20b9d543a6c4fda Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:45:10 -0400 Subject: [PATCH 05/17] [SM-915 ]Copy update for Service Account - Projects tab (#15073) * Copy update for SM * updates to copy on the service account projects component --- apps/web/src/locales/en/messages.json | 6 +++--- .../projects/service-account-projects.component.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 33468e0b306..6785c20d8f4 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7615,9 +7615,9 @@ "message": "Service account updated", "description": "Notifies that a service account has been updated" }, - "newSaSelectAccess": { - "message": "Type or select projects or secrets", - "description": "Instructions for selecting projects or secrets for a new service account" + "typeOrSelectProjects": { + "message": "Type or select projects", + "description": "Instructions for selecting projects for a service account" }, "newSaTypeToFilter": { "message": "Type to filter", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html index 623542bd33d..8e2889716e8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html @@ -9,7 +9,7 @@ [addButtonMode]="true" [items]="potentialGrantees" [label]="'projects' | i18n" - [hint]="'newSaSelectAccess' | i18n" + [hint]="'typeOrSelectProjects' | i18n" [columnTitle]="'projects' | i18n" [emptyMessage]="'serviceAccountEmptyProjectAccesspolicies' | i18n" > From 95667310a2e3231af50553a37a0589c6259b4cc8 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:57:21 -0400 Subject: [PATCH 06/17] [SM-1246] Routing to new machine account after machine account is created (#15080) * Routing to new machine account after machine account is created * Updating the width of the tabbed content during responsive size changes * Removing responsive UI changes --- .../dialog/service-account-dialog.component.ts | 15 ++++++++++++--- .../service-accounts/service-account.service.ts | 14 +++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 815ea1dc60c..250e0870ecf 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -2,9 +2,9 @@ // @ts-strict-ignore import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogRef, DIALOG_DATA, BitValidators, ToastService } from "@bitwarden/components"; import { ServiceAccountView } from "../../models/view/service-account.view"; @@ -46,8 +46,8 @@ export class ServiceAccountDialogComponent implements OnInit { @Inject(DIALOG_DATA) private data: ServiceAccountOperation, private serviceAccountService: ServiceAccountService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private toastService: ToastService, + private router: Router, ) {} async ngOnInit() { @@ -87,8 +87,17 @@ export class ServiceAccountDialogComponent implements OnInit { let serviceAccountMessage: string; if (this.data.operation == OperationType.Add) { - await this.serviceAccountService.create(this.data.organizationId, serviceAccountView); + const newServiceAccount = await this.serviceAccountService.create( + this.data.organizationId, + serviceAccountView, + ); serviceAccountMessage = this.i18nService.t("machineAccountCreated"); + await this.router.navigate([ + "sm", + this.data.organizationId, + "machine-accounts", + newServiceAccount.id, + ]); } else { await this.serviceAccountService.update( this.data.serviceAccountId, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts index c5d4f979ef4..19382793673 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.service.ts @@ -91,7 +91,10 @@ export class ServiceAccountService { ); } - async create(organizationId: string, serviceAccountView: ServiceAccountView) { + async create( + organizationId: string, + serviceAccountView: ServiceAccountView, + ): Promise { const orgKey = await this.getOrganizationKey(organizationId); const request = await this.getServiceAccountRequest(orgKey, serviceAccountView); const r = await this.apiService.send( @@ -101,9 +104,14 @@ export class ServiceAccountService { true, true, ); - this._serviceAccount.next( - await this.createServiceAccountView(orgKey, new ServiceAccountResponse(r)), + + const serviceAccount = await this.createServiceAccountView( + orgKey, + new ServiceAccountResponse(r), ); + this._serviceAccount.next(serviceAccount); + + return serviceAccount; } async delete(serviceAccounts: ServiceAccountView[]): Promise { From 8d4fc915906cd37c6996e97346e8ab9fd76e3963 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:57:39 -0400 Subject: [PATCH 07/17] Updating responsive width of ProjectPeople, ProjectServiceAcct, ServiceAccountPeople, and ServiceAccountProjects (#15084) --- .../projects/project/project-people.component.html | 2 +- .../projects/project/project-service-accounts.component.html | 2 +- .../people/service-account-people.component.html | 2 +- .../projects/service-account-projects.component.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html index 3f107486e27..cbac54fd7c6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.html @@ -1,5 +1,5 @@
-
+

{{ "projectPeopleDescription" | i18n }}

diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html index 5d22358277f..a3914ac9cf2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.html @@ -1,5 +1,5 @@ -
+

{{ "projectMachineAccountsDescription" | i18n }}

diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html index 96f7ae4d2bf..49cafeccc3b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.html @@ -1,5 +1,5 @@ -
+

{{ "machineAccountPeopleDescription" | i18n }}

diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html index 8e2889716e8..ab7d90ef078 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.html @@ -1,5 +1,5 @@ -
+

{{ "machineAccountProjectsDescription" | i18n }}

From 5fa153e743c01d5716503e20b046902c8739efcd Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:13:38 -0700 Subject: [PATCH 08/17] [PM-20643] - [Vault] [Desktop] Front End Changes to Enforce "Remove card item type policy" (#15176) * add restricted item types to legacy vault components * filter out restricted item types from new menu item in desktop * use CIPHER_MENU_ITEMS * use CIPHER_MENU_ITEMS. move restricted cipher service to common * use move restricted item types service to libs. re-use cipher menu items * add shareReplay. change variable name * move restricted filter to search service. remove unecessary import * add reusable service method * clean up spec * add optional chain * remove duplicate import * move isCipherViewRestricted to service module * fix logic * fix logic * remove extra space --------- Co-authored-by: SmithThe4th --- apps/browser/src/_locales/en/messages.json | 3 + .../popup/settings/autofill.component.ts | 2 +- .../new-item-dropdown-v2.component.spec.ts | 5 +- .../new-item-dropdown-v2.component.ts | 3 +- .../vault-popup-list-filters.service.spec.ts | 5 +- .../vault-popup-list-filters.service.ts | 19 ++-- apps/desktop/src/locales/en/messages.json | 3 + .../vault/app/vault/add-edit.component.html | 4 +- .../src/vault/app/vault/add-edit.component.ts | 16 ++++ .../filters/type-filter.component.html | 90 ++++--------------- .../filters/type-filter.component.ts | 20 ++++- .../app/vault/vault-items-v2.component.html | 26 ++---- .../app/vault/vault-items-v2.component.ts | 4 +- .../vault/app/vault/vault-items.component.ts | 4 +- .../vault-filter/vault-filter.component.ts | 2 +- .../vault-items/vault-items.stories.ts | 2 +- .../components/vault-filter.component.ts | 2 +- .../shared/models/filter-function.spec.ts | 2 +- .../shared/models/filter-function.ts | 24 ++--- .../vault-header/vault-header.component.ts | 2 +- .../vault/individual-vault/vault.component.ts | 2 +- .../src/services/jslib-services.module.ts | 6 ++ .../vault/components/add-edit.component.ts | 10 --- .../vault/components/vault-items.component.ts | 29 +++++- libs/common/src/vault/service-utils.ts | 1 + .../restricted-item-types.service.spec.ts | 20 ++--- .../services/restricted-item-types.service.ts | 25 +++++- .../src/vault/types/cipher-menu-items.ts | 2 +- libs/vault/src/index.ts | 4 - 29 files changed, 166 insertions(+), 171 deletions(-) rename libs/{vault/src => common/src/vault}/services/restricted-item-types.service.spec.ts (89%) rename libs/{vault/src => common/src/vault}/services/restricted-item-types.service.ts (79%) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a9d2d75d64c..2d29efcc89e 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1922,6 +1922,9 @@ "typeSshKey": { "message": "SSH key" }, + "typeNote": { + "message": "Note" + }, "newItemHeader": { "message": "New $TYPE$", "placeholders": { diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 2b58c32c926..852b79cad1d 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -45,6 +45,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CardComponent, CheckboxModule, @@ -58,7 +59,6 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts index cc97027c82e..7e3db27640e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts @@ -12,8 +12,11 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { + RestrictedCipherType, + RestrictedItemTypesService, +} from "@bitwarden/common/vault/services/restricted-item-types.service"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; -import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index caffd5e7119..fd7a0c4672b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -8,9 +8,10 @@ import { map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CipherMenuItem, CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; -import { AddEditFolderDialogComponent, RestrictedItemTypesService } from "@bitwarden/vault"; +import { AddEditFolderDialogComponent } from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index f8351fe0f61..8b2786fab77 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -20,7 +20,10 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault"; +import { + RestrictedCipherType, + RestrictedItemTypesService, +} from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CachedFilterState, diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index a3e5fc4c2bd..9f7363afd7e 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -39,9 +39,12 @@ import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; +import { + isCipherViewRestricted, + RestrictedItemTypesService, +} from "@bitwarden/common/vault/services/restricted-item-types.service"; import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; import { ChipSelectOption } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; const FILTER_VISIBILITY_KEY = new KeyDefinition(VAULT_SETTINGS_DISK, "filterVisibility", { deserializer: (obj) => obj, @@ -227,18 +230,8 @@ export class VaultPopupListFiltersService { } // Check if cipher type is restricted (with organization exemptions) - if (restrictions && restrictions.length > 0) { - const isRestricted = restrictions.some( - (restrictedType) => - restrictedType.cipherType === cipher.type && - (cipher.organizationId - ? !restrictedType.allowViewOrgIds.includes(cipher.organizationId) - : restrictedType.allowViewOrgIds.length === 0), - ); - - if (isRestricted) { - return false; - } + if (isCipherViewRestricted(cipher, restrictions)) { + return false; } if (filters.cipherType !== null && cipher.type !== filters.cipherType) { diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 1685de7d8d4..1431ab72020 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -23,6 +23,9 @@ "typeIdentity": { "message": "Identity" }, + "typeNote": { + "message": "Note" + }, "typeSecureNote": { "message": "Secure note" }, diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.html b/apps/desktop/src/vault/app/vault/add-edit.component.html index 9c316813d1d..2cd384885ce 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.html +++ b/apps/desktop/src/vault/app/vault/add-edit.component.html @@ -12,7 +12,9 @@
diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index eb04003a418..e9b18270f2d 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -3,6 +3,7 @@ import { DatePipe } from "@angular/common"; import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { NgForm } from "@angular/forms"; +import { map, shareReplay } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; @@ -22,6 +23,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; import { DialogService, ToastService } from "@bitwarden/components"; import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; @@ -35,6 +38,18 @@ const BroadcasterSubscriptionId = "AddEditComponent"; export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { @ViewChild("form") private form: NgForm; + menuItems$ = this.restrictedItemTypesService.restricted$.pipe( + map((restrictedItemTypes) => + // Filter out restricted item types from the default CIPHER_MENU_ITEMS array + CIPHER_MENU_ITEMS.filter( + (typeOption) => + !restrictedItemTypes.some( + (restrictedType) => restrictedType.cipherType === typeOption.type, + ), + ), + ), + shareReplay({ bufferSize: 1, refCount: true }), + ); constructor( cipherService: CipherService, @@ -59,6 +74,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On cipherAuthorizationService: CipherAuthorizationService, sdkService: SdkService, sshImportPromptService: SshImportPromptService, + protected restrictedItemTypesService: RestrictedItemTypesService, ) { super( cipherService, diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html index c3dcd191dfc..f8a83e01266 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.html @@ -20,78 +20,20 @@
    -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • + @for (typeFilter of typeFilters$ | async; track typeFilter) { +
  • + + + +
  • + }
diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts index 5920233b206..27e7d5c5ecb 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts @@ -1,6 +1,9 @@ import { Component } from "@angular/core"; +import { map, shareReplay } from "rxjs"; import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/type-filter.component"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; @Component({ selector: "app-type-filter", @@ -8,7 +11,22 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul standalone: false, }) export class TypeFilterComponent extends BaseTypeFilterComponent { - constructor() { + protected typeFilters$ = this.restrictedItemTypesService.restricted$.pipe( + map((restrictedItemTypes) => + // Filter out restricted item types from the typeFilters array + CIPHER_MENU_ITEMS.filter( + (typeFilter) => + !restrictedItemTypes.some( + (restrictedType) => + restrictedType.allowViewOrgIds.length === 0 && + restrictedType.cipherType === typeFilter.type, + ), + ), + ), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + constructor(private restrictedItemTypesService: RestrictedItemTypesService) { super(); } } diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.html b/apps/desktop/src/vault/app/vault/vault-items-v2.component.html index 63e648e3cf3..fcf38ee39bc 100644 --- a/apps/desktop/src/vault/app/vault/vault-items-v2.component.html +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.html @@ -72,25 +72,11 @@ - - - - - + @for (itemTypes of itemTypes$ | async; track itemTypes.type) { + + } diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts index 5a832ed79b0..1256c9e52e8 100644 --- a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -10,6 +10,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { MenuModule } from "@bitwarden/components"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; @@ -25,8 +26,9 @@ export class VaultItemsV2Component extends BaseVaultItemsComponent { private readonly searchBarService: SearchBarService, cipherService: CipherService, accountService: AccountService, + restrictedItemTypesService: RestrictedItemTypesService, ) { - super(searchService, cipherService, accountService); + super(searchService, cipherService, accountService, restrictedItemTypesService); this.searchBarService.searchText$ .pipe(distinctUntilChanged(), takeUntilDestroyed()) diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index 2d1ba784753..8bf4955343d 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -8,6 +8,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; @@ -22,8 +23,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent { searchBarService: SearchBarService, cipherService: CipherService, accountService: AccountService, + protected restrictedItemTypesService: RestrictedItemTypesService, ) { - super(searchService, cipherService, accountService); + super(searchService, cipherService, accountService, restrictedItemTypesService); // eslint-disable-next-line rxjs-angular/prefer-takeuntil searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => { diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index ff6ec9af0af..49bf43d60bf 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -11,8 +11,8 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component"; import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index 62b53d71e84..e65d423a57b 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -35,8 +35,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { LayoutComponent } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; import { GroupView } from "../../../admin-console/organizations/core"; import { PreloadedEnglishI18nModule } from "../../../core/tests"; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 8987fff04cf..72766817eeb 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -22,8 +22,8 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; import { TrialFlowService } from "../../../../billing/services/trial-flow.service"; import { VaultFilterService } from "../services/abstractions/vault-filter.service"; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.spec.ts index 660aeb293a4..2ec2b2c40a9 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.spec.ts @@ -3,7 +3,7 @@ import { Unassigned } from "@bitwarden/admin-console/common"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { RestrictedCipherType } from "@bitwarden/vault"; +import { RestrictedCipherType } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { createFilterFunction } from "./filter-function"; import { All } from "./routed-vault-filter.model"; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.ts index 61305fa5e49..93071aecae3 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/filter-function.ts @@ -1,7 +1,10 @@ import { Unassigned } from "@bitwarden/admin-console/common"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { RestrictedCipherType } from "@bitwarden/vault"; +import { + isCipherViewRestricted, + RestrictedCipherType, +} from "@bitwarden/common/vault/services/restricted-item-types.service"; import { All, RoutedVaultFilterModel } from "./routed-vault-filter.model"; @@ -83,24 +86,9 @@ export function createFilterFunction( ) { return false; } - // Restricted types - if (restrictedTypes && restrictedTypes.length > 0) { - // Filter the cipher if that type is restricted unless - // - The cipher belongs to an organization and that organization allows viewing the cipher type - // OR - // - The cipher belongs to the user's personal vault and at least one other organization does not restrict that type - if ( - restrictedTypes.some( - (restrictedType) => - restrictedType.cipherType === cipher.type && - (cipher.organizationId - ? !restrictedType.allowViewOrgIds.includes(cipher.organizationId) - : restrictedType.allowViewOrgIds.length === 0), - ) - ) { - return false; - } + if (restrictedTypes && isCipherViewRestricted(cipher, restrictedTypes)) { + return false; } return true; }; diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts index 48bc3a4268b..49e159143dd 100644 --- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts @@ -18,13 +18,13 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { BreadcrumbsModule, DialogService, MenuModule, SimpleDialogOptions, } from "@bitwarden/components"; -import { RestrictedItemTypesService } from "@bitwarden/vault"; import { CollectionDialogTabType } from "../../../admin-console/organizations/shared/components/collection-dialog"; import { HeaderModule } from "../../../layouts/header/header.module"; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 2c9079c7279..3d59a186705 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -66,6 +66,7 @@ import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-repromp import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { DialogRef, DialogService, Icons, ToastService } from "@bitwarden/components"; import { @@ -79,7 +80,6 @@ import { DecryptionFailureDialogComponent, DefaultCipherFormConfigService, PasswordRepromptService, - RestrictedItemTypesService, } from "@bitwarden/vault"; import { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 873cb4f9b63..c1c4844a61d 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -294,6 +294,7 @@ import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { DefaultTaskService, TaskService } from "@bitwarden/common/vault/tasks"; @@ -680,6 +681,11 @@ const safeProviders: SafeProvider[] = [ KdfConfigService, ], }), + safeProvider({ + provide: RestrictedItemTypesService, + useClass: RestrictedItemTypesService, + deps: [ConfigService, AccountService, OrganizationServiceAbstraction, PolicyServiceAbstraction], + }), safeProvider({ provide: PasswordStrengthServiceAbstraction, useClass: PasswordStrengthService, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 5d6343b0b3c..ec79ac9ef18 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -84,7 +84,6 @@ export class AddEditComponent implements OnInit, OnDestroy { showCardNumber = false; showCardCode = false; cipherType = CipherType; - typeOptions: any[]; cardBrandOptions: any[]; cardExpMonthOptions: any[]; identityTitleOptions: any[]; @@ -139,13 +138,6 @@ export class AddEditComponent implements OnInit, OnDestroy { protected sdkService: SdkService, private sshImportPromptService: SshImportPromptService, ) { - this.typeOptions = [ - { name: i18nService.t("typeLogin"), value: CipherType.Login }, - { name: i18nService.t("typeCard"), value: CipherType.Card }, - { name: i18nService.t("typeIdentity"), value: CipherType.Identity }, - { name: i18nService.t("typeSecureNote"), value: CipherType.SecureNote }, - ]; - this.cardBrandOptions = [ { name: "-- " + i18nService.t("select") + " --", value: null }, { name: "Visa", value: "Visa" }, @@ -215,8 +207,6 @@ export class AddEditComponent implements OnInit, OnDestroy { this.writeableCollections = await this.loadCollections(); this.canUseReprompt = await this.passwordRepromptService.enabled(); - - this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey }); } ngOnDestroy() { diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index c34816994be..db9ac581d41 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -8,7 +8,9 @@ import { combineLatest, filter, from, + map, of, + shareReplay, switchMap, takeUntil, } from "rxjs"; @@ -20,6 +22,11 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + isCipherViewRestricted, + RestrictedItemTypesService, +} from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; @Directive() export class VaultItemsComponent implements OnInit, OnDestroy { @@ -35,6 +42,19 @@ export class VaultItemsComponent implements OnInit, OnDestroy { organization: Organization; CipherType = CipherType; + protected itemTypes$ = this.restrictedItemTypesService.restricted$.pipe( + map((restrictedItemTypes) => + // Filter out restricted item types + CIPHER_MENU_ITEMS.filter( + (itemType) => + !restrictedItemTypes.some( + (restrictedType) => restrictedType.cipherType === itemType.type, + ), + ), + ), + shareReplay({ bufferSize: 1, refCount: true }), + ); + protected searchPending = false; /** Construct filters as an observable so it can be appended to the cipher stream. */ @@ -62,6 +82,7 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected searchService: SearchService, protected cipherService: CipherService, protected accountService: AccountService, + protected restrictedItemTypesService: RestrictedItemTypesService, ) { this.subscribeToCiphers(); } @@ -143,18 +164,22 @@ export class VaultItemsComponent implements OnInit, OnDestroy { this._searchText$, this._filter$, of(userId), + this.restrictedItemTypesService.restricted$, ]), ), - switchMap(([indexedCiphers, failedCiphers, searchText, filter, userId]) => { + switchMap(([indexedCiphers, failedCiphers, searchText, filter, userId, restricted]) => { let allCiphers = indexedCiphers ?? []; const _failedCiphers = failedCiphers ?? []; allCiphers = [..._failedCiphers, ...allCiphers]; + const restrictedTypeFilter = (cipher: CipherView) => + isCipherViewRestricted(cipher, restricted); + return this.searchService.searchCiphers( userId, searchText, - [filter, this.deletedFilter], + [filter, this.deletedFilter, restrictedTypeFilter], allCiphers, ); }), diff --git a/libs/common/src/vault/service-utils.ts b/libs/common/src/vault/service-utils.ts index 96ae406fae4..9595434223f 100644 --- a/libs/common/src/vault/service-utils.ts +++ b/libs/common/src/vault/service-utils.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore + import { ITreeNodeObject, TreeNode } from "./models/domain/tree-node"; export class ServiceUtils { diff --git a/libs/vault/src/services/restricted-item-types.service.spec.ts b/libs/common/src/vault/services/restricted-item-types.service.spec.ts similarity index 89% rename from libs/vault/src/services/restricted-item-types.service.spec.ts rename to libs/common/src/vault/services/restricted-item-types.service.spec.ts index 7ff48f0642b..9b549665184 100644 --- a/libs/vault/src/services/restricted-item-types.service.spec.ts +++ b/libs/common/src/vault/services/restricted-item-types.service.spec.ts @@ -1,4 +1,3 @@ -import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; @@ -49,19 +48,16 @@ describe("RestrictedItemTypesService", () => { fakeAccount = { id: Utils.newGuid() as UserId } as Account; accountService.activeAccount$ = of(fakeAccount); - TestBed.configureTestingModule({ - providers: [ - { provide: PolicyService, useValue: policyService }, - { provide: OrganizationService, useValue: organizationService }, - { provide: AccountService, useValue: accountService }, - { provide: ConfigService, useValue: configService }, - ], - }); - configService.getFeatureFlag$.mockReturnValue(of(true)); organizationService.organizations$.mockReturnValue(of([org1, org2])); policyService.policiesByType$.mockReturnValue(of([])); - service = TestBed.inject(RestrictedItemTypesService); + + service = new RestrictedItemTypesService( + configService, + accountService, + organizationService, + policyService, + ); }); it("emits empty array when feature flag is disabled", async () => { @@ -106,7 +102,6 @@ describe("RestrictedItemTypesService", () => { }); it("returns empty allowViewOrgIds when all orgs restrict the same type", async () => { - configService.getFeatureFlag$.mockReturnValue(of(true)); organizationService.organizations$.mockReturnValue(of([org1, org2])); policyService.policiesByType$.mockReturnValue(of([policyOrg1, policyOrg2])); @@ -117,7 +112,6 @@ describe("RestrictedItemTypesService", () => { }); it("aggregates multiple types and computes allowViewOrgIds correctly", async () => { - configService.getFeatureFlag$.mockReturnValue(of(true)); organizationService.organizations$.mockReturnValue(of([org1, org2])); policyService.policiesByType$.mockReturnValue( of([ diff --git a/libs/vault/src/services/restricted-item-types.service.ts b/libs/common/src/vault/services/restricted-item-types.service.ts similarity index 79% rename from libs/vault/src/services/restricted-item-types.service.ts rename to libs/common/src/vault/services/restricted-item-types.service.ts index b24533fb2f6..63c9577bc09 100644 --- a/libs/vault/src/services/restricted-item-types.service.ts +++ b/libs/common/src/vault/services/restricted-item-types.service.ts @@ -1,4 +1,3 @@ -import { Injectable } from "@angular/core"; import { combineLatest, map, of, Observable } from "rxjs"; import { switchMap, distinctUntilChanged, shareReplay } from "rxjs/operators"; @@ -10,13 +9,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; export type RestrictedCipherType = { cipherType: CipherType; allowViewOrgIds: string[]; }; -@Injectable({ providedIn: "root" }) export class RestrictedItemTypesService { /** * Emits an array of RestrictedCipherType objects: @@ -78,3 +77,25 @@ export class RestrictedItemTypesService { private policyService: PolicyService, ) {} } + +/** + * Filter that returns whether a cipher is restricted from being viewed by the user + * Criteria: + * - the cipher's type is restricted by at least one org + * UNLESS + * - the cipher belongs to an organization and that organization does not restrict that type + * OR + * - the cipher belongs to the user's personal vault and at least one other organization does not restrict that type + */ +export function isCipherViewRestricted( + cipher: CipherView, + restrictedTypes: RestrictedCipherType[], +) { + return restrictedTypes.some( + (restrictedType) => + restrictedType.cipherType === cipher.type && + (cipher.organizationId + ? !restrictedType.allowViewOrgIds.includes(cipher.organizationId) + : restrictedType.allowViewOrgIds.length === 0), + ); +} diff --git a/libs/common/src/vault/types/cipher-menu-items.ts b/libs/common/src/vault/types/cipher-menu-items.ts index e88c0457081..7108d0d0bd6 100644 --- a/libs/common/src/vault/types/cipher-menu-items.ts +++ b/libs/common/src/vault/types/cipher-menu-items.ts @@ -19,6 +19,6 @@ export const CIPHER_MENU_ITEMS = Object.freeze([ { type: CipherType.Login, icon: "bwi-globe", labelKey: "typeLogin" }, { type: CipherType.Card, icon: "bwi-credit-card", labelKey: "typeCard" }, { type: CipherType.Identity, icon: "bwi-id-card", labelKey: "typeIdentity" }, - { type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "note" }, + { type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "typeNote" }, { type: CipherType.SshKey, icon: "bwi-key", labelKey: "typeSshKey" }, ] as const) satisfies readonly CipherMenuItem[]; diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 7229b558f30..b39bb85ab30 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -24,10 +24,6 @@ export * as VaultIcons from "./icons"; export { DefaultSshImportPromptService } from "./services/default-ssh-import-prompt.service"; export { SshImportPromptService } from "./services/ssh-import-prompt.service"; -export { - RestrictedItemTypesService, - RestrictedCipherType, -} from "./services/restricted-item-types.service"; export * from "./abstractions/change-login-password.service"; export * from "./services/default-change-login-password.service"; From aa4a9babc504dea12f35f08537f7daf9e724da1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Wed, 18 Jun 2025 21:56:56 +0200 Subject: [PATCH 09/17] fix(desktop_proxy): [PM-22452] Fix desktop_proxy signing for DMG --- apps/desktop/scripts/after-pack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index 99c3d91be52..cdb5e098440 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -89,7 +89,7 @@ async function run(context) { } else { // For non-Appstore builds, we don't need the inherit binary as they are not sandboxed, // but we sign and include it anyway for consistency. It should be removed once DDG supports the proxy directly. - const entitlementsName = "entitlements.mac.plist"; + const entitlementsName = "entitlements.mac.inherit.plist"; const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName); child_process.execSync( `codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`, From e8f53fe9b716b4ac0ae6778245e704ddfd094e20 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 18 Jun 2025 14:44:21 -0700 Subject: [PATCH 10/17] [PM-22756] Send minimizeOnCopy message during copy on Desktop platform (#15232) * [PM-22756] Send minimizeOnCopy message during copy on Desktop platform * [PM-22756] Introduce optional CopyClickListener pattern * [PM-22756] Introduce CopyService that wraps PlatformUtilsService.copyToClipboard to allow scoped implementations * [PM-22756] Introduce DesktopVaultCopyService that sends the minimizeOnCopy message * [PM-22756] Remove leftover onCopy method * [PM-22756] Fix failing tests * [PM-22756] Revert CopyService solution * [PM-22756] Cleanup * [PM-22756] Update test * [PM-22756] Cleanup leftover test changes --- .../src/vault/app/vault/vault-v2.component.ts | 19 ++++++++++++----- .../copy-click/copy-click.directive.spec.ts | 17 +++++++++++---- .../src/copy-click/copy-click.directive.ts | 21 ++++++++++++++----- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index a84a868f4ca..354752c8b36 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -13,8 +13,6 @@ import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom, Observabl import { filter, map, take } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -45,6 +43,8 @@ import { DialogService, ItemModule, ToastService, + CopyClickListener, + COPY_CLICK_LISTENER, } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { @@ -115,9 +115,13 @@ const BroadcasterSubscriptionId = "VaultComponent"; useClass: DesktopPremiumUpgradePromptService, }, { provide: CipherFormGenerationService, useClass: DesktopCredentialGenerationService }, + { + provide: COPY_CLICK_LISTENER, + useExisting: VaultV2Component, + }, ], }) -export class VaultV2Component implements OnInit, OnDestroy { +export class VaultV2Component implements OnInit, OnDestroy, CopyClickListener { @ViewChild(VaultItemsV2Component, { static: true }) vaultItemsComponent: VaultItemsV2Component | null = null; @ViewChild(VaultFilterComponent, { static: true }) @@ -161,7 +165,6 @@ export class VaultV2Component implements OnInit, OnDestroy { ), ); - private modal: ModalRef | null = null; private componentIsDestroyed$ = new Subject(); private allOrganizations: Organization[] = []; private allCollections: CollectionView[] = []; @@ -170,7 +173,6 @@ export class VaultV2Component implements OnInit, OnDestroy { private route: ActivatedRoute, private router: Router, private i18nService: I18nService, - private modalService: ModalService, private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, @@ -378,6 +380,13 @@ export class VaultV2Component implements OnInit, OnDestroy { } } + /** + * Handler for Vault level CopyClickDirectives to send the minimizeOnCopy message + */ + onCopy() { + this.messagingService.send("minimizeOnCopy"); + } + async viewCipher(cipher: CipherView) { if (await this.shouldReprompt(cipher, "view")) { return; diff --git a/libs/components/src/copy-click/copy-click.directive.spec.ts b/libs/components/src/copy-click/copy-click.directive.spec.ts index 38f8ccb43cb..321a18596e4 100644 --- a/libs/components/src/copy-click/copy-click.directive.spec.ts +++ b/libs/components/src/copy-click/copy-click.directive.spec.ts @@ -1,10 +1,11 @@ import { Component, ElementRef, ViewChild } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "../"; +import { ToastService, CopyClickListener, COPY_CLICK_LISTENER } from "../"; import { CopyClickDirective } from "./copy-click.directive"; @@ -34,10 +35,12 @@ describe("CopyClickDirective", () => { let fixture: ComponentFixture; const copyToClipboard = jest.fn(); const showToast = jest.fn(); + const copyClickListener = mock(); beforeEach(async () => { copyToClipboard.mockClear(); showToast.mockClear(); + copyClickListener.onCopy.mockClear(); await TestBed.configureTestingModule({ imports: [TestCopyClickComponent], @@ -55,6 +58,7 @@ describe("CopyClickDirective", () => { }, { provide: PlatformUtilsService, useValue: { copyToClipboard } }, { provide: ToastService, useValue: { showToast } }, + { provide: COPY_CLICK_LISTENER, useValue: copyClickListener }, ], }).compileComponents(); @@ -92,7 +96,6 @@ describe("CopyClickDirective", () => { successToastButton.click(); expect(showToast).toHaveBeenCalledWith({ message: "copySuccessful", - title: null, variant: "success", }); }); @@ -103,7 +106,6 @@ describe("CopyClickDirective", () => { infoToastButton.click(); expect(showToast).toHaveBeenCalledWith({ message: "copySuccessful", - title: null, variant: "info", }); }); @@ -115,8 +117,15 @@ describe("CopyClickDirective", () => { expect(showToast).toHaveBeenCalledWith({ message: "valueCopied Content", - title: null, variant: "success", }); }); + + it("should call copyClickListener.onCopy when value is copied", () => { + const successToastButton = fixture.componentInstance.successToastButton.nativeElement; + + successToastButton.click(); + + expect(copyClickListener.onCopy).toHaveBeenCalledWith("success toast shown"); + }); }); diff --git a/libs/components/src/copy-click/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts index 1dfaf4387dc..514a55a0242 100644 --- a/libs/components/src/copy-click/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -1,12 +1,19 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, HostListener, Input } from "@angular/core"; +import { Directive, HostListener, Input, InjectionToken, Inject, Optional } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ToastService, ToastVariant } from "../"; +/** + * Listener that can be provided to receive copy events to allow for customized behavior. + */ +export interface CopyClickListener { + onCopy(value: string): void; +} + +export const COPY_CLICK_LISTENER = new InjectionToken("CopyClickListener"); + @Directive({ selector: "[appCopyClick]", }) @@ -18,6 +25,7 @@ export class CopyClickDirective { private platformUtilsService: PlatformUtilsService, private toastService: ToastService, private i18nService: I18nService, + @Optional() @Inject(COPY_CLICK_LISTENER) private copyListener?: CopyClickListener, ) {} @Input("appCopyClick") valueToCopy = ""; @@ -26,7 +34,7 @@ export class CopyClickDirective { * When set, the toast displayed will show ` copied` * instead of the default messaging. */ - @Input() valueLabel: string; + @Input() valueLabel?: string; /** * When set without a value, a success toast will be shown when the value is copied @@ -54,6 +62,10 @@ export class CopyClickDirective { @HostListener("click") onClick() { this.platformUtilsService.copyToClipboard(this.valueToCopy); + if (this.copyListener) { + this.copyListener.onCopy(this.valueToCopy); + } + if (this._showToast) { const message = this.valueLabel ? this.i18nService.t("valueCopied", this.valueLabel) @@ -61,7 +73,6 @@ export class CopyClickDirective { this.toastService.showToast({ variant: this.toastVariant, - title: null, message, }); } From b35583a5ac6fe68024cc681ebb63cdb214985da1 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:27:14 -0700 Subject: [PATCH 11/17] prevent double MP prompt on copy and delete (#15218) --- .../src/vault/app/vault/item-footer.component.ts | 5 +++-- .../src/vault/app/vault/vault-v2.component.html | 1 + .../src/vault/app/vault/vault-v2.component.ts | 13 ++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts index d83a530cc40..675a8403b20 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.ts +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -25,6 +25,7 @@ export class ItemFooterComponent implements OnInit { @Input() collectionId: string | null = null; @Input({ required: true }) action: string = "view"; @Input() isSubmitting: boolean = false; + @Input() masterPasswordAlreadyPrompted: boolean = false; @Output() onEdit = new EventEmitter(); @Output() onClone = new EventEmitter(); @Output() onDelete = new EventEmitter(); @@ -34,8 +35,7 @@ export class ItemFooterComponent implements OnInit { canDeleteCipher$: Observable = new Observable(); activeUserId: UserId | null = null; - - private passwordReprompted = false; + passwordReprompted: boolean = false; constructor( protected cipherService: CipherService, @@ -51,6 +51,7 @@ export class ItemFooterComponent implements OnInit { async ngOnInit() { this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher); this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } async clone() { diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.html b/apps/desktop/src/vault/app/vault/vault-v2.component.html index 4dd23466126..f1cb28f3ea5 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.html +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.html @@ -20,6 +20,7 @@ (onDelete)="deleteCipher()" (onCancel)="cancelCipher($event)" [isSubmitting]="isSubmitting" + [masterPasswordAlreadyPrompted]="cipherRepromptId === cipherId" >
diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 354752c8b36..849899bfe66 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -744,10 +744,17 @@ export class VaultV2Component implements OnInit, OnDestroy, CopyClickListener { } async editFolder(folderId: string) { + if (!this.activeUserId) { + return; + } const folderView = await firstValueFrom( this.folderService.getDecrypted$(folderId, this.activeUserId), ); + if (!folderView) { + return; + } + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { editFolderConfig: { folder: { @@ -762,7 +769,7 @@ export class VaultV2Component implements OnInit, OnDestroy, CopyClickListener { result === AddEditFolderDialogResult.Deleted || result === AddEditFolderDialogResult.Created ) { - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + await this.vaultFilterComponent?.reloadCollectionsAndFolders(this.activeFilter); } } @@ -807,10 +814,6 @@ export class VaultV2Component implements OnInit, OnDestroy, CopyClickListener { .catch(() => {}); } - private addCipherWithChangeDetection(type: CipherType) { - this.functionWithChangeDetection(() => this.addCipher(type).catch(() => {})); - } - private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) { this.functionWithChangeDetection(() => { (async () => { From f9b31d2906dee4a11ee858479f22b359819e1df7 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:27:34 -0700 Subject: [PATCH 12/17] remove legacy attachment upload (#15237) --- libs/common/src/abstractions/api.service.ts | 10 ---- libs/common/src/services/api.service.ts | 18 ------ .../file-upload/cipher-file-upload.service.ts | 60 +------------------ 3 files changed, 1 insertion(+), 87 deletions(-) diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 44b5e34a4a4..cabde4093c4 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -228,16 +228,6 @@ export abstract class ApiService { request: CipherBulkRestoreRequest, ) => Promise>; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postCipherAttachmentLegacy: (id: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postCipherAttachmentAdminLegacy: (id: string, data: FormData) => Promise; postCipherAttachment: ( id: string, request: AttachmentRequest, diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 1971cd86363..a2cc86a57ad 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -639,24 +639,6 @@ export class ApiService implements ApiServiceAbstraction { return new AttachmentUploadDataResponse(r); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postCipherAttachmentLegacy(id: string, data: FormData): Promise { - const r = await this.send("POST", "/ciphers/" + id + "/attachment", data, true, true); - return new CipherResponse(r); - } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postCipherAttachmentAdminLegacy(id: string, data: FormData): Promise { - const r = await this.send("POST", "/ciphers/" + id + "/attachment-admin", data, true, true); - return new CipherResponse(r); - } - deleteCipherAttachment(id: string, attachmentId: string): Promise { return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, true); } diff --git a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts index 4dd2f7f7338..10fa1d9580c 100644 --- a/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts +++ b/libs/common/src/vault/services/file-upload/cipher-file-upload.service.ts @@ -6,7 +6,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -47,18 +46,7 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti this.generateMethods(uploadDataResponse, response, request.adminRequest), ); } catch (e) { - if ( - (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) || - (e as ErrorResponse).statusCode === 405 - ) { - response = await this.legacyServerAttachmentFileUpload( - request.adminRequest, - cipher.id, - encFileName, - encData, - dataEncKey[1], - ); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -113,50 +101,4 @@ export class CipherFileUploadService implements CipherFileUploadServiceAbstracti } }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerAttachmentFileUpload( - admin: boolean, - cipherId: string, - encFileName: EncString, - encData: EncArrayBuffer, - key: EncString, - ) { - const fd = new FormData(); - try { - const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); - fd.append("key", key.encryptedString); - fd.append("data", blob, encFileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("key", key.encryptedString); - fd.append( - "data", - Buffer.from(encData.buffer) as any, - { - filepath: encFileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - - let response: CipherResponse; - try { - if (admin) { - response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); - } else { - response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); - } - } catch (e) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } - - return response; - } } From 3b830faf09e55c5fec2a043c44432487029cbbfc Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:08:13 -0700 Subject: [PATCH 13/17] fix logic for restrictedTypeFilter (#15253) --- libs/angular/src/vault/components/vault-items.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index db9ac581d41..0679d141bbd 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -174,7 +174,7 @@ export class VaultItemsComponent implements OnInit, OnDestroy { allCiphers = [..._failedCiphers, ...allCiphers]; const restrictedTypeFilter = (cipher: CipherView) => - isCipherViewRestricted(cipher, restricted); + !isCipherViewRestricted(cipher, restricted); return this.searchService.searchCiphers( userId, From 1b3877a3d2fe538f84d72cffa6c62fd2676fe6ee Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 18 Jun 2025 16:53:13 -0700 Subject: [PATCH 14/17] [PM-22764] Fix Desktop footer button permissions (#15254) * [PM-22764] Fix desktop footer button permissions * [PM-22764] Fix desktop edit button permission --- apps/desktop/src/vault/app/vault/item-footer.component.html | 4 ++-- apps/desktop/src/vault/app/vault/item-footer.component.ts | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.html b/apps/desktop/src/vault/app/vault/item-footer.component.html index 5a067da372e..c41bf254c80 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.html +++ b/apps/desktop/src/vault/app/vault/item-footer.component.html @@ -36,7 +36,7 @@ class="primary" (click)="restore()" appA11yTitle="{{ 'restore' | i18n }}" - *ngIf="cipher.isDeleted" + *ngIf="cipher.isDeleted && cipher.permissions.restore" > @@ -50,7 +50,7 @@ -
+