1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-12 14:34:02 +00:00

display translated content for attachments that cannot be downloaded

This commit is contained in:
Nick Krantz
2025-12-02 15:10:34 -06:00
parent 365af52e33
commit 75b21c8d35
12 changed files with 64 additions and 15 deletions

View File

@@ -5858,6 +5858,9 @@
"cardNumberLabel": {
"message": "Card number"
},
"errorCannotDecrypt": {
"message": "Error: Cannot decrypt"
},
"sessionTimeoutSettingsAction": {
"message": "Timeout action"
}

View File

@@ -4235,6 +4235,9 @@
"sessionTimeoutSettingsAction": {
"message": "Timeout action"
},
"errorCannotDecrypt": {
"message": "Error: Cannot decrypt"
},
"sessionTimeoutHeader": {
"message": "Session timeout"
}

View File

@@ -12185,6 +12185,9 @@
"confirmNoSelectedCriticalApplicationsDesc": {
"message": "Are you sure you want to continue?"
},
"errorCannotDecrypt": {
"message": "Error: Cannot decrypt"
},
"userVerificationFailed": {
"message": "User verification failed."
}

View File

@@ -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;

View File

@@ -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<Jsonify<AttachmentView>>): AttachmentView {
const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key);

View File

@@ -4,8 +4,12 @@
<li *ngFor="let attachment of cipher.attachments">
<bit-item>
<bit-item-content>
<span data-testid="file-name" [title]="attachment.fileName">{{ attachment.fileName }}</span>
<span slot="secondary" data-testid="file-size">{{ attachment.sizeName }}</span>
<span data-testid="file-name" [title]="getAttachmentFileName(attachment)">
{{ getAttachmentFileName(attachment) }}
</span>
@if (!attachment.hasDecryptionError) {
<span slot="secondary" data-testid="file-size">{{ attachment.sizeName }}</span>
}
</bit-item-content>
<ng-container slot="end">
<bit-item-action>

View File

@@ -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", () => {

View File

@@ -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();

View File

@@ -5,8 +5,12 @@
<bit-item-group>
<bit-item *ngFor="let attachment of cipher.attachments">
<bit-item-content>
<span data-testid="file-name" [title]="attachment.fileName">{{ attachment.fileName }}</span>
<span slot="secondary" data-testid="file-size">{{ attachment.sizeName }}</span>
<span data-testid="file-name" [title]="getAttachmentFileName(attachment)">
{{ getAttachmentFileName(attachment) }}
</span>
@if (!attachment.hasDecryptionError) {
<span slot="secondary" data-testid="file-size">{{ attachment.sizeName }}</span>
}
</bit-item-content>
<ng-container slot="end">
<bit-item-action>

View File

@@ -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 ?? "";
}
}

View File

@@ -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",

View File

@@ -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 */