From 75b21c8d35ebc8c77629c238b1d2c0485a091187 Mon Sep 17 00:00:00 2001 From: Nick Krantz Date: Tue, 2 Dec 2025 15:10:34 -0600 Subject: [PATCH] display translated content for attachments that cannot be downloaded --- apps/browser/src/_locales/en/messages.json | 3 +++ apps/desktop/src/locales/en/messages.json | 3 +++ apps/web/src/locales/en/messages.json | 3 +++ libs/common/src/vault/models/domain/attachment.ts | 6 ++++++ libs/common/src/vault/models/view/attachment.view.ts | 11 ++++++++++- .../attachments/cipher-attachments.component.html | 8 ++++++-- .../attachments/cipher-attachments.component.spec.ts | 4 ++-- .../attachments/cipher-attachments.component.ts | 8 ++++++++ .../attachments/attachments-v2-view.component.html | 8 ++++++-- .../attachments/attachments-v2-view.component.ts | 11 +++++++++++ .../download-attachment.component.spec.ts | 11 +++++------ .../download-attachment.component.ts | 3 +-- 12 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 6a7df1678bf..efb95832df4 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5858,6 +5858,9 @@ "cardNumberLabel": { "message": "Card number" }, + "errorCannotDecrypt": { + "message": "Error: Cannot decrypt" + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 757059c4e41..dac579925f4 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -4235,6 +4235,9 @@ "sessionTimeoutSettingsAction": { "message": "Timeout action" }, + "errorCannotDecrypt": { + "message": "Error: Cannot decrypt" + }, "sessionTimeoutHeader": { "message": "Session timeout" } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 582efade7f4..84f3ca228e4 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12185,6 +12185,9 @@ "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" }, + "errorCannotDecrypt": { + "message": "Error: Cannot decrypt" + }, "userVerificationFailed": { "message": "User verification failed." } diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 7b43af9be55..29689345fac 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -50,6 +50,12 @@ export class Attachment extends Domain { if (this.key != null) { view.key = await this.decryptAttachmentKey(orgId, encKey); view.encryptedKey = this.key; // Keep the encrypted key for the view + + // When the attachment key couldn't be decrypted, mark a decryption error + // The file won't be able to be downloaded in these cases + if (!view.key) { + view.hasDecryptionError = true; + } } return view; diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index ef4a9ed8b27..3f310239538 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -2,7 +2,7 @@ import { Jsonify } from "type-fest"; import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; -import { EncString } from "../../../key-management/crypto/models/enc-string"; +import { DECRYPT_ERROR, EncString } from "../../../key-management/crypto/models/enc-string"; import { View } from "../../../models/view/view"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { Attachment } from "../domain/attachment"; @@ -18,6 +18,7 @@ export class AttachmentView implements View { * The SDK returns an encrypted key for the attachment. */ encryptedKey: EncString | undefined; + private _hasDecryptionError?: boolean; constructor(a?: Attachment) { if (!a) { @@ -41,6 +42,14 @@ export class AttachmentView implements View { return 0; } + get hasDecryptionError(): boolean { + return this._hasDecryptionError || this.fileName === DECRYPT_ERROR; + } + + set hasDecryptionError(value: boolean) { + this._hasDecryptionError = value; + } + static fromJSON(obj: Partial>): AttachmentView { const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key); diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html index 83e5956a067..cbdaa9dd8cf 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html @@ -4,8 +4,12 @@
  • - {{ attachment.fileName }} - {{ attachment.sizeName }} + + {{ getAttachmentFileName(attachment) }} + + @if (!attachment.hasDecryptionError) { + {{ attachment.sizeName }} + } diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 06f62976548..a80c7575100 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -154,8 +154,8 @@ describe("CipherAttachmentsComponent", () => { const fileName = fixture.debugElement.query(By.css('[data-testid="file-name"]')); const fileSize = fixture.debugElement.query(By.css('[data-testid="file-size"]')); - expect(fileName.nativeElement.textContent).toEqual(attachment.fileName); - expect(fileSize.nativeElement.textContent).toEqual(attachment.sizeName); + expect(fileName.nativeElement.textContent.trim()).toEqual(attachment.fileName); + expect(fileSize.nativeElement.textContent.trim()).toEqual(attachment.sizeName); }); describe("bitSubmit", () => { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index a5306606199..b9443f61c6e 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -204,6 +204,14 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { } } + getAttachmentFileName(attachment: AttachmentView): string { + if (attachment.hasDecryptionError) { + return this.i18nService.t("errorCannotDecrypt"); + } + + return attachment.fileName ?? ""; + } + /** Save the attachments to the cipher */ submit = async () => { this.onUploadStarted.emit(); diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html index 67ded3f8358..05860e90295 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -5,8 +5,12 @@ - {{ attachment.fileName }} - {{ attachment.sizeName }} + + {{ getAttachmentFileName(attachment) }} + + @if (!attachment.hasDecryptionError) { + {{ attachment.sizeName }} + } diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts index 4e324d8002e..3826d3a3ad0 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -8,9 +8,11 @@ import { NEVER, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { EmergencyAccessId, OrganizationId } from "@bitwarden/common/types/guid"; import { OrgKey } from "@bitwarden/common/types/key"; +import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ItemModule, @@ -59,6 +61,7 @@ export class AttachmentsV2ViewComponent { private billingAccountProfileStateService: BillingAccountProfileStateService, private stateProvider: StateProvider, private accountService: AccountService, + private i18nService: I18nService, ) { this.subscribeToHasPremiumCheck(); this.subscribeToOrgKey(); @@ -89,4 +92,12 @@ export class AttachmentsV2ViewComponent { } }); } + + getAttachmentFileName(attachment: AttachmentView): string { + if (attachment.hasDecryptionError) { + return this.i18nService.t("errorCannotDecrypt"); + } + + return attachment.fileName ?? ""; + } } diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index ec5a9ce96fd..1d09f3a329e 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -36,12 +36,11 @@ describe("DownloadAttachmentComponent", () => { .mockResolvedValue({ url: "https://www.downloadattachement.com" }); const download = jest.fn(); - const attachment = { - id: "222-3333-4444", - url: "https://www.attachment.com", - fileName: "attachment-filename", - size: "1234", - } as AttachmentView; + const attachment = new AttachmentView(); + attachment.id = "222-3333-4444"; + attachment.url = "https://www.attachment.com"; + attachment.fileName = "attachment-filename"; + attachment.size = "1234"; const cipherView = { id: "5555-444-3333", diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index 2f9cd528990..22fa77a7ac5 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -6,7 +6,6 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DECRYPT_ERROR } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -60,7 +59,7 @@ export class DownloadAttachmentComponent { ) {} protected get isDecryptionFailure(): boolean { - return this.attachment.fileName === DECRYPT_ERROR; + return this.attachment.hasDecryptionError; } /** Download the attachment */