diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index 342042c95f..a6258eaede 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -12,12 +12,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonComponent } from "@bitwarden/components"; +import { CipherAttachmentsComponent } from "@bitwarden/vault"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { AttachmentsV2Component } from "./attachments-v2.component"; -import { CipherAttachmentsComponent } from "./cipher-attachments/cipher-attachments.component"; @Component({ standalone: true, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 20e553ca74..8e6b09bced 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -8,14 +8,13 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ButtonModule } from "@bitwarden/components"; +import { CipherAttachmentsComponent } from "@bitwarden/vault"; import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; -import { CipherAttachmentsComponent } from "./cipher-attachments/cipher-attachments.component"; - @Component({ standalone: true, selector: "app-attachments-v2", diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.html b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.html rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts similarity index 98% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 42c6c530ee..5a2d571c80 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -13,10 +13,10 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ButtonComponent, ToastService } from "@bitwarden/components"; +import { DownloadAttachmentComponent } from "@bitwarden/vault"; import { CipherAttachmentsComponent } from "./cipher-attachments.component"; import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; -import { DownloadAttachmentComponent } from "./download-attachment/download-attachment.component"; @Component({ standalone: true, diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts similarity index 98% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts rename to libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 2111595565..000963d184 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -39,8 +39,9 @@ import { TypographyModule, } from "@bitwarden/components"; +import { DownloadAttachmentComponent } from "../../../components/download-attachment/download-attachment.component"; + import { DeleteAttachmentComponent } from "./delete-attachment/delete-attachment.component"; -import { DownloadAttachmentComponent } from "./download-attachment/download-attachment.component"; type CipherAttachmentForm = FormGroup<{ file: FormControl; diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.html b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.html rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.spec.ts rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/delete-attachment/delete-attachment.component.ts rename to libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts 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 new file mode 100644 index 0000000000..467dd690c6 --- /dev/null +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -0,0 +1,22 @@ + + +

{{ "attachments" | i18n }}

+
+ + + + {{ attachment.fileName }} + {{ 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 new file mode 100644 index 0000000000..d7af28cb1e --- /dev/null +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.ts @@ -0,0 +1,73 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { NEVER, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + ItemModule, + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; + +import { DownloadAttachmentComponent } from "../../components/download-attachment/download-attachment.component"; + +@Component({ + selector: "app-attachments-v2-view", + templateUrl: "attachments-v2-view.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + ItemModule, + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + DownloadAttachmentComponent, + ], +}) +export class AttachmentsV2ViewComponent { + @Input() cipher: CipherView; + + canAccessPremium: boolean; + orgKey: OrgKey; + + constructor( + private cryptoService: CryptoService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private stateProvider: StateProvider, + ) { + this.subscribeToHasPremiumCheck(); + this.subscribeToOrgKey(); + } + + subscribeToHasPremiumCheck() { + this.billingAccountProfileStateService.hasPremiumFromAnySource$ + .pipe(takeUntilDestroyed()) + .subscribe((data) => { + this.canAccessPremium = data; + }); + } + + subscribeToOrgKey() { + this.stateProvider.activeUserId$ + .pipe( + switchMap((userId) => (userId != null ? this.cryptoService.orgKeys$(userId) : NEVER)), + takeUntilDestroyed(), + ) + .subscribe((data: Record | null) => { + if (data) { + this.orgKey = data[this.cipher.organizationId as OrganizationId]; + } + }); + } +} diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2.component.html deleted file mode 100644 index acce6f2622..0000000000 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - -

{{ "attachments" | i18n }}

-
- - -
-

- {{ attachment.fileName }} -

-
- {{ attachment.sizeName }} -
-
-
- - -
-
-
-
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts deleted file mode 100644 index 6ea96bec49..0000000000 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NEVER, switchMap } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { StateProvider } from "@bitwarden/common/platform/state"; -import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey } from "@bitwarden/common/types/key"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { - ToastService, - ItemModule, - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, -} from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -@Component({ - selector: "app-attachments-v2", - templateUrl: "attachments-v2.component.html", - standalone: true, - imports: [ - CommonModule, - JslibModule, - ItemModule, - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, - ], -}) -export class AttachmentsV2Component { - @Input() cipher: CipherView; - - canAccessPremium: boolean; - orgKey: OrgKey; - private passwordReprompted = false; - - constructor( - private passwordRepromptService: PasswordRepromptService, - private i18nService: I18nService, - private apiService: ApiService, - private fileDownloadService: FileDownloadService, - private cryptoService: CryptoService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private toastService: ToastService, - private stateProvider: StateProvider, - private encryptService: EncryptService, - ) { - this.subscribeToHasPremiumCheck(); - this.subscribeToOrgKey(); - } - - subscribeToHasPremiumCheck() { - this.billingAccountProfileStateService.hasPremiumFromAnySource$ - .pipe(takeUntilDestroyed()) - .subscribe((data) => { - this.canAccessPremium = data; - }); - } - - subscribeToOrgKey() { - this.stateProvider.activeUserId$ - .pipe( - switchMap((userId) => (userId != null ? this.cryptoService.orgKeys$(userId) : NEVER)), - takeUntilDestroyed(), - ) - .subscribe((data: Record | null) => { - if (data) { - this.orgKey = data[this.cipher.organizationId as OrganizationId]; - } - }); - } - - async downloadAttachment(attachment: any) { - this.passwordReprompted = - this.passwordReprompted || - (await this.passwordRepromptService.passwordRepromptCheck(this.cipher)); - if (!this.passwordReprompted) { - return; - } - const file = attachment as any; - - if (file.downloading) { - return; - } - - if (this.cipher.organizationId == null && !this.canAccessPremium) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("premiumRequired"), - message: this.i18nService.t("premiumRequiredDesc"), - }); - return; - } - - let url: string; - try { - const attachmentDownloadResponse = await this.apiService.getAttachmentData( - this.cipher.id, - attachment.id, - ); - url = attachmentDownloadResponse.url; - } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - url = attachment.url; - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } - - file.downloading = true; - const response = await fetch(new Request(url, { cache: "no-store" })); - if (response.status !== 200) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - file.downloading = false; - return; - } - - try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = attachment.key != null ? attachment.key : this.orgKey; - const decBuf = await this.encryptService.decryptToBytes(encBuf, key); - this.fileDownloadService.download({ - fileName: attachment.fileName, - blobData: decBuf, - }); - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - - file.downloading = false; - } -} diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 575d80257e..eb62de830d 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -20,7 +20,7 @@ - + diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 4764b57147..c2b32f13fd 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -18,7 +18,7 @@ import { PopupHeaderComponent } from "../../../../apps/browser/src/platform/popu import { PopupPageComponent } from "../../../../apps/browser/src/platform/popup/layout/popup-page.component"; import { AdditionalInformationComponent } from "./additional-information/additional-information.component"; -import { AttachmentsV2Component } from "./attachments/attachments-v2.component"; +import { AttachmentsV2ViewComponent } from "./attachments/attachments-v2-view.component"; import { CustomFieldV2Component } from "./custom-fields/custom-fields-v2.component"; import { ItemDetailsV2Component } from "./item-details/item-details-v2.component"; import { ItemHistoryV2Component } from "./item-history/item-history-v2.component"; @@ -36,7 +36,7 @@ import { ItemHistoryV2Component } from "./item-history/item-history-v2.component PopupFooterComponent, ItemDetailsV2Component, AdditionalInformationComponent, - AttachmentsV2Component, + AttachmentsV2ViewComponent, ItemHistoryV2Component, CustomFieldV2Component, ], diff --git a/libs/vault/src/cipher-view/index.ts b/libs/vault/src/cipher-view/index.ts index 8231f5c161..ec3c523507 100644 --- a/libs/vault/src/cipher-view/index.ts +++ b/libs/vault/src/cipher-view/index.ts @@ -1 +1,2 @@ export * from "./cipher-view.component"; +export { CipherAttachmentsComponent } from "../cipher-form/components/attachments/cipher-attachments.component"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.html b/libs/vault/src/components/download-attachment/download-attachment.component.html similarity index 100% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.html rename to libs/vault/src/components/download-attachment/download-attachment.component.html diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts similarity index 95% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts rename to libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index 45c9e7fb37..39a6e6bc2f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -14,8 +14,9 @@ import { StateProvider } from "@bitwarden/common/platform/state"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { ToastService } from "@bitwarden/components"; -import { ToastService } from "../../../../../../../../../../libs/components/src/toast"; +import { PasswordRepromptService } from "../../services/password-reprompt.service"; import { DownloadAttachmentComponent } from "./download-attachment.component"; @@ -65,6 +66,7 @@ describe("DownloadAttachmentComponent", () => { { provide: ToastService, useValue: { showToast } }, { provide: ApiService, useValue: { getAttachmentData } }, { provide: FileDownloadService, useValue: { download } }, + { provide: PasswordRepromptService, useValue: mock() }, ], }).compileComponents(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts similarity index 87% rename from apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts rename to libs/vault/src/components/download-attachment/download-attachment.component.ts index 528695eab4..3e00207c00 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/cipher-attachments/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -18,6 +18,8 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; +import { PasswordRepromptService } from "../../services/password-reprompt.service"; + @Component({ standalone: true, selector: "app-download-attachment", @@ -31,9 +33,14 @@ export class DownloadAttachmentComponent { /** The cipher associated with the attachment */ @Input({ required: true }) cipher: CipherView; + // When in view mode, we will want to check for the master password reprompt + @Input() checkPwReprompt?: boolean = false; + /** The organization key if the cipher is associated with one */ private orgKey: OrgKey | null = null; + private passwordReprompted = false; + constructor( private i18nService: I18nService, private apiService: ApiService, @@ -42,6 +49,7 @@ export class DownloadAttachmentComponent { private encryptService: EncryptService, private stateProvider: StateProvider, private cryptoService: CryptoService, + private passwordRepromptService: PasswordRepromptService, ) { this.stateProvider.activeUserId$ .pipe( @@ -57,6 +65,15 @@ export class DownloadAttachmentComponent { /** Download the attachment */ download = async () => { + if (this.checkPwReprompt) { + this.passwordReprompted = + this.passwordReprompted || + (await this.passwordRepromptService.passwordRepromptCheck(this.cipher)); + if (!this.passwordReprompted) { + return; + } + } + let url: string; try { diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index 5dee70ea46..b48d359b90 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -9,3 +9,5 @@ export { CollectionAssignmentParams, CollectionAssignmentResult, } from "./components/assign-collections.component"; + +export { DownloadAttachmentComponent } from "./components/download-attachment/download-attachment.component";