From befc756a022c61a3f6838ef7552c89a0aa681d0e Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 16 Sep 2025 08:58:01 -0500 Subject: [PATCH 01/16] [PM-25694] [Defect] Secrets manager + AC integrations page not listing any integrations (#16401) --- .../integrations/integrations.module.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts index fefcdcc3daa..2b0682b6602 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.module.ts @@ -1,5 +1,11 @@ import { NgModule } from "@angular/core"; +import { HecOrganizationIntegrationService } from "@bitwarden/bit-common/dirt/organization-integrations/services/hec-organization-integration-service"; +import { OrganizationIntegrationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-api.service"; +import { OrganizationIntegrationConfigurationApiService } from "@bitwarden/bit-common/dirt/organization-integrations/services/organization-integration-configuration-api.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { safeProvider } from "@bitwarden/ui-common"; + import { IntegrationCardComponent } from "../../dirt/organization-integrations/integration-card/integration-card.component"; import { IntegrationGridComponent } from "../../dirt/organization-integrations/integration-grid/integration-grid.component"; import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; @@ -14,6 +20,23 @@ import { IntegrationsComponent } from "./integrations.component"; IntegrationCardComponent, IntegrationGridComponent, ], + providers: [ + safeProvider({ + provide: HecOrganizationIntegrationService, + useClass: HecOrganizationIntegrationService, + deps: [OrganizationIntegrationApiService, OrganizationIntegrationConfigurationApiService], + }), + safeProvider({ + provide: OrganizationIntegrationApiService, + useClass: OrganizationIntegrationApiService, + deps: [ApiService], + }), + safeProvider({ + provide: OrganizationIntegrationConfigurationApiService, + useClass: OrganizationIntegrationConfigurationApiService, + deps: [ApiService], + }), + ], declarations: [IntegrationsComponent], }) export class IntegrationsModule {} From 59396f02623ec340b684c42525e6bdc1be73e7e1 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 16 Sep 2025 09:30:11 -0500 Subject: [PATCH 02/16] [PM-24655] Delete an existing Integration (#16382) --- apps/web/src/locales/en/messages.json | 3 + .../models/integration.ts | 1 - .../hec-organization-integration-service.ts | 28 ++++++ .../integration-card.component.spec.ts | 90 +++++++++++++++++- .../integration-card.component.ts | 94 +++++++++++++------ .../connect-dialog-hec.component.html | 13 +++ .../connect-dialog-hec.component.spec.ts | 5 +- .../connect-dialog-hec.component.ts | 62 +++++++++--- .../integrations.component.ts | 6 +- 9 files changed, 252 insertions(+), 50 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index ef6e68ff1a0..054ad26efe6 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9670,6 +9670,9 @@ "failedToSaveIntegration": { "message": "Failed to save integration. Please try again later." }, + "failedToDeleteIntegration": { + "message": "Failed to delete integration. Please try again later." + }, "deviceIdMissing": { "message": "Device ID is missing" }, diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts index 1bb38915e9a..abd1861caa9 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration.ts @@ -20,7 +20,6 @@ export type Integration = { */ newBadgeExpiration?: string; description?: string; - isConnected?: boolean; canSetupConnection?: boolean; configuration?: string; template?: string; diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts index d45d4d21652..ad3d6764713 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/services/hec-organization-integration-service.ts @@ -156,6 +156,34 @@ export class HecOrganizationIntegrationService { } } + async deleteHec( + organizationId: OrganizationId, + OrganizationIntegrationId: OrganizationIntegrationId, + OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId, + ) { + if (organizationId != this.organizationId$.getValue()) { + throw new Error("Organization ID mismatch"); + } + // delete the configuration first due to foreign key constraint + await this.integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration( + organizationId, + OrganizationIntegrationId, + OrganizationIntegrationConfigurationId, + ); + + // delete the integration + await this.integrationApiService.deleteOrganizationIntegration( + organizationId, + OrganizationIntegrationId, + ); + + // update the local observable + const updatedIntegrations = this._integrations$ + .getValue() + .filter((i) => i.id !== OrganizationIntegrationId); + this._integrations$.next(updatedIntegrations); + } + /** * Gets a OrganizationIntegration for an OrganizationIntegrationId * @param integrationId id of the integration diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts index c7a7ff45761..0facf282ba3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.spec.ts @@ -13,12 +13,13 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; -import { openHecConnectDialog } from "../integration-dialog"; +import { HecConnectDialogResultStatus, openHecConnectDialog } from "../integration-dialog"; import { IntegrationCardComponent } from "./integration-card.component"; jest.mock("../integration-dialog", () => ({ openHecConnectDialog: jest.fn(), + HecConnectDialogResultStatus: { Edited: "edit", Delete: "delete" }, })); describe("IntegrationCardComponent", () => { @@ -272,7 +273,7 @@ describe("IntegrationCardComponent", () => { it("should call updateHec if isUpdateAvailable is true", async () => { (openHecConnectDialog as jest.Mock).mockReturnValue({ closed: of({ - success: true, + success: HecConnectDialogResultStatus.Edited, url: "test-url", bearerToken: "token", index: "index", @@ -304,7 +305,7 @@ describe("IntegrationCardComponent", () => { (openHecConnectDialog as jest.Mock).mockReturnValue({ closed: of({ - success: true, + success: HecConnectDialogResultStatus.Edited, url: "test-url", bearerToken: "token", index: "index", @@ -327,10 +328,66 @@ describe("IntegrationCardComponent", () => { expect(mockIntegrationService.updateHec).not.toHaveBeenCalled(); }); - it("should show toast on error", async () => { + it("should call deleteHec when a delete is requested", async () => { + component.organizationId = "org-id" as any; + (openHecConnectDialog as jest.Mock).mockReturnValue({ closed: of({ - success: true, + success: HecConnectDialogResultStatus.Delete, + url: "test-url", + bearerToken: "token", + index: "index", + }), + }); + + mockIntegrationService.deleteHec.mockResolvedValue(undefined); + + await component.setupConnection(); + + expect(mockIntegrationService.deleteHec).toHaveBeenCalledWith( + "org-id", + "integration-id", + "config-id", + ); + expect(mockIntegrationService.saveHec).not.toHaveBeenCalled(); + }); + + it("should not call deleteHec if no existing configuration", async () => { + component.integrationSettings = { + organizationIntegration: null, + name: OrganizationIntegrationServiceType.CrowdStrike, + } as any; + component.organizationId = "org-id" as any; + + (openHecConnectDialog as jest.Mock).mockReturnValue({ + closed: of({ + success: HecConnectDialogResultStatus.Delete, + url: "test-url", + bearerToken: "token", + index: "index", + }), + }); + + mockIntegrationService.deleteHec.mockResolvedValue(undefined); + + await component.setupConnection(); + + expect(mockIntegrationService.deleteHec).not.toHaveBeenCalledWith( + "org-id", + "integration-id", + "config-id", + OrganizationIntegrationServiceType.CrowdStrike, + "test-url", + "token", + "index", + ); + expect(mockIntegrationService.updateHec).not.toHaveBeenCalled(); + }); + + it("should show toast on error while saving", async () => { + (openHecConnectDialog as jest.Mock).mockReturnValue({ + closed: of({ + success: HecConnectDialogResultStatus.Edited, url: "test-url", bearerToken: "token", index: "index", @@ -349,5 +406,28 @@ describe("IntegrationCardComponent", () => { message: mockI18nService.t("failedToSaveIntegration"), }); }); + + it("should show toast on error while deleting", async () => { + (openHecConnectDialog as jest.Mock).mockReturnValue({ + closed: of({ + success: HecConnectDialogResultStatus.Delete, + url: "test-url", + bearerToken: "token", + index: "index", + }), + }); + + jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true); + mockIntegrationService.deleteHec.mockRejectedValue(new Error("fail")); + + await component.setupConnection(); + + expect(mockIntegrationService.deleteHec).toHaveBeenCalled(); + expect(toastService.showToast).toHaveBeenCalledWith({ + variant: "error", + title: "", + message: mockI18nService.t("failedToDeleteIntegration"), + }); + }); }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts index f40fb03c5f4..b83b1609bce 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.ts @@ -21,7 +21,11 @@ import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; -import { openHecConnectDialog } from "../integration-dialog/index"; +import { + HecConnectDialogResult, + HecConnectDialogResultStatus, + openHecConnectDialog, +} from "../integration-dialog/index"; @Component({ selector: "app-integration-card", @@ -142,32 +146,20 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { } try { - if (this.isUpdateAvailable) { - const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; - const orgIntegrationConfigurationId = - this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id; + if (result.success === HecConnectDialogResultStatus.Delete) { + await this.deleteHec(); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("failedToDeleteIntegration"), + }); + } - if (!orgIntegrationId || !orgIntegrationConfigurationId) { - throw Error("Organization Integration ID or Configuration ID is missing"); - } - - await this.hecOrganizationIntegrationService.updateHec( - this.organizationId, - orgIntegrationId, - orgIntegrationConfigurationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.bearerToken, - result.index, - ); - } else { - await this.hecOrganizationIntegrationService.saveHec( - this.organizationId, - this.integrationSettings.name as OrganizationIntegrationServiceType, - result.url, - result.bearerToken, - result.index, - ); + try { + if (result.success === HecConnectDialogResultStatus.Edited) { + await this.saveHec(result); } } catch { this.toastService.showToast({ @@ -175,7 +167,55 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { title: "", message: this.i18nService.t("failedToSaveIntegration"), }); - return; } } + + async saveHec(result: HecConnectDialogResult) { + if (this.isUpdateAvailable) { + // retrieve org integration and configuration ids + const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; + const orgIntegrationConfigurationId = + this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id; + + if (!orgIntegrationId || !orgIntegrationConfigurationId) { + throw Error("Organization Integration ID or Configuration ID is missing"); + } + + // update existing integration and configuration + await this.hecOrganizationIntegrationService.updateHec( + this.organizationId, + orgIntegrationId, + orgIntegrationConfigurationId, + this.integrationSettings.name as OrganizationIntegrationServiceType, + result.url, + result.bearerToken, + result.index, + ); + } else { + // create new integration and configuration + await this.hecOrganizationIntegrationService.saveHec( + this.organizationId, + this.integrationSettings.name as OrganizationIntegrationServiceType, + result.url, + result.bearerToken, + result.index, + ); + } + } + + async deleteHec() { + const orgIntegrationId = this.integrationSettings.organizationIntegration?.id; + const orgIntegrationConfigurationId = + this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id; + + if (!orgIntegrationId || !orgIntegrationConfigurationId) { + throw Error("Organization Integration ID or Configuration ID is missing"); + } + + await this.hecOrganizationIntegrationService.deleteHec( + this.organizationId, + orgIntegrationId, + orgIntegrationConfigurationId, + ); + } } diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html index 2495feacf60..776ab9f006c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.html @@ -37,6 +37,19 @@ + + @if (canDelete) { +
+ +
+ } diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.spec.ts index 1325411ade8..7d367ea92be 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.spec.ts @@ -14,6 +14,7 @@ import { ConnectHecDialogComponent, HecConnectDialogParams, HecConnectDialogResult, + HecConnectDialogResultStatus, openHecConnectDialog, } from "./connect-dialog-hec.component"; @@ -65,7 +66,6 @@ describe("ConnectDialogHecComponent", () => { imageDarkMode: "test-image-dark.png", newBadgeExpiration: "2024-12-31", description: "Test Description", - isConnected: false, canSetupConnection: true, type: IntegrationType.EVENT, } as Integration; @@ -155,8 +155,7 @@ describe("ConnectDialogHecComponent", () => { bearerToken: "token", index: "1", service: "Test Service", - success: true, - error: null, + success: HecConnectDialogResultStatus.Edited, }); }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.ts index fbcb67b66ba..16090da1c00 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-dialog/connect-dialog/connect-dialog-hec.component.ts @@ -17,10 +17,17 @@ export interface HecConnectDialogResult { bearerToken: string; index: string; service: string; - success: boolean; - error: string | null; + success: HecConnectDialogResultStatusType | null; } +export const HecConnectDialogResultStatus = { + Edited: "edit", + Delete: "delete", +} as const; + +export type HecConnectDialogResultStatusType = + (typeof HecConnectDialogResultStatus)[keyof typeof HecConnectDialogResultStatus]; + @Component({ templateUrl: "./connect-dialog-hec.component.html", imports: [SharedModule], @@ -40,6 +47,7 @@ export class ConnectHecDialogComponent implements OnInit { @Inject(DIALOG_DATA) protected connectInfo: HecConnectDialogParams, protected formBuilder: FormBuilder, private dialogRef: DialogRef, + private dialogService: DialogService, ) {} ngOnInit(): void { @@ -62,23 +70,51 @@ export class ConnectHecDialogComponent implements OnInit { return !!this.hecConfig; } - submit = async (): Promise => { - const formJson = this.formGroup.getRawValue(); + get canDelete(): boolean { + return !!this.hecConfig; + } - const result: HecConnectDialogResult = { - integrationSettings: this.connectInfo.settings, - url: formJson.url || "", - bearerToken: formJson.bearerToken || "", - index: formJson.index || "", - service: formJson.service || "", - success: true, - error: null, - }; + submit = async (): Promise => { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; + } + const result = this.getHecConnectDialogResult(HecConnectDialogResultStatus.Edited); this.dialogRef.close(result); return; }; + + delete = async (): Promise => { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { + key: "deleteItemConfirmation", + }, + type: "warning", + }); + + if (confirmed) { + const result = this.getHecConnectDialogResult(HecConnectDialogResultStatus.Delete); + this.dialogRef.close(result); + } + }; + + private getHecConnectDialogResult( + status: HecConnectDialogResultStatusType, + ): HecConnectDialogResult { + const formJson = this.formGroup.getRawValue(); + + return { + integrationSettings: this.connectInfo.settings, + url: formJson.url || "", + bearerToken: formJson.bearerToken || "", + index: formJson.index || "", + service: formJson.service || "", + success: status, + }; + } } export function openHecConnectDialog( diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts index 6747fd1c2fe..e3af5e273ea 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integrations.component.ts @@ -253,7 +253,6 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg", type: IntegrationType.EVENT, description: "crowdstrikeEventIntegrationDesc", - isConnected: false, canSetupConnection: true, }; @@ -265,6 +264,11 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy { this.hecOrganizationIntegrationService.integrations$ .pipe(takeUntil(this.destroy$)) .subscribe((integrations) => { + // reset all integrations to null first - in case one was deleted + this.integrationsList.forEach((i) => { + i.organizationIntegration = null; + }); + integrations.map((integration) => { const item = this.integrationsList.find((i) => i.name === integration.serviceType); if (item) { From d06d47e26a5d5306a67536cb4e89a2979e0edae9 Mon Sep 17 00:00:00 2001 From: Konrad <11725227+mKoonrad@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:53:36 +0200 Subject: [PATCH 03/16] Full headers (#16184) Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/browser/src/_locales/en/messages.json | 103 +++++++++++++----- .../add-edit/send-add-edit.component.ts | 15 +-- .../add-edit/add-edit-v2.component.ts | 23 ++-- .../view-v2/view-v2.component.spec.ts | 8 +- .../vault-v2/view-v2/view-v2.component.ts | 20 ++-- .../emergency-view-dialog.component.spec.ts | 8 +- .../view/emergency-view-dialog.component.ts | 10 +- .../vault-item-dialog.component.ts | 57 +++++----- .../individual-vault/add-edit-v2.component.ts | 22 ++-- .../vault/individual-vault/view.component.ts | 10 +- apps/web/src/locales/en/messages.json | 103 +++++++++++++----- .../send-add-edit-dialog.component.ts | 15 +-- 12 files changed, 231 insertions(+), 163 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b09b0b7b67b..103b1ca2a5b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1934,32 +1934,81 @@ "typeNote": { "message": "Note" }, - "newItemHeader": { - "message": "New $TYPE$", - "placeholders": { - "type": { - "content": "$1", - "example": "Login" - } - } + "newItemHeaderLogin": { + "message": "New Login", + "description": "Header for new login item type" }, - "editItemHeader": { - "message": "Edit $TYPE$", - "placeholders": { - "type": { - "content": "$1", - "example": "Login" - } - } + "newItemHeaderCard": { + "message": "New Card", + "description": "Header for new card item type" }, - "viewItemHeader": { - "message": "View $TYPE$", - "placeholders": { - "type": { - "content": "$1", - "example": "Login" - } - } + "newItemHeaderIdentity": { + "message": "New Identity", + "description": "Header for new identity item type" + }, + "newItemHeaderNote": { + "message": "New Note", + "description": "Header for new note item type" + }, + "newItemHeaderSshKey": { + "message": "New SSH key", + "description": "Header for new SSH key item type" + }, + "newItemHeaderTextSend": { + "message": "New Text Send", + "description": "Header for new text send" + }, + "newItemHeaderFileSend": { + "message": "New File Send", + "description": "Header for new file send" + }, + "editItemHeaderLogin": { + "message": "Edit Login", + "description": "Header for edit login item type" + }, + "editItemHeaderCard": { + "message": "Edit Card", + "description": "Header for edit card item type" + }, + "editItemHeaderIdentity": { + "message": "Edit Identity", + "description": "Header for edit identity item type" + }, + "editItemHeaderNote": { + "message": "Edit Note", + "description": "Header for edit note item type" + }, + "editItemHeaderSshKey": { + "message": "Edit SSH key", + "description": "Header for edit SSH key item type" + }, + "editItemHeaderTextSend": { + "message": "Edit Text Send", + "description": "Header for edit text send" + }, + "editItemHeaderFileSend": { + "message": "Edit File Send", + "description": "Header for edit file send" + }, + "viewItemHeaderLogin": { + "message": "View Login", + "description": "Header for view login item type" + }, + "viewItemHeaderCard": { + "message": "View Card", + "description": "Header for view card item type" + }, + "viewItemHeaderIdentity": { + "message": "View Identity", + "description": "Header for view identity item type" + }, + "viewItemHeaderNote": { + "message": "View Note", + "description": "Header for view note item type" + }, + "viewItemHeaderSshKey": { + "message": "View SSH key", + "description": "Header for view SSH key item type" }, "passwordHistory": { "message": "Password history" @@ -5092,15 +5141,9 @@ "itemLocation": { "message": "Item Location" }, - "fileSend": { - "message": "File Send" - }, "fileSends": { "message": "File Sends" }, - "textSend": { - "message": "Text Send" - }, "textSends": { "message": "Text Sends" }, diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index b6957248d75..5911b3b6d89 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -188,14 +188,11 @@ export class SendAddEditComponent { * @returns The header text. */ private getHeaderText(mode: SendFormMode, type: SendType) { - const headerKey = - mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader"; - - switch (type) { - case SendType.Text: - return this.i18nService.t(headerKey, this.i18nService.t("textSend")); - case SendType.File: - return this.i18nService.t(headerKey, this.i18nService.t("fileSend")); - } + const isEditMode = mode === "edit" || mode === "partial-edit"; + const translation = { + [SendType.Text]: isEditMode ? "editItemHeaderTextSend" : "newItemHeaderTextSend", + [SendType.File]: isEditMode ? "editItemHeaderFileSend" : "newItemHeaderFileSend", + }; + return this.i18nService.t(translation[type]); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 3985fc85a54..a1e0a43343f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -368,20 +368,15 @@ export class AddEditV2Component implements OnInit { } setHeader(mode: CipherFormMode, type: CipherType) { - const partOne = mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader"; - - switch (type) { - case CipherType.Login: - return this.i18nService.t(partOne, this.i18nService.t("typeLogin")); - case CipherType.Card: - return this.i18nService.t(partOne, this.i18nService.t("typeCard")); - case CipherType.Identity: - return this.i18nService.t(partOne, this.i18nService.t("typeIdentity")); - case CipherType.SecureNote: - return this.i18nService.t(partOne, this.i18nService.t("note")); - case CipherType.SshKey: - return this.i18nService.t(partOne, this.i18nService.t("typeSshKey")); - } + const isEditMode = mode === "edit" || mode === "partial-edit"; + const translation = { + [CipherType.Login]: isEditMode ? "editItemHeaderLogin" : "newItemHeaderLogin", + [CipherType.Card]: isEditMode ? "editItemHeaderCard" : "newItemHeaderCard", + [CipherType.Identity]: isEditMode ? "editItemHeaderIdentity" : "newItemHeaderIdentity", + [CipherType.SecureNote]: isEditMode ? "editItemHeaderNote" : "newItemHeaderNote", + [CipherType.SshKey]: isEditMode ? "editItemHeaderSshKey" : "newItemHeaderSshKey", + }; + return this.i18nService.t(translation[type]); } delete = async () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 51116da2865..3d4fdb2e9f9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -172,28 +172,28 @@ describe("ViewV2Component", () => { params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typeLogin"); + expect(component.headerText).toEqual("viewItemHeaderLogin"); // Set header text for a card mockCipher.type = CipherType.Card; params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typeCard"); + expect(component.headerText).toEqual("viewItemHeaderCard"); // Set header text for an identity mockCipher.type = CipherType.Identity; params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader typeIdentity"); + expect(component.headerText).toEqual("viewItemHeaderIdentity"); // Set header text for a secure note mockCipher.type = CipherType.SecureNote; params$.next({ cipherId: mockCipher.id }); flush(); // Resolve all promises - expect(component.headerText).toEqual("viewItemHeader note"); + expect(component.headerText).toEqual("viewItemHeaderNote"); })); it("sends viewed event", fakeAsync(() => { 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 5983b2577a5..915a27e4fd1 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 @@ -194,18 +194,14 @@ export class ViewV2Component { } setHeader(type: CipherType) { - switch (type) { - case CipherType.Login: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeLogin")); - case CipherType.Card: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeCard")); - case CipherType.Identity: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeIdentity")); - case CipherType.SecureNote: - return this.i18nService.t("viewItemHeader", this.i18nService.t("note")); - case CipherType.SshKey: - return this.i18nService.t("viewItemHeader", this.i18nService.t("typeSshkey")); - } + const translation = { + [CipherType.Login]: "viewItemHeaderLogin", + [CipherType.Card]: "viewItemHeaderCard", + [CipherType.Identity]: "viewItemHeaderIdentity", + [CipherType.SecureNote]: "viewItemHeaderNote", + [CipherType.SshKey]: "viewItemHeaderSshKey", + }; + return this.i18nService.t(translation[type]); } async getCipherData(id: string, userId: UserId) { diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index f0ecca1686d..60993924ded 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -134,7 +134,7 @@ describe("EmergencyViewDialogComponent", () => { component["updateTitle"](); - expect(component["title"]).toBe("viewItemType typelogin"); + expect(component["title"]).toBe("viewItemHeaderLogin"); }); it("sets card title", () => { @@ -142,7 +142,7 @@ describe("EmergencyViewDialogComponent", () => { component["updateTitle"](); - expect(component["title"]).toBe("viewItemType typecard"); + expect(component["title"]).toBe("viewItemHeaderCard"); }); it("sets identity title", () => { @@ -150,7 +150,7 @@ describe("EmergencyViewDialogComponent", () => { component["updateTitle"](); - expect(component["title"]).toBe("viewItemType typeidentity"); + expect(component["title"]).toBe("viewItemHeaderIdentity"); }); it("sets note title", () => { @@ -158,7 +158,7 @@ describe("EmergencyViewDialogComponent", () => { component["updateTitle"](); - expect(component["title"]).toBe("viewItemType note"); + expect(component["title"]).toBe("viewItemHeaderNote"); }); }); }); diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts index 67612e5dcd3..656ec894f27 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts @@ -73,22 +73,20 @@ export class EmergencyViewDialogComponent { }; private updateTitle() { - const partOne = "viewItemType"; - const type = this.cipher.type; switch (type) { case CipherType.Login: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLowerCase()); + this.title = this.i18nService.t("viewItemHeaderLogin"); break; case CipherType.Card: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard").toLowerCase()); + this.title = this.i18nService.t("viewItemHeaderCard"); break; case CipherType.Identity: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLowerCase()); + this.title = this.i18nService.t("viewItemHeaderIdentity"); break; case CipherType.SecureNote: - this.title = this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase()); + this.title = this.i18nService.t("viewItemHeaderNote"); break; } } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 8462e801de0..78e71b392e5 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -521,36 +521,39 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return await this.cipherService.decrypt(config.originalCipher, activeUserId); } - private updateTitle() { - let partOne: string; + private updateTitle(): void { + const mode = this.formConfig.mode || this.params.mode; + const type = this.cipher?.type ?? this.formConfig.cipherType; + const translation: { [key: string]: { [key: number]: string } } = { + view: { + [CipherType.Login]: "viewItemHeaderLogin", + [CipherType.Card]: "viewItemHeaderCard", + [CipherType.Identity]: "viewItemHeaderIdentity", + [CipherType.SecureNote]: "viewItemHeaderNote", + [CipherType.SshKey]: "viewItemHeaderSshKey", + }, + new: { + [CipherType.Login]: "newItemHeaderLogin", + [CipherType.Card]: "newItemHeaderCard", + [CipherType.Identity]: "newItemHeaderIdentity", + [CipherType.SecureNote]: "newItemHeaderNote", + [CipherType.SshKey]: "newItemHeaderSshKey", + }, + edit: { + [CipherType.Login]: "editItemHeaderLogin", + [CipherType.Card]: "editItemHeaderCard", + [CipherType.Identity]: "editItemHeaderIdentity", + [CipherType.SecureNote]: "editItemHeaderNote", + [CipherType.SshKey]: "editItemHeaderSshKey", + }, + }; - if (this.params.mode === "view") { - partOne = "viewItemType"; - } else if (this.formConfig.mode === "edit" || this.formConfig.mode === "partial-edit") { - partOne = "editItemHeader"; - } else { - partOne = "newItemHeader"; - } + const effectiveMode = + mode === "partial-edit" || mode === "edit" ? "edit" : translation[mode] ? mode : "new"; - const type = this.cipher?.type ?? this.formConfig.cipherType ?? CipherType.Login; + const fullTranslation = translation[effectiveMode][type]; - switch (type) { - case CipherType.Login: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeLogin")); - break; - case CipherType.Card: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeCard")); - break; - case CipherType.Identity: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeIdentity")); - break; - case CipherType.SecureNote: - this.title = this.i18nService.t(partOne, this.i18nService.t("note")); - break; - case CipherType.SshKey: - this.title = this.i18nService.t(partOne, this.i18nService.t("typeSshKey")); - break; - } + this.title = this.i18nService.t(fullTranslation); } /** diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index bfad71aca4b..c09238e7953 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -138,19 +138,15 @@ export class AddEditComponentV2 implements OnInit { * @returns The header text. */ setHeader(mode: CipherFormMode, type: CipherType) { - const partOne = mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader"; - switch (type) { - case CipherType.Login: - return this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLowerCase()); - case CipherType.Card: - return this.i18nService.t(partOne, this.i18nService.t("typeCard").toLowerCase()); - case CipherType.Identity: - return this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLowerCase()); - case CipherType.SecureNote: - return this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase()); - case CipherType.SshKey: - return this.i18nService.t(partOne, this.i18nService.t("typeSshKey").toLowerCase()); - } + const isEditMode = mode === "edit" || mode === "partial-edit"; + const translation = { + [CipherType.Login]: isEditMode ? "editItemHeaderLogin" : "newItemHeaderLogin", + [CipherType.Card]: isEditMode ? "editItemHeaderCard" : "newItemHeaderCard", + [CipherType.Identity]: isEditMode ? "editItemHeaderIdentity" : "newItemHeaderIdentity", + [CipherType.SecureNote]: isEditMode ? "editItemHeaderNote" : "newItemHeaderNote", + [CipherType.SshKey]: isEditMode ? "editItemHeaderSshKey" : "newItemHeaderSshKey", + }; + return this.i18nService.t(translation[type]); } /** 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 97f3037407c..ea0b66f12d0 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -194,15 +194,15 @@ export class ViewComponent implements OnInit { switch (this.cipher.type) { case CipherType.Login: - return this.i18nService.t("viewItemType", this.i18nService.t("typeLogin").toLowerCase()); + return this.i18nService.t("viewItemHeaderLogin"); case CipherType.SecureNote: - return this.i18nService.t("viewItemType", this.i18nService.t("note").toLowerCase()); + return this.i18nService.t("viewItemHeaderCard"); case CipherType.Card: - return this.i18nService.t("viewItemType", this.i18nService.t("typeCard").toLowerCase()); + return this.i18nService.t("viewItemHeaderIdentity"); case CipherType.Identity: - return this.i18nService.t("viewItemType", this.i18nService.t("typeIdentity").toLowerCase()); + return this.i18nService.t("viewItemHeaderNote"); case CipherType.SshKey: - return this.i18nService.t("viewItemType", this.i18nService.t("typeSshKey").toLowerCase()); + return this.i18nService.t("viewItemHeaderSshKey"); default: return null; } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 054ad26efe6..dd2e132ba04 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -734,32 +734,81 @@ "viewItem": { "message": "View item" }, - "newItemHeader": { - "message": "New $TYPE$", - "placeholders": { - "type": { - "content": "$1", - "example": "login" - } - } + "newItemHeaderLogin": { + "message": "New Login", + "description": "Header for new login item type" }, - "editItemHeader": { - "message": "Edit $TYPE$", - "placeholders": { - "type": { - "content": "$1", - "example": "login" - } - } + "newItemHeaderCard": { + "message": "New Card", + "description": "Header for new card item type" }, - "viewItemType": { - "message": "View $ITEMTYPE$", - "placeholders": { - "itemtype": { - "content": "$1", - "example": "login" - } - } + "newItemHeaderIdentity": { + "message": "New Identity", + "description": "Header for new identity item type" + }, + "newItemHeaderNote": { + "message": "New Note", + "description": "Header for new note item type" + }, + "newItemHeaderSshKey": { + "message": "New SSH key", + "description": "Header for new SSH key item type" + }, + "newItemHeaderTextSend": { + "message": "New Text Send", + "description": "Header for new text send" + }, + "newItemHeaderFileSend": { + "message": "New File Send", + "description": "Header for new file send" + }, + "editItemHeaderLogin": { + "message": "Edit Login", + "description": "Header for edit login item type" + }, + "editItemHeaderCard": { + "message": "Edit Card", + "description": "Header for edit card item type" + }, + "editItemHeaderIdentity": { + "message": "Edit Identity", + "description": "Header for edit identity item type" + }, + "editItemHeaderNote": { + "message": "Edit Note", + "description": "Header for edit note item type" + }, + "editItemHeaderSshKey": { + "message": "Edit SSH key", + "description": "Header for edit SSH key item type" + }, + "editItemHeaderTextSend": { + "message": "Edit Text Send", + "description": "Header for edit text send" + }, + "editItemHeaderFileSend": { + "message": "Edit File Send", + "description": "Header for edit file send" + }, + "viewItemHeaderLogin": { + "message": "View Login", + "description": "Header for view login item type" + }, + "viewItemHeaderCard": { + "message": "View Card", + "description": "Header for view card item type" + }, + "viewItemHeaderIdentity": { + "message": "View Identity", + "description": "Header for view identity item type" + }, + "viewItemHeaderNote": { + "message": "View Note", + "description": "Header for view note item type" + }, + "viewItemHeaderSshKey": { + "message": "View SSH key", + "description": "Header for view SSH key item type" }, "new": { "message": "New", @@ -10217,15 +10266,9 @@ "learnMoreAboutApi": { "message": "Learn more about Bitwarden's API" }, - "fileSend": { - "message": "File Send" - }, "fileSends": { "message": "File Sends" }, - "textSend": { - "message": "Text Send" - }, "textSends": { "message": "Text Sends" }, diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 4bcf11bf94f..383bf4be7ec 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -153,15 +153,12 @@ export class SendAddEditDialogComponent { * @returns The header text. */ private getHeaderText(mode: SendFormMode, type: SendType) { - const headerKey = - mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader"; - - switch (type) { - case SendType.Text: - return this.i18nService.t(headerKey, this.i18nService.t("textSend")); - case SendType.File: - return this.i18nService.t(headerKey, this.i18nService.t("fileSend")); - } + const isEditMode = mode === "edit" || mode === "partial-edit"; + const translation = { + [SendType.Text]: isEditMode ? "editItemHeaderTextSend" : "newItemHeaderTextSend", + [SendType.File]: isEditMode ? "editItemHeaderFileSend" : "newItemHeaderFileSend", + }; + return this.i18nService.t(translation[type]); } /** From 4219a31f8801f728d10fbeb1df489a7e9c83d401 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:02:32 -0700 Subject: [PATCH 04/16] filter out restricted ciphers before filtering (#16403) --- .../organizations/collections/vault.component.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index c3e573bc3c5..7c5301ffba0 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/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 { CipherViewLike, CipherViewLikeUtils, @@ -288,6 +289,7 @@ export class vNextVaultComponent implements OnInit, OnDestroy { private billingNotificationService: BillingNotificationService, private organizationWarningsService: OrganizationWarningsService, private collectionService: CollectionService, + private restrictedItemTypesService: RestrictedItemTypesService, ) { this.userId$ = this.accountService.activeAccount$.pipe(getUserId); this.filter$ = this.routedVaultFilterService.filter$; @@ -357,9 +359,10 @@ export class vNextVaultComponent implements OnInit, OnDestroy { this.allCiphers$ = combineLatest([ this.organization$, this.userId$, + this.restrictedItemTypesService.restricted$, this.refreshingSubject$, ]).pipe( - switchMap(async ([organization, userId]) => { + switchMap(async ([organization, userId, restricted]) => { // If user swaps organization reset the addAccessToggle if (!this.showAddAccessToggle || organization) { this.addAccessToggle(0); @@ -381,6 +384,11 @@ export class vNextVaultComponent implements OnInit, OnDestroy { ciphers = await this.cipherService.getManyFromApiForOrganization(organization.id); } + // Filter out restricted ciphers before indexing + ciphers = ciphers.filter( + (cipher) => !this.restrictedItemTypesService.isCipherRestricted(cipher, restricted), + ); + await this.searchService.indexCiphers(userId, ciphers, organization.id); return ciphers; }), From f94e79892032f230fc02140944b41a0d77ae6c78 Mon Sep 17 00:00:00 2001 From: Tyler <71953103+fntyler@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:09:23 -0400 Subject: [PATCH 05/16] BRE-1040 Dockerfile shared ownership (#16189) * BRE-1040 Dockerfile shared ownership * fix: include entrypoint scripts --- .github/CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0adead2928c..76503845fd6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -203,10 +203,10 @@ apps/web/src/locales/en/messages.json .github/workflows/release-web.yml @bitwarden/dept-bre ## Docker files have shared ownership ## -**/Dockerfile -**/*.Dockerfile -**/.dockerignore -**/entrypoint.sh +**/Dockerfile @bitwarden/team-appsec @bitwarden/dept-bre +**/*.Dockerfile @bitwarden/team-appsec @bitwarden/dept-bre +**/*.dockerignore @bitwarden/team-appsec @bitwarden/dept-bre +**/entrypoint.sh @bitwarden/team-appsec @bitwarden/dept-bre ## Overrides # For the time being platform owns tsconfig and jest config From 001f8fa579b6c195d14c1746998c991296b56bde Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:22:05 -0700 Subject: [PATCH 06/16] PM-23906 Wrap sdk callsite with try/catch to handle errors appropriately (#16410) * wrap sdk callsite in try/catch to handle error appropriately `encryptService.decryptString()` calls code in the internal SDK which when provided an invalid key returns `CryptoError::InvalidMac`. The originating callsite has been wrapped in a try/catch in order to intercept the error and return false so that logic in parse() may return a more appropriate error message in the UI. * add unit test and explanatory comment * remove misleading comment * remove null comparison and unused variable --- .../bitwarden-password-protected-importer.spec.ts | 12 ++++++++++++ .../bitwarden-password-protected-importer.ts | 10 ++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts index fc81adebcdc..7812cce2c05 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts @@ -136,5 +136,17 @@ describe("BitwardenPasswordProtectedImporter", () => { jDoc.data = null; expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(false); }); + + it("returns invalidFilePassword errorMessage if decryptString throws", async () => { + encryptService.decryptString.mockImplementation(() => { + throw new Error("SDK error"); + }); + i18nService.t.mockReturnValue("invalidFilePassword"); + + const result = await importer.parse(JSON.stringify(jDoc)); + + expect(result.success).toBe(false); + expect(result.errorMessage).toBe("invalidFilePassword"); + }); }); }); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index 02dedd98c75..7062089482d 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -90,14 +90,12 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT); - const encKeyValidationDecrypt = await this.encryptService.decryptString( - encKeyValidation, - this.key, - ); - if (encKeyValidationDecrypt === null) { + try { + await this.encryptService.decryptString(encKeyValidation, this.key); + return true; + } catch { return false; } - return true; } private cannotParseFile(jdoc: BitwardenPasswordProtectedFileFormat): boolean { From 942d9d666c64dbdf5b962b18b55ceddf435b4576 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 16 Sep 2025 13:35:21 -0500 Subject: [PATCH 07/16] PM-23366 Define Categories and map the events to the categories (#16444) --- libs/common/src/enums/event-category.enum.ts | 110 +++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 libs/common/src/enums/event-category.enum.ts diff --git a/libs/common/src/enums/event-category.enum.ts b/libs/common/src/enums/event-category.enum.ts new file mode 100644 index 00000000000..a00de477301 --- /dev/null +++ b/libs/common/src/enums/event-category.enum.ts @@ -0,0 +1,110 @@ +import { EventType } from "./event-type.enum"; + +export const EventCategory = { + UserEvents: "userEvents", + ItemEvents: "itemEvents", + CollectionEvents: "collectionEvents", + GroupEvents: "groupEvents", + OrganizationMemberEvents: "organizationMemberEvents", + OrganizationEvents: "organizationEvents", + ProviderEvents: "providerEvents", +} as const; + +export type EventCategory = (typeof EventCategory)[keyof typeof EventCategory]; + +export const EventCategoryEventTypes: Record = { + [EventCategory.UserEvents]: [ + EventType.User_LoggedIn, + EventType.User_ChangedPassword, + EventType.User_Updated2fa, + EventType.User_Disabled2fa, + EventType.User_Recovered2fa, + EventType.User_FailedLogIn, + EventType.User_FailedLogIn2fa, + EventType.User_ClientExportedVault, + EventType.User_UpdatedTempPassword, + EventType.User_MigratedKeyToKeyConnector, + EventType.User_RequestedDeviceApproval, + EventType.User_TdeOffboardingPasswordSet, + ], + [EventCategory.ItemEvents]: [ + EventType.Cipher_Created, + EventType.Cipher_Updated, + EventType.Cipher_Deleted, + EventType.Cipher_AttachmentCreated, + EventType.Cipher_AttachmentDeleted, + EventType.Cipher_Shared, + EventType.Cipher_UpdatedCollections, + EventType.Cipher_ClientViewed, + EventType.Cipher_ClientToggledPasswordVisible, + EventType.Cipher_ClientToggledHiddenFieldVisible, + EventType.Cipher_ClientToggledCardCodeVisible, + EventType.Cipher_ClientCopiedPassword, + EventType.Cipher_ClientCopiedHiddenField, + EventType.Cipher_ClientCopiedCardCode, + EventType.Cipher_ClientAutofilled, + EventType.Cipher_SoftDeleted, + EventType.Cipher_Restored, + EventType.Cipher_ClientToggledCardNumberVisible, + EventType.Cipher_ClientToggledTOTPSeedVisible, + ], + [EventCategory.CollectionEvents]: [ + EventType.Collection_Created, + EventType.Collection_Updated, + EventType.Collection_Deleted, + ], + [EventCategory.GroupEvents]: [ + EventType.Group_Created, + EventType.Group_Updated, + EventType.Group_Deleted, + ], + [EventCategory.OrganizationMemberEvents]: [ + EventType.OrganizationUser_Invited, + EventType.OrganizationUser_Confirmed, + EventType.OrganizationUser_Updated, + EventType.OrganizationUser_Removed, + EventType.OrganizationUser_UpdatedGroups, + EventType.OrganizationUser_UnlinkedSso, + EventType.OrganizationUser_ResetPassword_Enroll, + EventType.OrganizationUser_ResetPassword_Withdraw, + EventType.OrganizationUser_AdminResetPassword, + EventType.OrganizationUser_ResetSsoLink, + EventType.OrganizationUser_FirstSsoLogin, + EventType.OrganizationUser_Revoked, + EventType.OrganizationUser_Restored, + EventType.OrganizationUser_ApprovedAuthRequest, + EventType.OrganizationUser_RejectedAuthRequest, + EventType.OrganizationUser_Deleted, + EventType.OrganizationUser_Left, + ], + [EventCategory.OrganizationEvents]: [ + EventType.Organization_Updated, + EventType.Organization_PurgedVault, + EventType.Organization_ClientExportedVault, + EventType.Organization_VaultAccessed, + EventType.Organization_EnabledSso, + EventType.Organization_DisabledSso, + EventType.Organization_EnabledKeyConnector, + EventType.Organization_DisabledKeyConnector, + EventType.Organization_SponsorshipsSynced, + EventType.Organization_CollectionManagementUpdated, + EventType.Organization_CollectionManagement_LimitCollectionCreationEnabled, + EventType.Organization_CollectionManagement_LimitCollectionCreationDisabled, + EventType.Organization_CollectionManagement_LimitCollectionDeletionEnabled, + EventType.Organization_CollectionManagement_LimitCollectionDeletionDisabled, + EventType.Organization_CollectionManagement_LimitItemDeletionEnabled, + EventType.Organization_CollectionManagement_LimitItemDeletionDisabled, + EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled, + EventType.Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled, + ], + [EventCategory.ProviderEvents]: [ + EventType.ProviderUser_Invited, + EventType.ProviderUser_Confirmed, + EventType.ProviderUser_Updated, + EventType.ProviderUser_Removed, + EventType.ProviderOrganization_Created, + EventType.ProviderOrganization_Added, + EventType.ProviderOrganization_Removed, + EventType.ProviderOrganization_VaultAccessed, + ], +}; From 4711e51de3e9ffd8d699cbbec3aa55ddf5992a90 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Tue, 16 Sep 2025 14:41:17 -0400 Subject: [PATCH 08/16] [CL-816] Anon layout icon sizing (#16349) * add max-width to SVG to prevent oversizing * add max width to svg * add comment to explain targeted svg styling --- libs/components/src/anon-layout/anon-layout.component.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/components/src/anon-layout/anon-layout.component.html b/libs/components/src/anon-layout/anon-layout.component.html index f22d12f5138..c66647c482d 100644 --- a/libs/components/src/anon-layout/anon-layout.component.html +++ b/libs/components/src/anon-layout/anon-layout.component.html @@ -14,7 +14,12 @@
-
+ + +
From 3a6088c187788e8167d3eac5d580b0d7c7356cdc Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:52:46 -0700 Subject: [PATCH 09/16] clear orgId when switching to a personal vault (#16396) --- .../src/vault/app/vault/vault-v2.component.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 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 13a8fa91bc4..141f05a28aa 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -926,17 +926,22 @@ export class VaultV2Component } } else if (this.activeFilter.selectedOrganizationId) { this.addOrganizationId = this.activeFilter.selectedOrganizationId; + } else { + // clear out organizationId when the user switches to a personal vault filter + this.addOrganizationId = null; } if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) { this.folderId = this.activeFilter.selectedFolderId; } - if (this.addOrganizationId && this.config) { - this.config.initialValues = { - ...this.config.initialValues, - organizationId: this.addOrganizationId as OrganizationId, - }; + if (this.config == null) { + return; } + + this.config.initialValues = { + ...this.config.initialValues, + organizationId: this.addOrganizationId as OrganizationId, + }; } private async canNavigateAway(action: string, cipher?: CipherView) { From 8be80c705c3d47ed5db5e1c3e01423eeb6755028 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:53:56 -0700 Subject: [PATCH 10/16] [deps] Vault: Update commander to v14 (#16046) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 12 ++++++------ package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 9687d33cb23..659a68d13a5 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -68,7 +68,7 @@ "big-integer": "1.6.52", "browser-hrtime": "1.1.8", "chalk": "4.1.2", - "commander": "11.1.0", + "commander": "14.0.0", "core-js": "3.45.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", diff --git a/package-lock.json b/package-lock.json index 0f9ab32af76..253ca29481c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", - "commander": "11.1.0", + "commander": "14.0.0", "core-js": "3.45.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", @@ -203,7 +203,7 @@ "big-integer": "1.6.52", "browser-hrtime": "1.1.8", "chalk": "4.1.2", - "commander": "11.1.0", + "commander": "14.0.0", "core-js": "3.45.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", @@ -18366,12 +18366,12 @@ } }, "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "license": "MIT", "engines": { - "node": ">=16" + "node": ">=20" } }, "node_modules/common-path-prefix": { diff --git a/package.json b/package.json index c11b1ab0b2e..e93db03fe9f 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", - "commander": "11.1.0", + "commander": "14.0.0", "core-js": "3.45.0", "form-data": "4.0.4", "https-proxy-agent": "7.0.6", From 0a7fb49f3cb3d5cefdd12c9802e1ad492dd201d5 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 16 Sep 2025 13:55:32 -0500 Subject: [PATCH 11/16] PM-25870 feature flag for Activity tab on Risk Insights (#16447) --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index cb28a0ac6ef..e5f2b9bf48a 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -41,6 +41,7 @@ export enum FeatureFlag { /* DIRT */ EventBasedOrganizationIntegrations = "event-based-organization-integrations", + PM22887_RiskInsightsActivityTab = "pm-22887-risk-insights-activity-tab", /* Vault */ PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk", @@ -84,6 +85,7 @@ export const DefaultFeatureFlagValue = { /* DIRT */ [FeatureFlag.EventBasedOrganizationIntegrations]: FALSE, + [FeatureFlag.PM22887_RiskInsightsActivityTab]: FALSE, /* Vault */ [FeatureFlag.CipherKeyEncryption]: FALSE, From 5b167d67485bbea65f6ddfa7e1cab47045bfa35f Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:14:32 -0500 Subject: [PATCH 12/16] [PM-16718] Focus unlock with biometrics button on lock view swap (#16342) --- libs/key-management-ui/src/lock/components/lock.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/key-management-ui/src/lock/components/lock.component.html b/libs/key-management-ui/src/lock/components/lock.component.html index efc7fb26a2f..9a8e8c9f768 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.html +++ b/libs/key-management-ui/src/lock/components/lock.component.html @@ -10,6 +10,7 @@