diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 0a60478c94d..db3e69f7d6f 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -17,7 +17,6 @@ import { import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { LoginComponent, LoginSecondaryContentComponent, @@ -50,7 +49,6 @@ import { SetPasswordComponent } from "../auth/set-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; -import { VaultComponent } from "../vault/app/vault/vault.component"; import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component"; import { SendComponent } from "./tools/send/send.component"; @@ -102,15 +100,11 @@ const routes: Routes = [ }, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, - ...featureFlaggedRoute({ - defaultComponent: VaultComponent, - flaggedComponent: VaultV2Component, - featureFlag: FeatureFlag.PM18520_UpdateDesktopCipherForm, - routeOptions: { - path: "vault", - canActivate: [authGuard], - }, - }), + { + path: "vault", + component: VaultV2Component, + canActivate: [authGuard], + }, { path: "set-password", component: SetPasswordComponent }, { path: "send", diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 58c3e10e334..112732d8f2c 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -18,19 +18,8 @@ import { UpdateTempPasswordComponent } from "../auth/update-temp-password.compon import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; -import { AddEditCustomFieldsComponent } from "../vault/app/vault/add-edit-custom-fields.component"; -import { AddEditComponent } from "../vault/app/vault/add-edit.component"; -import { AttachmentsComponent } from "../vault/app/vault/attachments.component"; -import { CollectionsComponent } from "../vault/app/vault/collections.component"; -import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component"; -import { PasswordHistoryComponent } from "../vault/app/vault/password-history.component"; -import { ShareComponent } from "../vault/app/vault/share.component"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; -import { VaultItemsComponent } from "../vault/app/vault/vault-items.component"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; -import { VaultComponent } from "../vault/app/vault/vault.component"; -import { ViewCustomFieldsComponent } from "../vault/app/vault/view-custom-fields.component"; -import { ViewComponent } from "../vault/app/vault/view.component"; import { SettingsComponent } from "./accounts/settings.component"; import { VaultTimeoutInputComponent } from "./accounts/vault-timeout-input.component"; @@ -61,28 +50,17 @@ import { SharedModule } from "./shared/shared.module"; ], declarations: [ AccountSwitcherComponent, - AddEditComponent, - AddEditCustomFieldsComponent, AppComponent, - AttachmentsComponent, - CollectionsComponent, ColorPasswordPipe, ColorPasswordCountPipe, - FolderAddEditComponent, HeaderComponent, - PasswordHistoryComponent, PremiumComponent, RemovePasswordComponent, SearchComponent, SetPasswordComponent, SettingsComponent, - ShareComponent, UpdateTempPasswordComponent, - VaultComponent, - VaultItemsComponent, VaultTimeoutInputComponent, - ViewCustomFieldsComponent, - ViewComponent, ], providers: [SshAgentService], bootstrap: [AppComponent], diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index ce03406dd24..3c3d4ff508c 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -441,10 +441,6 @@ img, user-select: none; } -app-vault-view .box-footer { - user-select: auto; -} - /* override for vault icon in desktop */ app-vault-icon > div { display: flex; diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.html b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.html deleted file mode 100644 index bc0c3876b71..00000000000 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.html +++ /dev/null @@ -1,135 +0,0 @@ -
-

- {{ "customFields" | i18n }} -

-
-
-
- - - -
- - - - - - - -
- - -
- -
-
- -
-
-
- -
- - - -
-
-
diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts deleted file mode 100644 index b4be2406c4b..00000000000 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from "@angular/core"; - -import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/vault/components/add-edit-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -@Component({ - selector: "app-vault-add-edit-custom-fields", - templateUrl: "add-edit-custom-fields.component.html", - standalone: false, -}) -export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { - constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { - super(i18nService, eventCollectionService); - } -} diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.html b/apps/desktop/src/vault/app/vault/add-edit.component.html deleted file mode 100644 index 2cd384885ce..00000000000 --- a/apps/desktop/src/vault/app/vault/add-edit.component.html +++ /dev/null @@ -1,826 +0,0 @@ -
-
-
-
- - {{ "personalOwnershipPolicyInEffect" | i18n }} - -

- {{ title }} -

-
-
- - -
-
- - -
- -
-
-
- - -
-
- -
-
-
-
- - -
-
- - - -
-
- -
- -
- {{ "typePasskey" | i18n }} - {{ fido2CredentialCreationDateValue }} -
-
- -
- - -
-
- -
-
- - -
-
-
- - -
-
- -
-
-
- - - - - - - -
-
- - - - - - - -
-
- - -
-
-
- - -
-
- -
-
-
- -
-
- - - - - - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
- -
-
-
-
- - - -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
-
-
-
- -
- -
- - - - -
-
- -
-
-
- -
-
-
-
-
- - -
-
- - -
-
- - -
- - -
-
-
-

- -

-
-
- -
-
-
- - -
-

- {{ "ownership" | i18n }} -

-
-
- - -
-
-
-
-

- {{ "collections" | i18n }} -

-
- {{ "noCollectionsInList" | i18n }} -
-
-
- - -
-
-
-
-
- -
diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts deleted file mode 100644 index e9b18270f2d..00000000000 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ /dev/null @@ -1,185 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe } from "@angular/common"; -import { Component, NgZone, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { NgForm } from "@angular/forms"; -import { map, shareReplay } from "rxjs"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; -import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; - -const BroadcasterSubscriptionId = "AddEditComponent"; - -@Component({ - selector: "app-vault-add-edit", - templateUrl: "add-edit.component.html", - standalone: false, -}) -export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { - @ViewChild("form") - private form: NgForm; - menuItems$ = this.restrictedItemTypesService.restricted$.pipe( - map((restrictedItemTypes) => - // Filter out restricted item types from the default CIPHER_MENU_ITEMS array - CIPHER_MENU_ITEMS.filter( - (typeOption) => - !restrictedItemTypes.some( - (restrictedType) => restrictedType.cipherType === typeOption.type, - ), - ), - ), - shareReplay({ bufferSize: 1, refCount: true }), - ); - - constructor( - cipherService: CipherService, - folderService: FolderService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - accountService: AccountService, - collectionService: CollectionService, - messagingService: MessagingService, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - passwordRepromptService: PasswordRepromptService, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - logService: LogService, - organizationService: OrganizationService, - dialogService: DialogService, - datePipe: DatePipe, - configService: ConfigService, - toastService: ToastService, - cipherAuthorizationService: CipherAuthorizationService, - sdkService: SdkService, - sshImportPromptService: SshImportPromptService, - protected restrictedItemTypesService: RestrictedItemTypesService, - ) { - super( - cipherService, - folderService, - i18nService, - platformUtilsService, - auditService, - accountService, - collectionService, - messagingService, - eventCollectionService, - policyService, - logService, - passwordRepromptService, - organizationService, - dialogService, - window, - datePipe, - configService, - cipherAuthorizationService, - toastService, - sdkService, - sshImportPromptService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - await this.load(); - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - default: - } - }); - }); - // We use ngOnChanges for everything else instead. - } - - async ngOnChanges() { - await this.load(); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async load() { - if ( - document.querySelectorAll("app-vault-add-edit .ng-dirty").length === 0 || - (this.cipher != null && this.cipherId !== this.cipher.id) - ) { - this.cipher = null; - } - - await super.load(); - } - - onWindowHidden() { - this.showPassword = false; - this.showCardNumber = false; - this.showCardCode = false; - if (this.cipher !== null && this.cipher.hasFields) { - this.cipher.fields.forEach((field) => { - field.showValue = false; - }); - } - } - - allowOwnershipOptions(): boolean { - return ( - (!this.editMode || this.cloneMode) && - this.ownershipOptions && - (this.ownershipOptions.length > 1 || !this.allowPersonal) - ); - } - - markPasswordAsDirty() { - this.form.controls["Login.Password"].markAsDirty(); - } - - openHelpReprompt() { - this.platformUtilsService.launchUri( - "https://bitwarden.com/help/managing-items/#protect-individual-items", - ); - } - - /** - * Updates the cipher when an attachment is altered. - * Note: This only updates the `attachments` and `revisionDate` - * properties to ensure any in-progress edits are not lost. - */ - patchCipherAttachments(cipher: CipherView) { - this.cipher.attachments = cipher.attachments; - this.cipher.revisionDate = cipher.revisionDate; - } - - truncateString(value: string, length: number) { - return value.length > length ? value.substring(0, length) + "..." : value; - } - - togglePrivateKey() { - this.showPrivateKey = !this.showPrivateKey; - } -} diff --git a/apps/desktop/src/vault/app/vault/attachments.component.html b/apps/desktop/src/vault/app/vault/attachments.component.html deleted file mode 100644 index addd068f1a4..00000000000 --- a/apps/desktop/src/vault/app/vault/attachments.component.html +++ /dev/null @@ -1,69 +0,0 @@ - diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts deleted file mode 100644 index a116a4d2acb..00000000000 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Component } from "@angular/core"; - -import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/angular/vault/components/attachments.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-vault-attachments", - templateUrl: "attachments.component.html", - standalone: false, -}) -export class AttachmentsComponent extends BaseAttachmentsComponent { - constructor( - cipherService: CipherService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - apiService: ApiService, - logService: LogService, - stateService: StateService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - configService: ConfigService, - ) { - super( - cipherService, - i18nService, - keyService, - encryptService, - platformUtilsService, - apiService, - window, - logService, - stateService, - fileDownloadService, - dialogService, - billingAccountProfileStateService, - accountService, - toastService, - configService, - ); - } -} diff --git a/apps/desktop/src/vault/app/vault/collections.component.html b/apps/desktop/src/vault/app/vault/collections.component.html deleted file mode 100644 index a87cbc6b180..00000000000 --- a/apps/desktop/src/vault/app/vault/collections.component.html +++ /dev/null @@ -1,43 +0,0 @@ - diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts deleted file mode 100644 index 46455d04cd2..00000000000 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component } from "@angular/core"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { CollectionsComponent as BaseCollectionsComponent } from "@bitwarden/angular/admin-console/components/collections.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-vault-collections", - templateUrl: "collections.component.html", - standalone: false, -}) -export class CollectionsComponent extends BaseCollectionsComponent { - constructor( - cipherService: CipherService, - i18nService: I18nService, - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - organizationService: OrganizationService, - logService: LogService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - organizationService, - logService, - accountService, - toastService, - ); - } -} diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.html b/apps/desktop/src/vault/app/vault/folder-add-edit.component.html deleted file mode 100644 index d22628df046..00000000000 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.html +++ /dev/null @@ -1,59 +0,0 @@ - diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts deleted file mode 100644 index cecd5cd671c..00000000000 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; - -import { FolderAddEditComponent as BaseFolderAddEditComponent } from "@bitwarden/angular/vault/components/folder-add-edit.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Component({ - selector: "app-folder-add-edit", - templateUrl: "folder-add-edit.component.html", - standalone: false, -}) -export class FolderAddEditComponent extends BaseFolderAddEditComponent { - constructor( - folderService: FolderService, - folderApiService: FolderApiServiceAbstraction, - accountService: AccountService, - keyService: KeyService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - logService: LogService, - dialogService: DialogService, - formBuilder: FormBuilder, - toastService: ToastService, - ) { - super( - folderService, - folderApiService, - accountService, - keyService, - i18nService, - platformUtilsService, - logService, - dialogService, - formBuilder, - toastService, - ); - } -} diff --git a/apps/desktop/src/vault/app/vault/password-history.component.html b/apps/desktop/src/vault/app/vault/password-history.component.html deleted file mode 100644 index 362061b250d..00000000000 --- a/apps/desktop/src/vault/app/vault/password-history.component.html +++ /dev/null @@ -1,38 +0,0 @@ - diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts deleted file mode 100644 index e83ce0d56ea..00000000000 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component } from "@angular/core"; - -import { PasswordHistoryComponent as BasePasswordHistoryComponent } from "@bitwarden/angular/vault/components/password-history.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ToastService } from "@bitwarden/components"; - -@Component({ - selector: "app-password-history", - templateUrl: "password-history.component.html", - standalone: false, -}) -export class PasswordHistoryComponent extends BasePasswordHistoryComponent { - constructor( - cipherService: CipherService, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - accountService: AccountService, - toastService: ToastService, - ) { - super(cipherService, platformUtilsService, i18nService, accountService, window, toastService); - } -} diff --git a/apps/desktop/src/vault/app/vault/share.component.html b/apps/desktop/src/vault/app/vault/share.component.html deleted file mode 100644 index 8f85ecf891e..00000000000 --- a/apps/desktop/src/vault/app/vault/share.component.html +++ /dev/null @@ -1,76 +0,0 @@ - diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts deleted file mode 100644 index 50842439323..00000000000 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Component } from "@angular/core"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ShareComponent as BaseShareComponent } from "@bitwarden/angular/components/share.component"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; - -@Component({ - selector: "app-vault-share", - templateUrl: "share.component.html", - standalone: false, -}) -export class ShareComponent extends BaseShareComponent { - constructor( - cipherService: CipherService, - i18nService: I18nService, - collectionService: CollectionService, - platformUtilsService: PlatformUtilsService, - logService: LogService, - organizationService: OrganizationService, - accountService: AccountService, - private modalRef: ModalRef, - ) { - super( - collectionService, - platformUtilsService, - i18nService, - cipherService, - logService, - organizationService, - accountService, - ); - } - - protected close() { - this.modalRef.close(); - } -} diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.html b/apps/desktop/src/vault/app/vault/vault-items.component.html deleted file mode 100644 index 8a869cd2a32..00000000000 --- a/apps/desktop/src/vault/app/vault/vault-items.component.html +++ /dev/null @@ -1,72 +0,0 @@ -
- -
- -
- -
- -
-
-
- -

{{ "noItemsInList" | i18n }}

- -
- -
-
diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts deleted file mode 100644 index c37a29833d9..00000000000 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component } from "@angular/core"; -import { distinctUntilChanged } from "rxjs"; - -import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; - -import { SearchBarService } from "../../../app/layout/search/search-bar.service"; - -@Component({ - selector: "app-vault-items", - templateUrl: "vault-items.component.html", - standalone: false, -}) -export class VaultItemsComponent extends BaseVaultItemsComponent { - constructor( - searchService: SearchService, - searchBarService: SearchBarService, - cipherService: CipherService, - accountService: AccountService, - protected restrictedItemTypesService: RestrictedItemTypesService, - ) { - super(searchService, cipherService, accountService, restrictedItemTypesService); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => { - this.searchText = searchText; - }); - } - - trackByFn(index: number, c: CipherView) { - return c.id; - } -} diff --git a/apps/desktop/src/vault/app/vault/vault.component.html b/apps/desktop/src/vault/app/vault/vault.component.html deleted file mode 100644 index 9a25619b1a8..00000000000 --- a/apps/desktop/src/vault/app/vault/vault.component.html +++ /dev/null @@ -1,74 +0,0 @@ -
- - - - - - - -
- - -
-
- - - - - - diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts deleted file mode 100644 index d8a54f1ec35..00000000000 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ /dev/null @@ -1,870 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { - ChangeDetectorRef, - Component, - NgZone, - OnDestroy, - OnInit, - ViewChild, - ViewContainerRef, -} from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; -import { filter, first, map, take } from "rxjs/operators"; - -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EventType } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherId, UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType, toCipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - AddEditFolderDialogComponent, - AddEditFolderDialogResult, - DecryptionFailureDialogComponent, - PasswordRepromptService, -} from "@bitwarden/vault"; - -import { SearchBarService } from "../../../app/layout/search/search-bar.service"; -import { invokeMenu, RendererMenuItem } from "../../../utils"; - -import { AddEditComponent } from "./add-edit.component"; -import { AttachmentsComponent } from "./attachments.component"; -import { CollectionsComponent } from "./collections.component"; -import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; -import { PasswordHistoryComponent } from "./password-history.component"; -import { ShareComponent } from "./share.component"; -import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; -import { VaultItemsComponent } from "./vault-items.component"; -import { ViewComponent } from "./view.component"; - -const BroadcasterSubscriptionId = "VaultComponent"; - -@Component({ - selector: "app-vault", - templateUrl: "vault.component.html", - standalone: false, -}) -export class VaultComponent implements OnInit, OnDestroy { - @ViewChild(ViewComponent) viewComponent: ViewComponent; - @ViewChild(AddEditComponent) addEditComponent: AddEditComponent; - @ViewChild(VaultItemsComponent, { static: true }) vaultItemsComponent: VaultItemsComponent; - @ViewChild("generator", { read: ViewContainerRef, static: true }) - generatorModalRef: ViewContainerRef; - @ViewChild(VaultFilterComponent, { static: true }) vaultFilterComponent: VaultFilterComponent; - @ViewChild("attachments", { read: ViewContainerRef, static: true }) - attachmentsModalRef: ViewContainerRef; - @ViewChild("passwordHistory", { read: ViewContainerRef, static: true }) - passwordHistoryModalRef: ViewContainerRef; - @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; - @ViewChild("collections", { read: ViewContainerRef, static: true }) - collectionsModalRef: ViewContainerRef; - - action: string; - cipherId: string = null; - favorites = false; - type: CipherType = null; - folderId: string = null; - collectionId: string = null; - organizationId: string = null; - myVaultOnly = false; - addType: CipherType = null; - addOrganizationId: string = null; - addCollectionIds: string[] = null; - showingModal = false; - deleted = false; - userHasPremiumAccess = false; - activeFilter: VaultFilter = new VaultFilter(); - activeUserId: UserId; - cipherRepromptId: string | null = null; - - private modal: ModalRef = null; - private componentIsDestroyed$ = new Subject(); - - constructor( - private route: ActivatedRoute, - private router: Router, - private i18nService: I18nService, - private modalService: ModalService, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private ngZone: NgZone, - private syncService: SyncService, - private messagingService: MessagingService, - private platformUtilsService: PlatformUtilsService, - private eventCollectionService: EventCollectionService, - private totpService: TotpService, - private passwordRepromptService: PasswordRepromptService, - private searchBarService: SearchBarService, - private apiService: ApiService, - private dialogService: DialogService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private toastService: ToastService, - private accountService: AccountService, - private cipherService: CipherService, - private folderService: FolderService, - private authRequestService: AuthRequestServiceAbstraction, - private configService: ConfigService, - ) {} - - async ngOnInit() { - this.accountService.activeAccount$ - .pipe( - switchMap((account) => - this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), - ), - takeUntil(this.componentIsDestroyed$), - ) - .subscribe((canAccessPremium: boolean) => { - this.userHasPremiumAccess = canAccessPremium; - }); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ngZone.run(async () => { - let detectChanges = true; - - switch (message.command) { - case "newLogin": - await this.addCipher(CipherType.Login); - break; - case "newCard": - await this.addCipher(CipherType.Card); - break; - case "newIdentity": - await this.addCipher(CipherType.Identity); - break; - case "newSecureNote": - await this.addCipher(CipherType.SecureNote); - break; - case "newSshKey": - await this.addCipher(CipherType.SshKey); - break; - case "focusSearch": - (document.querySelector("#search") as HTMLInputElement).select(); - detectChanges = false; - break; - case "syncCompleted": - await this.vaultItemsComponent.reload(this.activeFilter.buildFilter()); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); - await this.vaultFilterComponent.reloadOrganizations(); - break; - case "modalShown": - this.showingModal = true; - break; - case "modalClosed": - this.showingModal = false; - break; - case "copyUsername": { - const uComponent = - this.addEditComponent == null ? this.viewComponent : this.addEditComponent; - const uCipher = uComponent != null ? uComponent.cipher : null; - if ( - this.cipherId != null && - uCipher != null && - uCipher.id === this.cipherId && - uCipher.login != null && - uCipher.login.username != null - ) { - this.copyValue(uCipher, uCipher.login.username, "username", "Username"); - } - break; - } - case "copyPassword": { - const pComponent = - this.addEditComponent == null ? this.viewComponent : this.addEditComponent; - const pCipher = pComponent != null ? pComponent.cipher : null; - if ( - this.cipherId != null && - pCipher != null && - pCipher.id === this.cipherId && - pCipher.login != null && - pCipher.login.password != null && - pCipher.viewPassword - ) { - this.copyValue(pCipher, pCipher.login.password, "password", "Password"); - } - break; - } - case "copyTotp": { - const tComponent = - this.addEditComponent == null ? this.viewComponent : this.addEditComponent; - const tCipher = tComponent != null ? tComponent.cipher : null; - if ( - this.cipherId != null && - tCipher != null && - tCipher.id === this.cipherId && - tCipher.login != null && - tCipher.login.hasTotp && - this.userHasPremiumAccess - ) { - const value = await firstValueFrom(this.totpService.getCode$(tCipher.login.totp)); - this.copyValue(tCipher, value.code, "verificationCodeTotp", "TOTP"); - } - break; - } - default: - detectChanges = false; - break; - } - - if (detectChanges) { - this.changeDetectorRef.detectChanges(); - } - }); - }); - - if (!this.syncService.syncInProgress) { - await this.load(); - } - - this.searchBarService.setEnabled(true); - this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); - - const browserLoginApprovalFeatureFlag = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.PM14938_BrowserExtensionLoginApproval), - ); - if (browserLoginApprovalFeatureFlag === true) { - const authRequests = await firstValueFrom(this.authRequestService.getPendingAuthRequests$()); - // There is a chance that there is more than one auth request in the response we only show the most recent one - if (authRequests.length > 0) { - const mostRecentAuthRequest = authRequests.reduce((latest, current) => { - const latestDate = new Date(latest.creationDate).getTime(); - const currentDate = new Date(current.creationDate).getTime(); - return currentDate > latestDate ? current : latest; - }); - - this.messagingService.send("openLoginApproval", { - notificationId: mostRecentAuthRequest.id, - }); - } - } else { - const authRequest = await this.apiService.getLastAuthRequest(); - if (authRequest != null) { - this.messagingService.send("openLoginApproval", { - notificationId: authRequest.id, - }); - } - } - - this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - this.cipherService - .failedToDecryptCiphers$(this.activeUserId) - .pipe( - map((ciphers) => ciphers?.filter((c) => !c.isDeleted) ?? []), - filter((ciphers) => ciphers.length > 0), - take(1), - takeUntil(this.componentIsDestroyed$), - ) - .subscribe((ciphers) => { - DecryptionFailureDialogComponent.open(this.dialogService, { - cipherIds: ciphers.map((c) => c.id as CipherId), - }); - }); - } - - ngOnDestroy() { - this.searchBarService.setEnabled(false); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.componentIsDestroyed$.next(true); - this.componentIsDestroyed$.complete(); - } - - async load() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - const cipherView = new CipherView(); - cipherView.id = params.cipherId; - if (params.action === "clone") { - await this.cloneCipher(cipherView); - } else if (params.action === "edit") { - await this.editCipher(cipherView); - } else { - await this.viewCipher(cipherView); - } - } else if (params.action === "add") { - this.addType = toCipherType(params.addType); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.addCipher(this.addType); - } - - const paramCipherType = toCipherType(params.type); - this.activeFilter = new VaultFilter({ - status: params.deleted ? "trash" : params.favorites ? "favorites" : "all", - cipherType: params.action === "add" || paramCipherType == null ? null : paramCipherType, - selectedFolderId: params.folderId, - selectedCollectionId: params.selectedCollectionId, - selectedOrganizationId: params.selectedOrganizationId, - myVaultOnly: params.myVaultOnly ?? false, - }); - await this.vaultItemsComponent.reload(this.activeFilter.buildFilter()); - }); - } - - async viewCipher(cipher: CipherView) { - if (!(await this.canNavigateAway("view", cipher))) { - return; - } else if (!(await this.passwordReprompt(cipher))) { - return; - } - - this.cipherId = cipher.id; - this.action = "view"; - this.go(); - } - - viewCipherMenu(cipher: CipherView) { - const menu: RendererMenuItem[] = [ - { - label: this.i18nService.t("view"), - click: () => - this.functionWithChangeDetection(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.viewCipher(cipher); - }), - }, - ]; - - if (cipher.decryptionFailure) { - invokeMenu(menu); - return; - } - - if (!cipher.isDeleted) { - menu.push({ - label: this.i18nService.t("edit"), - click: () => - this.functionWithChangeDetection(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.editCipher(cipher); - }), - }); - if (!cipher.organizationId) { - menu.push({ - label: this.i18nService.t("clone"), - click: () => - this.functionWithChangeDetection(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.cloneCipher(cipher); - }), - }); - } - } - - switch (cipher.type) { - case CipherType.Login: - if ( - cipher.login.canLaunch || - cipher.login.username != null || - cipher.login.password != null - ) { - menu.push({ type: "separator" }); - } - if (cipher.login.canLaunch) { - menu.push({ - label: this.i18nService.t("launch"), - click: () => this.platformUtilsService.launchUri(cipher.login.launchUri), - }); - } - if (cipher.login.username != null) { - menu.push({ - label: this.i18nService.t("copyUsername"), - click: () => this.copyValue(cipher, cipher.login.username, "username", "Username"), - }); - } - if (cipher.login.password != null && cipher.viewPassword) { - menu.push({ - label: this.i18nService.t("copyPassword"), - click: () => { - this.copyValue(cipher, cipher.login.password, "password", "Password"); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); - }, - }); - } - if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) { - menu.push({ - label: this.i18nService.t("copyVerificationCodeTotp"), - click: async () => { - const value = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); - this.copyValue(cipher, value.code, "verificationCodeTotp", "TOTP"); - }, - }); - } - break; - case CipherType.Card: - if (cipher.card.number != null || cipher.card.code != null) { - menu.push({ type: "separator" }); - } - if (cipher.card.number != null) { - menu.push({ - label: this.i18nService.t("copyNumber"), - click: () => this.copyValue(cipher, cipher.card.number, "number", "Card Number"), - }); - } - if (cipher.card.code != null) { - menu.push({ - label: this.i18nService.t("copySecurityCode"), - click: () => { - this.copyValue(cipher, cipher.card.code, "securityCode", "Security Code"); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, cipher.id); - }, - }); - } - break; - default: - break; - } - - invokeMenu(menu); - } - - async editCipher(cipher: CipherView) { - if (!(await this.canNavigateAway("edit", cipher))) { - return; - } else if (!(await this.passwordReprompt(cipher))) { - return; - } - - await this.editCipherWithoutPasswordPrompt(cipher); - } - - async editCipherWithoutPasswordPrompt(cipher: CipherView) { - if (!(await this.canNavigateAway("edit", cipher))) { - return; - } - - this.cipherId = cipher.id; - this.action = "edit"; - this.go(); - } - - async cloneCipher(cipher: CipherView) { - if (!(await this.canNavigateAway("clone", cipher))) { - return; - } else if (!(await this.passwordReprompt(cipher))) { - return; - } - - await this.cloneCipherWithoutPasswordPrompt(cipher); - } - - async cloneCipherWithoutPasswordPrompt(cipher: CipherView) { - if (!(await this.canNavigateAway("edit", cipher))) { - return; - } - - this.cipherId = cipher.id; - this.action = "clone"; - this.go(); - } - - async addCipher(type: CipherType = null) { - if (!(await this.canNavigateAway("add", null))) { - return; - } - - this.addType = type || this.activeFilter.cipherType; - this.action = "add"; - this.cipherId = null; - this.prefillNewCipherFromFilter(); - this.go(); - - if (type === CipherType.SshKey) { - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyGenerated"), - }); - } - } - - addCipherOptions() { - const menu: RendererMenuItem[] = [ - { - label: this.i18nService.t("typeLogin"), - click: () => this.addCipherWithChangeDetection(CipherType.Login), - }, - { - label: this.i18nService.t("typeCard"), - click: () => this.addCipherWithChangeDetection(CipherType.Card), - }, - { - label: this.i18nService.t("typeIdentity"), - click: () => this.addCipherWithChangeDetection(CipherType.Identity), - }, - { - label: this.i18nService.t("typeSecureNote"), - click: () => this.addCipherWithChangeDetection(CipherType.SecureNote), - }, - ]; - - invokeMenu(menu); - } - - async savedCipher(cipher: CipherView) { - this.cipherId = null; - this.action = "view"; - await this.vaultItemsComponent.refresh(); - this.cipherId = cipher.id; - await this.cipherService.clearCache(this.activeUserId); - await this.vaultItemsComponent.load(this.activeFilter.buildFilter()); - this.go(); - await this.vaultItemsComponent.refresh(); - } - - async deletedCipher(cipher: CipherView) { - this.cipherId = null; - this.action = null; - this.go(); - await this.vaultItemsComponent.refresh(); - } - - async restoredCipher(cipher: CipherView) { - this.cipherId = null; - this.action = null; - this.go(); - await this.vaultItemsComponent.refresh(); - } - - async editCipherAttachments(cipher: CipherView) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - AttachmentsComponent, - this.attachmentsModalRef, - (comp) => (comp.cipherId = cipher.id), - ); - this.modal = modal; - - let madeAttachmentChanges = false; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onUploadedAttachment.subscribe((cipher) => { - madeAttachmentChanges = true; - // Update the edit component cipher with the updated cipher, - // which is needed because the revision date is updated when an attachment is altered - this.addEditComponent.patchCipherAttachments(cipher); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onDeletedAttachment.subscribe((cipher) => { - madeAttachmentChanges = true; - // Update the edit component cipher with the updated cipher, - // which is needed because the revision date is updated when an attachment is altered - this.addEditComponent.patchCipherAttachments(cipher); - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.modal.onClosed.subscribe(async () => { - this.modal = null; - if (madeAttachmentChanges) { - await this.vaultItemsComponent.refresh(); - } - madeAttachmentChanges = false; - }); - } - - async shareCipher(cipher: CipherView) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - ShareComponent, - this.shareModalRef, - (comp) => (comp.cipherId = cipher.id), - ); - this.modal = modal; - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSharedCipher.subscribe(async () => { - this.modal.close(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.viewCipher(cipher); - await this.vaultItemsComponent.refresh(); - await this.cipherService.clearCache(this.activeUserId); - await this.vaultItemsComponent.load(this.activeFilter.buildFilter()); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.modal.onClosed.subscribe(async () => { - this.modal = null; - }); - } - - async cipherCollections(cipher: CipherView) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - CollectionsComponent, - this.collectionsModalRef, - (comp) => (comp.cipherId = cipher.id), - ); - this.modal = modal; - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onSavedCollections.subscribe(() => { - this.modal.close(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.viewCipher(cipher); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.modal.onClosed.subscribe(async () => { - this.modal = null; - }); - } - - async viewCipherPasswordHistory(cipher: CipherView) { - if (this.modal != null) { - this.modal.close(); - } - - [this.modal] = await this.modalService.openViewRef( - PasswordHistoryComponent, - this.passwordHistoryModalRef, - (comp) => (comp.cipherId = cipher.id), - ); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.modal.onClosed.subscribe(async () => { - this.modal = null; - }); - } - - cancelledAddEdit(cipher: CipherView) { - this.cipherId = cipher.id; - this.action = this.cipherId != null ? "view" : null; - this.go(); - } - - async applyVaultFilter(vaultFilter: VaultFilter) { - this.searchBarService.setPlaceholderText( - this.i18nService.t(this.calculateSearchBarLocalizationString(vaultFilter)), - ); - this.activeFilter = vaultFilter; - await this.vaultItemsComponent.reload( - this.activeFilter.buildFilter(), - vaultFilter.status === "trash", - ); - this.go(); - } - - private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string { - if (vaultFilter.status === "favorites") { - return "searchFavorites"; - } - if (vaultFilter.status === "trash") { - return "searchTrash"; - } - if (vaultFilter.cipherType != null) { - return "searchType"; - } - if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId != "none") { - return "searchFolder"; - } - if (vaultFilter.selectedCollectionId != null) { - return "searchCollection"; - } - if (vaultFilter.selectedOrganizationId != null) { - return "searchOrganization"; - } - if (vaultFilter.myVaultOnly) { - return "searchMyVault"; - } - - return "searchVault"; - } - - async openGenerator(passwordType = true) { - CredentialGeneratorDialogComponent.open(this.dialogService, { - onCredentialGenerated: (value?: string) => { - if (this.addEditComponent != null) { - this.addEditComponent.markPasswordAsDirty(); - if (passwordType) { - this.addEditComponent.cipher.login.password = value ?? ""; - } else { - this.addEditComponent.cipher.login.username = value ?? ""; - } - } - }, - type: passwordType ? "password" : "username", - }); - return; - } - - async addFolder() { - this.messagingService.send("newFolder"); - } - - async editFolder(folderId: string) { - const folderView = await firstValueFrom( - this.folderService.getDecrypted$(folderId, this.activeUserId), - ); - - const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { - editFolderConfig: { - folder: { - ...folderView, - }, - }, - }); - - const result = await lastValueFrom(dialogRef.closed); - - if ( - result === AddEditFolderDialogResult.Deleted || - result === AddEditFolderDialogResult.Created - ) { - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); - } - } - - private dirtyInput(): boolean { - return ( - (this.action === "add" || this.action === "edit" || this.action === "clone") && - document.querySelectorAll("app-vault-add-edit .ng-dirty").length > 0 - ); - } - - private async wantsToSaveChanges(): Promise { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "unsavedChangesTitle" }, - content: { key: "unsavedChangesConfirmation" }, - type: "warning", - }); - return !confirmed; - } - - private go(queryParams: any = null) { - if (queryParams == null) { - queryParams = { - action: this.action, - cipherId: this.cipherId, - favorites: this.favorites ? true : null, - type: this.type, - folderId: this.folderId, - collectionId: this.collectionId, - deleted: this.deleted ? true : null, - organizationId: this.organizationId, - myVaultOnly: this.myVaultOnly, - }; - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([], { - relativeTo: this.route, - queryParams: queryParams, - replaceUrl: true, - }); - } - - private addCipherWithChangeDetection(type: CipherType = null) { - this.functionWithChangeDetection(() => this.addCipher(type)); - } - - private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) { - this.functionWithChangeDetection(async () => { - if ( - this.passwordRepromptService.protectedFields().includes(aType) && - !(await this.passwordReprompt(cipher)) - ) { - return; - } - - this.platformUtilsService.copyToClipboard(value); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)), - }); - if (this.action === "view") { - this.messagingService.send("minimizeOnCopy"); - } - }); - } - - private functionWithChangeDetection(func: () => void) { - this.ngZone.run(() => { - func(); - this.changeDetectorRef.detectChanges(); - }); - } - - private prefillNewCipherFromFilter() { - if (this.activeFilter.selectedCollectionId != null) { - const collection = this.vaultFilterComponent.collections.fullList.filter( - (c) => c.id === this.activeFilter.selectedCollectionId, - ); - if (collection.length > 0) { - this.addOrganizationId = collection[0].organizationId; - this.addCollectionIds = [this.activeFilter.selectedCollectionId]; - } - } else if (this.activeFilter.selectedOrganizationId) { - this.addOrganizationId = this.activeFilter.selectedOrganizationId; - } - if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) { - this.folderId = this.activeFilter.selectedFolderId; - } - } - - private async canNavigateAway(action: string, cipher?: CipherView) { - // Don't navigate to same route - if (this.action === action && (cipher == null || this.cipherId === cipher.id)) { - return false; - } else if (this.dirtyInput() && (await this.wantsToSaveChanges())) { - return false; - } - - return true; - } - - private async passwordReprompt(cipher: CipherView) { - if (cipher.reprompt === CipherRepromptType.None) { - this.cipherRepromptId = null; - return true; - } - if (this.cipherRepromptId === cipher.id) { - return true; - } - const repromptResult = await this.passwordRepromptService.showPasswordPrompt(); - if (repromptResult) { - this.cipherRepromptId = cipher.id; - } - return repromptResult; - } -} diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.html b/apps/desktop/src/vault/app/vault/view-custom-fields.component.html deleted file mode 100644 index 5e0389af25a..00000000000 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
-

- {{ "customFields" | i18n }} -

-
-
-
- - {{ field.name }} - - - {{ "cfTypeLinked" | i18n }}: {{ field.name }} - -
- {{ field.value || " " }} -
-
- {{ field.maskedValue }} - - -
-
- -
-
- {{ cipher.linkedFieldI18nKey(field.linkedId) | i18n }} -
-
-
- - - -
-
-
-
diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts deleted file mode 100644 index efe61ad1fa7..00000000000 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from "@angular/core"; - -import { ViewCustomFieldsComponent as BaseViewCustomFieldsComponent } from "@bitwarden/angular/vault/components/view-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; - -@Component({ - selector: "app-vault-view-custom-fields", - templateUrl: "view-custom-fields.component.html", - standalone: false, -}) -export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { - constructor(eventCollectionService: EventCollectionService) { - super(eventCollectionService); - } -} diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html deleted file mode 100644 index d3e3a751d9d..00000000000 --- a/apps/desktop/src/vault/app/vault/view.component.html +++ /dev/null @@ -1,683 +0,0 @@ -
-
-
-

- {{ "itemInformation" | i18n }} -

-
-
- {{ "name" | i18n }} - {{ cipher.name }} -
- -
-
-
- {{ "username" | i18n }} - {{ cipher.login.username }} -
-
- -
-
-
-
- {{ "password" | i18n }} -
- {{ cipher.login.maskedPassword }} -
-
-
-
-
- - - - -
-
- -
- {{ "typePasskey" | i18n }} - {{ fido2CredentialCreationDateValue }} -
- - -
-
- {{ "verificationCodeTotp" | i18n }} - {{ totpInfo.totpCodeFormatted }} -
- -
- -
-
-
- -
-
- {{ "verificationCodeTotp" | i18n }} - - {{ "premiumSubcriptionRequired" | i18n }} - - -
-
-
- -
-
- {{ "cardholderName" | i18n }} - {{ cipher.card.cardholderName }} -
-
-
- {{ "number" | i18n }} - {{ - cipher.card.maskedNumber | creditCardNumber: cipher.card.brand - }} - {{ - cipher.card.number | creditCardNumber: cipher.card.brand - }} -
-
- - -
-
-
- {{ "brand" | i18n }} - {{ cipher.card.brand }} -
-
- {{ "expiration" | i18n }} - {{ cipher.card.expiration }} -
-
-
- {{ "securityCode" | i18n }} - {{ cipher.card.maskedCode }} - {{ cipher.card.code }} -
-
- - -
-
-
- -
-
- {{ "identityName" | i18n }} - {{ cipher.identity.fullName }} -
-
- {{ "username" | i18n }} - {{ cipher.identity.username }} -
-
- {{ "company" | i18n }} - {{ cipher.identity.company }} -
-
- {{ "ssn" | i18n }} - {{ cipher.identity.ssn }} -
-
- {{ "passportNumber" | i18n }} - {{ cipher.identity.passportNumber }} -
-
- {{ "licenseNumber" | i18n }} - {{ cipher.identity.licenseNumber }} -
-
- {{ "email" | i18n }} - {{ cipher.identity.email }} -
-
- {{ "phone" | i18n }} - {{ cipher.identity.phone }} -
-
- {{ "address" | i18n }} -
{{ cipher.identity.address1 }}
-
{{ cipher.identity.address2 }}
-
{{ cipher.identity.address3 }}
-
- {{ cipher.identity.fullAddressPart2 }} -
-
{{ cipher.identity.country }}
-
-
- -
-
-
- {{ "sshPrivateKey" | i18n }} -
-
-
-
- - -
-
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
-
- {{ "uri" | i18n }} - {{ "website" | i18n }} - {{ u.hostOrUri }} -
-
- - -
-
-
-
-
-
-
- - -
-
-
-
-

- {{ "notes" | i18n }} -

-
-
{{ cipher.notes }}
-
-
- - -
-

- {{ "attachments" | i18n }} -

-
- -
-
-
- -
-
-
- diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts deleted file mode 100644 index 7e7f7b57fc8..00000000000 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { DatePipe } from "@angular/common"; -import { - ChangeDetectorRef, - Component, - EventEmitter, - Input, - NgZone, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges, -} from "@angular/core"; - -import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; - -const BroadcasterSubscriptionId = "ViewComponent"; - -@Component({ - selector: "app-vault-view", - templateUrl: "view.component.html", - standalone: false, -}) -export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { - @Output() onViewCipherPasswordHistory = new EventEmitter(); - @Input() masterPasswordAlreadyPrompted: boolean = false; - - constructor( - cipherService: CipherService, - folderService: FolderService, - totpService: TotpService, - tokenService: TokenService, - i18nService: I18nService, - keyService: KeyService, - encryptService: EncryptService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - broadcasterService: BroadcasterService, - ngZone: NgZone, - changeDetectorRef: ChangeDetectorRef, - eventCollectionService: EventCollectionService, - apiService: ApiService, - private messagingService: MessagingService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - stateService: StateService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - datePipe: DatePipe, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - cipherAuthorizationService: CipherAuthorizationService, - configService: ConfigService, - ) { - super( - cipherService, - folderService, - totpService, - tokenService, - i18nService, - keyService, - encryptService, - platformUtilsService, - auditService, - window, - broadcasterService, - ngZone, - changeDetectorRef, - eventCollectionService, - apiService, - passwordRepromptService, - logService, - stateService, - fileDownloadService, - dialogService, - datePipe, - accountService, - billingAccountProfileStateService, - toastService, - cipherAuthorizationService, - configService, - ); - } - - ngOnInit() { - super.ngOnInit(); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - default: - } - }); - }); - this.passwordReprompted = this.masterPasswordAlreadyPrompted; - } - - ngOnDestroy() { - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - async ngOnChanges(changes: SimpleChanges) { - if (this.cipher?.decryptionFailure) { - DecryptionFailureDialogComponent.open(this.dialogService, { - cipherIds: [this.cipherId as CipherId], - }); - return; - } - this.passwordReprompted = this.masterPasswordAlreadyPrompted; - - if (changes["cipherId"]) { - if (changes["cipherId"].currentValue !== changes["cipherId"].previousValue) { - this.showPrivateKey = false; - } - } - } - - viewHistory() { - this.onViewCipherPasswordHistory.emit(this.cipher); - } - - async copy(value: string, typeI18nKey: string, aType: string): Promise { - const hasCopied = await super.copy(value, typeI18nKey, aType); - if (hasCopied) { - this.messagingService.send("minimizeOnCopy"); - } - - return hasCopied; - } - - onWindowHidden() { - this.showPassword = false; - this.showCardNumber = false; - this.showCardCode = false; - if (this.cipher !== null && this.cipher.hasFields) { - this.cipher.fields.forEach((field) => { - field.showValue = false; - }); - } - } - - showGetPremium() { - if (!this.canAccessPremium) { - this.messagingService.send("premiumRequired"); - } - } -} diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts deleted file mode 100644 index 9d36ab4619f..00000000000 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ /dev/null @@ -1,136 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { ToastService } from "@bitwarden/components"; - -@Directive() -export class CollectionsComponent implements OnInit { - @Input() cipherId: string; - @Input() allowSelectNone = false; - @Output() onSavedCollections = new EventEmitter(); - - formPromise: Promise; - cipher: CipherView; - collectionIds: string[]; - collections: CollectionView[] = []; - organization: Organization; - - protected cipherDomain: Cipher; - - constructor( - protected collectionService: CollectionService, - protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, - protected cipherService: CipherService, - protected organizationService: OrganizationService, - private logService: LogService, - private accountService: AccountService, - private toastService: ToastService, - ) {} - - async ngOnInit() { - await this.load(); - } - - async load() { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.cipherDomain = await this.loadCipher(activeUserId); - this.collectionIds = this.loadCipherCollections(); - this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); - this.collections = await this.loadCollections(); - - this.collections.forEach((c) => ((c as any).checked = false)); - if (this.collectionIds != null) { - this.collections.forEach((c) => { - (c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1; - }); - } - - if (this.organization == null) { - this.organization = await firstValueFrom( - this.organizationService - .organizations$(activeUserId) - .pipe( - map((organizations) => - organizations.find((org) => org.id === this.cipher.organizationId), - ), - ), - ); - } - } - - async submit(): Promise { - const selectedCollectionIds = this.collections - .filter((c) => { - if (this.organization.canEditAllCiphers) { - return !!(c as any).checked; - } else { - return !!(c as any).checked && !c.readOnly; - } - }) - .map((c) => c.id); - if (!this.allowSelectNone && selectedCollectionIds.length === 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("selectOneCollection"), - }); - return false; - } - this.cipherDomain.collectionIds = selectedCollectionIds; - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.formPromise = this.saveCollections(activeUserId); - await this.formPromise; - this.onSavedCollections.emit(); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("editedItem"), - }); - return true; - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e.message, - }); - return false; - } - } - - protected loadCipher(userId: UserId) { - return this.cipherService.get(this.cipherId, userId); - } - - protected loadCipherCollections() { - return this.cipherDomain.collectionIds; - } - - protected async loadCollections() { - const allCollections = await this.collectionService.getAllDecrypted(); - return allCollections.filter( - (c) => !c.readOnly && c.organizationId === this.cipher.organizationId, - ); - } - - protected saveCollections(userId: UserId) { - return this.cipherService.saveCollectionsWithServer(this.cipherDomain, userId); - } -} diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts deleted file mode 100644 index 51827bfb9f2..00000000000 --- a/libs/angular/src/components/share.component.ts +++ /dev/null @@ -1,142 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Checkable, isChecked } from "@bitwarden/common/types/checkable"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; - -@Directive() -export class ShareComponent implements OnInit, OnDestroy { - @Input() cipherId: string; - @Input() organizationId: string; - @Output() onSharedCipher = new EventEmitter(); - - formPromise: Promise; - cipher: CipherView; - collections: Checkable[] = []; - organizations$: Observable; - - protected writeableCollections: Checkable[] = []; - - private _destroy = new Subject(); - - constructor( - protected collectionService: CollectionService, - protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, - protected cipherService: CipherService, - private logService: LogService, - protected organizationService: OrganizationService, - protected accountService: AccountService, - ) {} - - async ngOnInit() { - await this.load(); - } - - ngOnDestroy(): void { - this._destroy.next(); - this._destroy.complete(); - } - - async load() { - const allCollections = await this.collectionService.getAllDecrypted(); - this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly); - - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((account) => account?.id)), - ); - - this.organizations$ = this.organizationService.memberOrganizations$(userId).pipe( - map((orgs) => { - return orgs - .filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed) - .sort(Utils.getSortFunction(this.i18nService, "name")); - }), - ); - - this.organizations$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { - if (this.organizationId == null && orgs.length > 0) { - this.organizationId = orgs[0].id; - this.filterCollections(); - } - }); - - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - this.cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); - } - - filterCollections() { - this.writeableCollections.forEach((c) => (c.checked = false)); - if (this.organizationId == null || this.writeableCollections.length === 0) { - this.collections = []; - } else { - this.collections = this.writeableCollections.filter( - (c) => c.organizationId === this.organizationId, - ); - } - } - - async submit(): Promise { - const selectedCollectionIds = this.collections.filter(isChecked).map((c) => c.id); - if (selectedCollectionIds.length === 0) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("selectOneCollection"), - ); - return; - } - - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipherView = await this.cipherService.decrypt(cipherDomain, activeUserId); - const orgs = await firstValueFrom(this.organizations$); - const orgName = - orgs.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization"); - - try { - this.formPromise = this.cipherService - .shareWithServer(cipherView, this.organizationId, selectedCollectionIds, activeUserId) - .then(async () => { - this.onSharedCipher.emit(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("movedItemToOrg", cipherView.name, orgName), - ); - }); - await this.formPromise; - return true; - } catch (e) { - this.logService.error(e); - } - return false; - } - - get canSave() { - if (this.collections != null) { - for (let i = 0; i < this.collections.length; i++) { - if (this.collections[i].checked) { - return true; - } - } - } - return false; - } -} diff --git a/libs/angular/src/vault/components/add-edit-custom-fields.component.ts b/libs/angular/src/vault/components/add-edit-custom-fields.component.ts deleted file mode 100644 index 9f774ca3c24..00000000000 --- a/libs/angular/src/vault/components/add-edit-custom-fields.component.ts +++ /dev/null @@ -1,125 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop"; -import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core"; - -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { EventType } from "@bitwarden/common/enums"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FieldType, CipherType } from "@bitwarden/common/vault/enums"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; - -@Directive() -export class AddEditCustomFieldsComponent implements OnChanges { - @Input() cipher: CipherView; - @Input() thisCipherType: CipherType; - @Input() editMode: boolean; - - addFieldType: FieldType = FieldType.Text; - addFieldTypeOptions: any[]; - addFieldLinkedTypeOption: any; - linkedFieldOptions: any[] = []; - - cipherType = CipherType; - fieldType = FieldType; - eventType = EventType; - - constructor( - private i18nService: I18nService, - private eventCollectionService: EventCollectionService, - ) { - this.addFieldTypeOptions = [ - { name: i18nService.t("cfTypeText"), value: FieldType.Text }, - { name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden }, - { name: i18nService.t("cfTypeBoolean"), value: FieldType.Boolean }, - ]; - this.addFieldLinkedTypeOption = { - name: this.i18nService.t("cfTypeLinked"), - value: FieldType.Linked, - }; - } - - ngOnChanges(changes: SimpleChanges) { - if (changes.thisCipherType != null) { - this.setLinkedFieldOptions(); - - if (!changes.thisCipherType.firstChange) { - this.resetCipherLinkedFields(); - } - } - } - - addField() { - if (this.cipher.fields == null) { - this.cipher.fields = []; - } - - const f = new FieldView(); - f.type = this.addFieldType; - f.newField = true; - - if (f.type === FieldType.Linked) { - f.linkedId = this.linkedFieldOptions[0].value; - } - - this.cipher.fields.push(f); - } - - removeField(field: FieldView) { - const i = this.cipher.fields.indexOf(field); - if (i > -1) { - this.cipher.fields.splice(i, 1); - } - } - - toggleFieldValue(field: FieldView) { - const f = field as any; - f.showValue = !f.showValue; - if (this.editMode && f.showValue) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( - EventType.Cipher_ClientToggledHiddenFieldVisible, - this.cipher.id, - ); - } - } - - trackByFunction(index: number, item: any) { - return index; - } - - drop(event: CdkDragDrop) { - moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex); - } - - private setLinkedFieldOptions() { - if (this.cipher.linkedFieldOptions == null) { - return; - } - - const options: any = []; - this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) => - options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id }), - ); - this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, "name")); - } - - private resetCipherLinkedFields() { - if (this.cipher.fields == null || this.cipher.fields.length === 0) { - return; - } - - // Delete any Linked custom fields if the item type does not support them - if (this.cipher.linkedFieldOptions == null) { - this.cipher.fields = this.cipher.fields.filter((f) => f.type !== FieldType.Linked); - return; - } - - this.cipher.fields - .filter((f) => f.type === FieldType.Linked) - .forEach((f) => (f.linkedId = this.linkedFieldOptions[0].value)); - } -} diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts deleted file mode 100644 index 3541fa0c8e8..00000000000 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ /dev/null @@ -1,855 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe } from "@angular/common"; -import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { concatMap, firstValueFrom, map, Observable, Subject, switchMap, takeUntil } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; -import { EventType } from "@bitwarden/common/enums"; -import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -import { - CipherService, - EncryptionContext, -} from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; -import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { generate_ssh_key } from "@bitwarden/sdk-internal"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { PasswordRepromptService, SshImportPromptService } from "@bitwarden/vault"; - -@Directive() -export class AddEditComponent implements OnInit, OnDestroy { - @Input() cloneMode = false; - @Input() folderId: string = null; - @Input() cipherId: string; - @Input() type: CipherType; - @Input() collectionIds: string[]; - @Input() organizationId: string = null; - @Input() collectionId: string = null; - @Output() onSavedCipher = new EventEmitter(); - @Output() onDeletedCipher = new EventEmitter(); - @Output() onRestoredCipher = new EventEmitter(); - @Output() onCancelled = new EventEmitter(); - @Output() onEditAttachments = new EventEmitter(); - @Output() onShareCipher = new EventEmitter(); - @Output() onEditCollections = new EventEmitter(); - @Output() onGeneratePassword = new EventEmitter(); - @Output() onGenerateUsername = new EventEmitter(); - - canDeleteCipher$: Observable; - - editMode = false; - cipher: CipherView; - folders$: Observable; - collections: CollectionView[] = []; - title: string; - formPromise: Promise; - deletePromise: Promise; - restorePromise: Promise; - checkPasswordPromise: Promise; - showPassword = false; - showPrivateKey = false; - showTotpSeed = false; - showCardNumber = false; - showCardCode = false; - cipherType = CipherType; - cardBrandOptions: any[]; - cardExpMonthOptions: any[]; - identityTitleOptions: any[]; - uriMatchOptions: any[]; - ownershipOptions: any[] = []; - autofillOnPageLoadOptions: any[]; - currentDate = new Date(); - allowPersonal = true; - reprompt = false; - canUseReprompt = true; - organization: Organization; - /** - * Flag to determine if the action is being performed from the admin console. - */ - isAdminConsoleAction: boolean = false; - - protected componentName = ""; - protected destroy$ = new Subject(); - protected writeableCollections: CollectionView[]; - private organizationDataOwnershipAppliesToUser: boolean; - private previousCipherId: string; - - get fido2CredentialCreationDateValue(): string { - const dateCreated = this.i18nService.t("dateCreated"); - const creationDate = this.datePipe.transform( - this.cipher?.login?.fido2Credentials?.[0]?.creationDate, - "short", - ); - return `${dateCreated} ${creationDate}`; - } - - constructor( - protected cipherService: CipherService, - protected folderService: FolderService, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - protected auditService: AuditService, - protected accountService: AccountService, - protected collectionService: CollectionService, - protected messagingService: MessagingService, - protected eventCollectionService: EventCollectionService, - protected policyService: PolicyService, - protected logService: LogService, - protected passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, - protected dialogService: DialogService, - protected win: Window, - protected datePipe: DatePipe, - protected configService: ConfigService, - protected cipherAuthorizationService: CipherAuthorizationService, - protected toastService: ToastService, - protected sdkService: SdkService, - private sshImportPromptService: SshImportPromptService, - ) { - this.cardBrandOptions = [ - { name: "-- " + i18nService.t("select") + " --", value: null }, - { name: "Visa", value: "Visa" }, - { name: "Mastercard", value: "Mastercard" }, - { name: "American Express", value: "Amex" }, - { name: "Discover", value: "Discover" }, - { name: "Diners Club", value: "Diners Club" }, - { name: "JCB", value: "JCB" }, - { name: "Maestro", value: "Maestro" }, - { name: "UnionPay", value: "UnionPay" }, - { name: "RuPay", value: "RuPay" }, - { name: i18nService.t("other"), value: "Other" }, - ]; - this.cardExpMonthOptions = [ - { name: "-- " + i18nService.t("select") + " --", value: null }, - { name: "01 - " + i18nService.t("january"), value: "1" }, - { name: "02 - " + i18nService.t("february"), value: "2" }, - { name: "03 - " + i18nService.t("march"), value: "3" }, - { name: "04 - " + i18nService.t("april"), value: "4" }, - { name: "05 - " + i18nService.t("may"), value: "5" }, - { name: "06 - " + i18nService.t("june"), value: "6" }, - { name: "07 - " + i18nService.t("july"), value: "7" }, - { name: "08 - " + i18nService.t("august"), value: "8" }, - { name: "09 - " + i18nService.t("september"), value: "9" }, - { name: "10 - " + i18nService.t("october"), value: "10" }, - { name: "11 - " + i18nService.t("november"), value: "11" }, - { name: "12 - " + i18nService.t("december"), value: "12" }, - ]; - this.identityTitleOptions = [ - { name: "-- " + i18nService.t("select") + " --", value: null }, - { name: i18nService.t("mr"), value: i18nService.t("mr") }, - { name: i18nService.t("mrs"), value: i18nService.t("mrs") }, - { name: i18nService.t("ms"), value: i18nService.t("ms") }, - { name: i18nService.t("mx"), value: i18nService.t("mx") }, - { name: i18nService.t("dr"), value: i18nService.t("dr") }, - ]; - this.uriMatchOptions = [ - { name: i18nService.t("defaultMatchDetection"), value: null }, - { name: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain }, - { name: i18nService.t("host"), value: UriMatchStrategy.Host }, - { name: i18nService.t("startsWith"), value: UriMatchStrategy.StartsWith }, - { name: i18nService.t("regEx"), value: UriMatchStrategy.RegularExpression }, - { name: i18nService.t("exact"), value: UriMatchStrategy.Exact }, - { name: i18nService.t("never"), value: UriMatchStrategy.Never }, - ]; - this.autofillOnPageLoadOptions = [ - { name: i18nService.t("autoFillOnPageLoadUseDefault"), value: null }, - { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, - { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, - ]; - } - - async ngOnInit() { - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => - this.policyService.policyAppliesToUser$(PolicyType.OrganizationDataOwnership, userId), - ), - concatMap(async (policyAppliesToActiveUser) => { - this.organizationDataOwnershipAppliesToUser = policyAppliesToActiveUser; - await this.init(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - this.writeableCollections = await this.loadCollections(); - this.canUseReprompt = await this.passwordRepromptService.enabled(); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async init() { - if (this.ownershipOptions.length) { - this.ownershipOptions = []; - } - if (this.organizationDataOwnershipAppliesToUser) { - this.allowPersonal = false; - } else { - const myEmail = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - this.ownershipOptions.push({ name: myEmail, value: null }); - } - - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((account) => account?.id)), - ); - const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); - orgs - .filter((org) => org.isMember) - .sort(Utils.getSortFunction(this.i18nService, "name")) - .forEach((o) => { - if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { - this.ownershipOptions.push({ name: o.name, value: o.id }); - } - }); - if (!this.allowPersonal && this.organizationId == undefined) { - this.organizationId = this.defaultOwnerId; - } - } - - async load() { - this.editMode = this.cipherId != null; - if (this.editMode) { - this.editMode = true; - if (this.cloneMode) { - this.cloneMode = true; - this.title = this.i18nService.t("addItem"); - } else { - this.title = this.i18nService.t("editItem"); - } - } else { - this.title = this.i18nService.t("addItem"); - } - - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(activeUserId); - - if (this.cipher == null) { - if (this.editMode) { - const cipher = await this.loadCipher(activeUserId); - this.cipher = await this.cipherService.decrypt(cipher, activeUserId); - - // Adjust Cipher Name if Cloning - if (this.cloneMode) { - this.cipher.name += " - " + this.i18nService.t("clone"); - // If not allowing personal ownership, update cipher's org Id to prompt downstream changes - if (this.cipher.organizationId == null && !this.allowPersonal) { - this.cipher.organizationId = this.organizationId; - } - } - } else { - this.cipher = new CipherView(); - this.cipher.organizationId = this.organizationId == null ? null : this.organizationId; - this.cipher.folderId = this.folderId; - this.cipher.type = this.type == null ? CipherType.Login : this.type; - this.cipher.login = new LoginView(); - this.cipher.login.uris = [new LoginUriView()]; - this.cipher.card = new CardView(); - this.cipher.identity = new IdentityView(); - this.cipher.secureNote = new SecureNoteView(); - this.cipher.secureNote.type = SecureNoteType.Generic; - this.cipher.sshKey = new SshKeyView(); - this.cipher.reprompt = CipherRepromptType.None; - } - } - - if (this.cipher != null && (!this.editMode || loadedAddEditCipherInfo || this.cloneMode)) { - await this.organizationChanged(); - if ( - this.collectionIds != null && - this.collectionIds.length > 0 && - this.collections.length > 0 - ) { - this.collections.forEach((c) => { - if (this.collectionIds.indexOf(c.id) > -1) { - (c as any).checked = true; - } - }); - } - } - // Only Admins can clone a cipher to different owner - if (this.cloneMode && this.cipher.organizationId != null) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - const cipherOrg = ( - await firstValueFrom(this.organizationService.memberOrganizations$(activeUserId)) - ).find((o) => o.id === this.cipher.organizationId); - - if (cipherOrg != null && !cipherOrg.isAdmin && !cipherOrg.permissions.editAnyCollection) { - this.ownershipOptions = [{ name: cipherOrg.name, value: cipherOrg.id }]; - } - } - - // We don't want to copy passkeys when we clone a cipher - if (this.cloneMode && this.cipher?.login?.hasFido2Credentials) { - this.cipher.login.fido2Credentials = null; - } - - this.folders$ = this.folderService.folderViews$(activeUserId); - - if (this.editMode && this.previousCipherId !== this.cipherId) { - void this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, [this.cipher]); - } - this.previousCipherId = this.cipherId; - this.reprompt = this.cipher.reprompt !== CipherRepromptType.None; - if (this.reprompt) { - this.cipher.login.autofillOnPageLoad = this.autofillOnPageLoadOptions[2].value; - } - - this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$( - this.cipher, - this.isAdminConsoleAction, - ); - - if (!this.editMode || this.cloneMode) { - // Creating an ssh key directly while filtering to the ssh key category - // must force a key to be set. SSH keys must never be created with an empty private key field - if ( - this.cipher.type === CipherType.SshKey && - (this.cipher.sshKey.privateKey == null || this.cipher.sshKey.privateKey === "") - ) { - await this.generateSshKey(false); - } - } - } - - async submit(): Promise { - if (this.cipher.isDeleted) { - return this.restore(); - } - - // normalize card expiry year on save - if (this.cipher.type === this.cipherType.Card) { - this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); - } - - // trim whitespace from the TOTP field - if (this.cipher.type === this.cipherType.Login && this.cipher.login.totp) { - this.cipher.login.totp = this.cipher.login.totp.trim(); - } - - if (this.cipher.name == null || this.cipher.name === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("nameRequired"), - }); - return false; - } - - if ( - (!this.editMode || this.cloneMode) && - !this.allowPersonal && - this.cipher.organizationId == null - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("personalOwnershipSubmitError"), - }); - return false; - } - - if ( - (!this.editMode || this.cloneMode) && - this.cipher.type === CipherType.Login && - this.cipher.login.uris != null && - this.cipher.login.uris.length === 1 && - (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") - ) { - this.cipher.login.uris = []; - } - - // Allows saving of selected collections during "Add" and "Clone" flows - if ((!this.editMode || this.cloneMode) && this.cipher.organizationId != null) { - this.cipher.collectionIds = - this.collections == null - ? [] - : this.collections.filter((c) => (c as any).checked).map((c) => c.id); - } - - // Clear current Cipher Id if exists to trigger "Add" cipher flow - if (this.cloneMode) { - this.cipher.id = null; - } - - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const cipher = await this.encryptCipher(activeUserId); - - try { - this.formPromise = this.saveCipher(cipher); - const savedCipher = await this.formPromise; - - // Reset local cipher from the saved cipher returned from the server - this.cipher = await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem"), - }); - this.onSavedCipher.emit(this.cipher); - this.messagingService.send(this.editMode && !this.cloneMode ? "editedCipher" : "addedCipher"); - return true; - } catch (e) { - this.logService.error(e); - } - - return false; - } - - addUri() { - if (this.cipher.type !== CipherType.Login) { - return; - } - - if (this.cipher.login.uris == null) { - this.cipher.login.uris = []; - } - - this.cipher.login.uris.push(new LoginUriView()); - } - - removeUri(uri: LoginUriView) { - if (this.cipher.type !== CipherType.Login || this.cipher.login.uris == null) { - return; - } - - const i = this.cipher.login.uris.indexOf(uri); - if (i > -1) { - this.cipher.login.uris.splice(i, 1); - } - } - - removePasskey() { - if (this.cipher.type !== CipherType.Login || this.cipher.login.fido2Credentials == null) { - return; - } - - this.cipher.login.fido2Credentials = null; - } - - onCardNumberChange(): void { - this.cipher.card.brand = CardView.getCardBrandByPatterns(this.cipher.card.number); - } - - getCardExpMonthDisplay() { - return this.cardExpMonthOptions.find((x) => x.value == this.cipher.card.expMonth)?.name; - } - - trackByFunction(index: number, item: any) { - return index; - } - - cancel() { - this.onCancelled.emit(this.cipher); - } - - attachments() { - this.onEditAttachments.emit(this.cipher); - } - - share() { - this.onShareCipher.emit(this.cipher); - } - - editCollections() { - this.onEditCollections.emit(this.cipher); - } - - async delete(): Promise { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "deleteItem" }, - content: { - key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation", - }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.deletePromise = this.deleteCipher(activeUserId); - await this.deletePromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t( - this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", - ), - }); - this.onDeletedCipher.emit(this.cipher); - this.messagingService.send( - this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher", - ); - } catch (e) { - this.logService.error(e); - } - - return true; - } - - async restore(): Promise { - if (!this.cipher.isDeleted) { - return false; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.restorePromise = this.restoreCipher(activeUserId); - await this.restorePromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("restoredItem"), - }); - this.onRestoredCipher.emit(this.cipher); - this.messagingService.send("restoredCipher"); - } catch (e) { - this.logService.error(e); - } - - return true; - } - - async generateUsername(): Promise { - if (this.cipher.login?.username?.length) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "overwriteUsername" }, - content: { key: "overwriteUsernameConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - } - - this.onGenerateUsername.emit(); - return true; - } - - async generatePassword(): Promise { - if (this.cipher.login?.password?.length) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "overwritePassword" }, - content: { key: "overwritePasswordConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - } - - this.onGeneratePassword.emit(); - return true; - } - - togglePassword() { - this.showPassword = !this.showPassword; - - if (this.editMode && this.showPassword) { - document.getElementById("loginPassword")?.focus(); - - void this.eventCollectionService.collectMany(EventType.Cipher_ClientToggledPasswordVisible, [ - this.cipher, - ]); - } - } - - toggleTotpSeed() { - this.showTotpSeed = !this.showTotpSeed; - - if (this.editMode && this.showTotpSeed) { - document.getElementById("loginTotp")?.focus(); - - void this.eventCollectionService.collectMany(EventType.Cipher_ClientToggledTOTPSeedVisible, [ - this.cipher, - ]); - } - } - - async toggleCardNumber() { - this.showCardNumber = !this.showCardNumber; - if (this.showCardNumber) { - void this.eventCollectionService.collectMany( - EventType.Cipher_ClientToggledCardNumberVisible, - [this.cipher], - ); - } - } - - toggleCardCode() { - this.showCardCode = !this.showCardCode; - document.getElementById("cardCode").focus(); - if (this.editMode && this.showCardCode) { - void this.eventCollectionService.collectMany(EventType.Cipher_ClientToggledCardCodeVisible, [ - this.cipher, - ]); - } - } - - togglePrivateKey() { - this.showPrivateKey = !this.showPrivateKey; - } - - toggleUriOptions(uri: LoginUriView) { - const u = uri as any; - u.showOptions = u.showOptions == null && uri.match != null ? false : !u.showOptions; - } - - loginUriMatchChanged(uri: LoginUriView) { - const u = uri as any; - u.showOptions = u.showOptions == null ? true : u.showOptions; - } - - async organizationChanged() { - if (this.writeableCollections != null) { - this.writeableCollections.forEach((c) => ((c as any).checked = false)); - } - if (this.cipher.organizationId != null) { - this.collections = this.writeableCollections?.filter( - (c) => c.organizationId === this.cipher.organizationId, - ); - // If there's only one collection, check it by default - if (this.collections.length === 1) { - (this.collections[0] as any).checked = true; - } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - const org = ( - await firstValueFrom(this.organizationService.organizations$(activeUserId)) - ).find((org) => org.id === this.cipher.organizationId); - if (org != null) { - this.cipher.organizationUseTotp = org.useTotp; - } - } else { - this.collections = []; - } - } - - async checkPassword() { - if (this.checkPasswordPromise != null) { - return; - } - - if ( - this.cipher.login == null || - this.cipher.login.password == null || - this.cipher.login.password === "" - ) { - return; - } - - this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); - const matches = await this.checkPasswordPromise; - this.checkPasswordPromise = null; - - if (matches > 0) { - this.toastService.showToast({ - variant: "warning", - title: null, - message: this.i18nService.t("passwordExposed", matches.toString()), - }); - } else { - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("passwordSafe"), - }); - } - } - - repromptChanged() { - this.reprompt = !this.reprompt; - if (this.reprompt) { - this.cipher.reprompt = CipherRepromptType.Password; - this.cipher.login.autofillOnPageLoad = this.autofillOnPageLoadOptions[2].value; - } else { - this.cipher.reprompt = CipherRepromptType.None; - this.cipher.login.autofillOnPageLoad = this.autofillOnPageLoadOptions[0].value; - } - } - - protected async loadCollections() { - const allCollections = await this.collectionService.getAllDecrypted(); - return allCollections.filter((c) => !c.readOnly); - } - - protected loadCipher(userId: UserId) { - return this.cipherService.get(this.cipherId, userId); - } - - protected encryptCipher(userId: UserId) { - return this.cipherService.encrypt(this.cipher, userId); - } - - protected saveCipher(data: EncryptionContext) { - let orgAdmin = this.organization?.canEditAllCiphers; - - // if a cipher is unassigned we want to check if they are an admin or have permission to edit any collection - if (!data.cipher.collectionIds) { - orgAdmin = this.organization?.canEditUnassignedCiphers; - } - - return this.cipher.id == null - ? this.cipherService.createWithServer(data, orgAdmin) - : this.cipherService.updateWithServer(data, orgAdmin); - } - - protected deleteCipher(userId: UserId) { - return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id, userId, this.asAdmin) - : this.cipherService.softDeleteWithServer(this.cipher.id, userId, this.asAdmin); - } - - protected restoreCipher(userId: UserId) { - return this.cipherService.restoreWithServer(this.cipher.id, userId, this.asAdmin); - } - - /** - * Determines if a cipher must be deleted as an admin by belonging to an organization and being unassigned to a collection. - */ - get asAdmin(): boolean { - return ( - this.cipher.organizationId !== null && - this.cipher.organizationId.length > 0 && - (this.organization?.canEditAllCiphers || - !this.cipher.collectionIds || - this.cipher.collectionIds.length === 0) - ); - } - - get defaultOwnerId(): string | null { - return this.ownershipOptions[0].value; - } - - async loadAddEditCipherInfo(userId: UserId): Promise { - const addEditCipherInfo: any = await firstValueFrom( - this.cipherService.addEditCipherInfo$(userId), - ); - const loadedSavedInfo = addEditCipherInfo != null; - - if (loadedSavedInfo) { - this.cipher = addEditCipherInfo.cipher; - this.collectionIds = addEditCipherInfo.collectionIds; - - if (!this.editMode && !this.allowPersonal && this.cipher.organizationId == null) { - // This is a new cipher and personal ownership isn't allowed, so we need to set the default owner - this.cipher.organizationId = this.defaultOwnerId; - } - } - - await this.cipherService.setAddEditCipherInfo(null, userId); - - return loadedSavedInfo; - } - - async copy(value: string, typeI18nKey: string, aType: string): Promise { - if (value == null) { - return false; - } - - const copyOptions = this.win != null ? { window: this.win } : null; - this.platformUtilsService.copyToClipboard(value, copyOptions); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - }); - - if (typeI18nKey === "password") { - void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedPassword, [ - this.cipher, - ]); - } else if (typeI18nKey === "securityCode") { - void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedCardCode, [ - this.cipher, - ]); - } else if (aType === "H_Field") { - void this.eventCollectionService.collectMany(EventType.Cipher_ClientCopiedHiddenField, [ - this.cipher, - ]); - } - - return true; - } - - async importSshKeyFromClipboard() { - const key = await this.sshImportPromptService.importSshKeyFromClipboard(); - if (key != null) { - this.cipher.sshKey.privateKey = key.privateKey; - this.cipher.sshKey.publicKey = key.publicKey; - this.cipher.sshKey.keyFingerprint = key.keyFingerprint; - } - } - - private async generateSshKey(showNotification: boolean = true) { - await firstValueFrom(this.sdkService.client$); - const sshKey = generate_ssh_key("Ed25519"); - this.cipher.sshKey.privateKey = sshKey.privateKey; - this.cipher.sshKey.publicKey = sshKey.publicKey; - this.cipher.sshKey.keyFingerprint = sshKey.fingerprint; - - if (showNotification) { - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("sshKeyGenerated"), - }); - } - } - - async typeChange() { - if (this.cipher.type === CipherType.SshKey) { - await this.generateSshKey(); - } - } -} diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts deleted file mode 100644 index e4b01d3aac1..00000000000 --- a/libs/angular/src/vault/components/attachments.component.ts +++ /dev/null @@ -1,354 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherId, UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; -import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; -import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -@Directive() -export class AttachmentsComponent implements OnInit { - @Input() cipherId: string; - @Input() viewOnly: boolean; - @Output() onUploadedAttachment = new EventEmitter(); - @Output() onDeletedAttachment = new EventEmitter(); - @Output() onReuploadedAttachment = new EventEmitter(); - - cipher: CipherView; - cipherDomain: Cipher; - canAccessAttachments: boolean; - formPromise: Promise; - deletePromises: { [id: string]: Promise } = {}; - reuploadPromises: { [id: string]: Promise } = {}; - emergencyAccessId?: string = null; - protected componentName = ""; - - constructor( - protected cipherService: CipherService, - protected i18nService: I18nService, - protected keyService: KeyService, - protected encryptService: EncryptService, - protected platformUtilsService: PlatformUtilsService, - protected apiService: ApiService, - protected win: Window, - protected logService: LogService, - protected stateService: StateService, - protected fileDownloadService: FileDownloadService, - protected dialogService: DialogService, - protected billingAccountProfileStateService: BillingAccountProfileStateService, - protected accountService: AccountService, - protected toastService: ToastService, - protected configService: ConfigService, - ) {} - - async ngOnInit() { - await this.init(); - } - - async submit() { - const fileEl = document.getElementById("file") as HTMLInputElement; - const files = fileEl.files; - if (files == null || files.length === 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("selectFile"), - }); - return; - } - - if (files[0].size > 524288000) { - // 500 MB - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("maxFileSize"), - }); - return; - } - - try { - const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.formPromise = this.saveCipherAttachment(files[0], activeUserId); - this.cipherDomain = await this.formPromise; - this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("attachmentSaved"), - }); - this.onUploadedAttachment.emit(this.cipher); - } catch (e) { - this.logService.error(e); - } - - // reset file input - // ref: https://stackoverflow.com/a/20552042 - fileEl.type = ""; - fileEl.type = "file"; - fileEl.value = ""; - } - - async delete(attachment: AttachmentView) { - if (this.deletePromises[attachment.id] != null) { - return; - } - - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "deleteAttachment" }, - content: { key: "deleteAttachmentConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - - this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id, activeUserId); - const updatedCipher = await this.deletePromises[attachment.id]; - - const cipher = new Cipher(updatedCipher); - this.cipher = await this.cipherService.decrypt(cipher, activeUserId); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("deletedAttachment"), - }); - const i = this.cipher.attachments.indexOf(attachment); - if (i > -1) { - this.cipher.attachments.splice(i, 1); - } - } catch (e) { - this.logService.error(e); - } - - this.deletePromises[attachment.id] = null; - this.onDeletedAttachment.emit(this.cipher); - } - - async download(attachment: AttachmentView) { - const a = attachment as any; - if (a.downloading) { - return; - } - - if (!this.canAccessAttachments) { - 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, - this.emergencyAccessId, - ); - 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; - } - } - - a.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"), - }); - a.downloading = false; - return; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( - this.cipherDomain.id as CipherId, - attachment, - response, - activeUserId, - ); - - this.fileDownloadService.download({ - fileName: attachment.fileName, - blobData: decBuf, - }); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("fileSavedToDevice"), - }); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - - a.downloading = false; - } - - protected async init() { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.cipherDomain = await this.loadCipher(activeUserId); - this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); - - const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), - ); - this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; - - if (!this.canAccessAttachments) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "premiumRequired" }, - content: { key: "premiumRequiredDesc" }, - acceptButtonText: { key: "learnMore" }, - type: "success", - }); - - if (confirmed) { - this.platformUtilsService.launchUri( - "https://vault.bitwarden.com/#/settings/subscription/premium", - ); - } - } - } - - protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) { - const a = attachment as any; - if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) { - return; - } - - try { - this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => { - // 1. Download - a.downloading = true; - const response = await fetch(new Request(attachment.url, { cache: "no-store" })); - if (response.status !== 200) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - a.downloading = false; - return; - } - - try { - // 2. Resave - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(getUserId), - ); - - const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( - this.cipherDomain.id as CipherId, - attachment, - response, - activeUserId, - ); - - this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( - this.cipherDomain, - attachment.fileName, - decBuf, - activeUserId, - admin, - ); - this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); - - // 3. Delete old - this.deletePromises[attachment.id] = this.deleteCipherAttachment( - attachment.id, - activeUserId, - ); - await this.deletePromises[attachment.id]; - const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id); - if (foundAttachment.length > 0) { - const i = this.cipher.attachments.indexOf(foundAttachment[0]); - if (i > -1) { - this.cipher.attachments.splice(i, 1); - } - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("attachmentSaved"), - }); - this.onReuploadedAttachment.emit(); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - - a.downloading = false; - }); - await this.reuploadPromises[attachment.id]; - } catch (e) { - this.logService.error(e); - } - } - - protected loadCipher(userId: UserId) { - return this.cipherService.get(this.cipherId, userId); - } - - protected saveCipherAttachment(file: File, userId: UserId) { - return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId); - } - - protected deleteCipherAttachment(attachmentId: string, userId: UserId) { - return this.cipherService.deleteAttachmentWithServer( - this.cipher.id, - attachmentId, - userId, - false, - ); - } - - protected async reupload(attachment: AttachmentView) { - // TODO: This should be removed but is needed since we re-use the same template - } -} diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts deleted file mode 100644 index acb89b82191..00000000000 --- a/libs/angular/src/vault/components/password-history.component.ts +++ /dev/null @@ -1,48 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view"; -import { ToastService } from "@bitwarden/components"; - -@Directive() -export class PasswordHistoryComponent implements OnInit { - cipherId: string; - history: PasswordHistoryView[] = []; - - constructor( - protected cipherService: CipherService, - protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, - protected accountService: AccountService, - private win: Window, - private toastService: ToastService, - ) {} - - async ngOnInit() { - await this.init(); - } - - copy(password: string) { - const copyOptions = this.win != null ? { window: this.win } : null; - this.platformUtilsService.copyToClipboard(password, copyOptions); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t("password")), - }); - } - - protected async init() { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const cipher = await this.cipherService.get(this.cipherId, activeUserId); - const decCipher = await this.cipherService.decrypt(cipher, activeUserId); - this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; - } -} diff --git a/libs/angular/src/vault/components/view-custom-fields.component.ts b/libs/angular/src/vault/components/view-custom-fields.component.ts deleted file mode 100644 index d991a0260c1..00000000000 --- a/libs/angular/src/vault/components/view-custom-fields.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, Input } from "@angular/core"; - -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { EventType } from "@bitwarden/common/enums"; -import { FieldType } from "@bitwarden/common/vault/enums"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; - -@Directive() -export class ViewCustomFieldsComponent { - @Input() cipher: CipherView; - @Input() promptPassword: () => Promise; - @Input() copy: (value: string, typeI18nKey: string, aType: string) => void; - - fieldType = FieldType; - - constructor(private eventCollectionService: EventCollectionService) {} - - async toggleFieldValue(field: FieldView) { - if (!(await this.promptPassword())) { - return; - } - - const f = field as any; - f.showValue = !f.showValue; - f.showCount = false; - if (f.showValue) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( - EventType.Cipher_ClientToggledHiddenFieldVisible, - this.cipher.id, - ); - } - } - - async toggleFieldCount(field: FieldView) { - if (!field.showValue) { - return; - } - - field.showCount = !field.showCount; - } - - setTextDataOnDrag(event: DragEvent, data: string) { - event.dataTransfer.setData("text", data); - } -} diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts deleted file mode 100644 index e3eb50cb29e..00000000000 --- a/libs/angular/src/vault/components/view.component.ts +++ /dev/null @@ -1,568 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe } from "@angular/common"; -import { - ChangeDetectorRef, - Directive, - EventEmitter, - Input, - NgZone, - OnDestroy, - OnInit, - Output, -} from "@angular/core"; -import { - BehaviorSubject, - combineLatest, - filter, - firstValueFrom, - map, - Observable, - of, - switchMap, - tap, -} from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EventType } from "@bitwarden/common/enums"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { CipherId, UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType, FieldType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; -import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { TotpInfo } from "@bitwarden/common/vault/services/totp.service"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { PasswordRepromptService } from "@bitwarden/vault"; - -const BroadcasterSubscriptionId = "BaseViewComponent"; - -@Directive() -export class ViewComponent implements OnDestroy, OnInit { - /** Observable of cipherId$ that will update each time the `Input` updates */ - private _cipherId$ = new BehaviorSubject(null); - - @Input() - set cipherId(value: string) { - this._cipherId$.next(value); - } - - get cipherId(): string { - return this._cipherId$.getValue(); - } - - @Input() collectionId: string; - @Output() onEditCipher = new EventEmitter(); - @Output() onCloneCipher = new EventEmitter(); - @Output() onShareCipher = new EventEmitter(); - @Output() onDeletedCipher = new EventEmitter(); - @Output() onRestoredCipher = new EventEmitter(); - - canDeleteCipher$: Observable; - canRestoreCipher$: Observable; - cipher: CipherView; - showPassword: boolean; - showPasswordCount: boolean; - showCardNumber: boolean; - showCardCode: boolean; - showPrivateKey: boolean; - canAccessPremium: boolean; - showPremiumRequiredTotp: boolean; - fieldType = FieldType; - checkPasswordPromise: Promise; - folder: FolderView; - cipherType = CipherType; - - private previousCipherId: string; - protected passwordReprompted = false; - - /** - * Represents TOTP information including display formatting and timing - */ - protected totpInfo$: Observable | undefined; - - get fido2CredentialCreationDateValue(): string { - const dateCreated = this.i18nService.t("dateCreated"); - const creationDate = this.datePipe.transform( - this.cipher?.login?.fido2Credentials?.[0]?.creationDate, - "short", - ); - return `${dateCreated} ${creationDate}`; - } - - constructor( - protected cipherService: CipherService, - protected folderService: FolderService, - protected totpService: TotpService, - protected tokenService: TokenService, - protected i18nService: I18nService, - protected keyService: KeyService, - protected encryptService: EncryptService, - protected platformUtilsService: PlatformUtilsService, - protected auditService: AuditService, - protected win: Window, - protected broadcasterService: BroadcasterService, - protected ngZone: NgZone, - protected changeDetectorRef: ChangeDetectorRef, - protected eventCollectionService: EventCollectionService, - protected apiService: ApiService, - protected passwordRepromptService: PasswordRepromptService, - private logService: LogService, - protected stateService: StateService, - protected fileDownloadService: FileDownloadService, - protected dialogService: DialogService, - protected datePipe: DatePipe, - protected accountService: AccountService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - protected toastService: ToastService, - private cipherAuthorizationService: CipherAuthorizationService, - protected configService: ConfigService, - ) {} - - ngOnInit() { - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - this.changeDetectorRef.detectChanges(); - } - break; - } - }); - }); - - // Set up the subscription to the activeAccount$ and cipherId$ observables - combineLatest([this.accountService.activeAccount$.pipe(getUserId), this._cipherId$]) - .pipe( - tap(() => this.cleanUp()), - switchMap(([userId, cipherId]) => { - const cipher$ = this.cipherService.cipherViews$(userId).pipe( - map((ciphers) => ciphers?.find((c) => c.id === cipherId)), - filter((cipher) => !!cipher), - ); - return combineLatest([of(userId), cipher$]); - }), - ) - .subscribe(([userId, cipher]) => { - this.cipher = cipher; - - void this.constructCipherDetails(userId); - }); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.cleanUp(); - } - - async edit() { - this.onEditCipher.emit(this.cipher); - } - - async clone() { - if (this.cipher.login?.hasFido2Credentials) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "passkeyNotCopied" }, - content: { key: "passkeyNotCopiedAlert" }, - type: "info", - }); - - if (!confirmed) { - return false; - } - } - - if (await this.promptPassword()) { - this.onCloneCipher.emit(this.cipher); - return true; - } - - return false; - } - - async share() { - if (await this.promptPassword()) { - this.onShareCipher.emit(this.cipher); - return true; - } - - return false; - } - - async delete(): Promise { - if (!(await this.promptPassword())) { - return; - } - - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "deleteItem" }, - content: { - key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation", - }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.deleteCipher(activeUserId); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t( - this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", - ), - }); - this.onDeletedCipher.emit(this.cipher); - } catch (e) { - this.logService.error(e); - } - - return true; - } - - async restore(): Promise { - if (!this.cipher.isDeleted) { - return false; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.restoreCipher(activeUserId); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("restoredItem"), - }); - this.onRestoredCipher.emit(this.cipher); - } catch (e) { - this.logService.error(e); - } - - return true; - } - - async togglePassword() { - if (!(await this.promptPassword())) { - return; - } - - this.showPassword = !this.showPassword; - this.showPasswordCount = false; - if (this.showPassword) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( - EventType.Cipher_ClientToggledPasswordVisible, - this.cipherId, - ); - } - } - - async togglePasswordCount() { - if (!this.showPassword) { - return; - } - - this.showPasswordCount = !this.showPasswordCount; - } - - async toggleCardNumber() { - if (!(await this.promptPassword())) { - return; - } - - this.showCardNumber = !this.showCardNumber; - if (this.showCardNumber) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( - EventType.Cipher_ClientToggledCardNumberVisible, - this.cipherId, - ); - } - } - - async toggleCardCode() { - if (!(await this.promptPassword())) { - return; - } - - this.showCardCode = !this.showCardCode; - if (this.showCardCode) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect( - EventType.Cipher_ClientToggledCardCodeVisible, - this.cipherId, - ); - } - } - - togglePrivateKey() { - this.showPrivateKey = !this.showPrivateKey; - } - - async checkPassword() { - if ( - this.cipher.login == null || - this.cipher.login.password == null || - this.cipher.login.password === "" - ) { - return; - } - - this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); - const matches = await this.checkPasswordPromise; - - if (matches > 0) { - this.toastService.showToast({ - variant: "warning", - title: null, - message: this.i18nService.t("passwordExposed", matches.toString()), - }); - } else { - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("passwordSafe"), - }); - } - } - - async launch(uri: Launchable, cipherId?: string) { - if (!uri.canLaunch) { - return; - } - - if (cipherId) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.cipherService.updateLastLaunchedDate(cipherId, activeUserId); - } - - this.platformUtilsService.launchUri(uri.launchUri); - } - - async copy(value: string, typeI18nKey: string, aType: string): Promise { - if (value == null) { - return false; - } - - if ( - this.passwordRepromptService.protectedFields().includes(aType) && - !(await this.promptPassword()) - ) { - return false; - } - - const copyOptions = this.win != null ? { window: this.win } : null; - this.platformUtilsService.copyToClipboard(value, copyOptions); - this.toastService.showToast({ - variant: "info", - title: null, - message: this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)), - }); - - if (typeI18nKey === "password") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, this.cipherId); - } else if (typeI18nKey === "securityCode") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId); - } else if (aType === "H_Field") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId); - } - - return true; - } - - setTextDataOnDrag(event: DragEvent, data: string) { - event.dataTransfer.setData("text", data); - } - - async downloadAttachment(attachment: AttachmentView) { - if (!(await this.promptPassword())) { - return; - } - const a = attachment as any; - if (a.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; - } - } - - a.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"), - }); - a.downloading = false; - return; - } - - try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( - this.cipher.id as CipherId, - attachment, - response, - activeUserId, - ); - - this.fileDownloadService.download({ - fileName: attachment.fileName, - blobData: decBuf, - }); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - - a.downloading = false; - } - - protected deleteCipher(userId: UserId) { - return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id, userId) - : this.cipherService.softDeleteWithServer(this.cipher.id, userId); - } - - protected restoreCipher(userId: UserId) { - return this.cipherService.restoreWithServer(this.cipher.id, userId); - } - - protected async promptPassword() { - if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) { - return true; - } - - return (this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt()); - } - - private cleanUp() { - this.cipher = null; - this.folder = null; - this.showPassword = false; - this.showCardNumber = false; - this.showCardCode = false; - this.passwordReprompted = false; - } - - /** - * When a cipher is viewed, construct all details for the view that are not directly - * available from the cipher object itself. - */ - private async constructCipherDetails(userId: UserId) { - this.canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId), - ); - this.showPremiumRequiredTotp = - this.cipher.login.totp && !this.canAccessPremium && !this.cipher.organizationUseTotp; - this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher); - this.canRestoreCipher$ = this.cipherAuthorizationService.canRestoreCipher$(this.cipher); - - if (this.cipher.folderId) { - this.folder = await ( - await firstValueFrom(this.folderService.folderViews$(userId)) - ).find((f) => f.id == this.cipher.folderId); - } - - const canGenerateTotp = - this.cipher.type === CipherType.Login && - this.cipher.login.totp && - (this.cipher.organizationUseTotp || this.canAccessPremium); - - this.totpInfo$ = canGenerateTotp - ? this.totpService.getCode$(this.cipher.login.totp).pipe( - map((response) => { - const epoch = Math.round(new Date().getTime() / 1000.0); - const mod = epoch % response.period; - - // Format code - const totpCodeFormatted = - response.code.length > 4 - ? `${response.code.slice(0, Math.floor(response.code.length / 2))} ${response.code.slice(Math.floor(response.code.length / 2))}` - : response.code; - - return { - totpCode: response.code, - totpCodeFormatted, - totpDash: +(Math.round(((78.6 / response.period) * mod + "e+2") as any) + "e-2"), - totpSec: response.period - mod, - totpLow: response.period - mod <= 7, - } as TotpInfo; - }), - ) - : undefined; - - if (this.previousCipherId !== this.cipherId) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientViewed, this.cipherId); - } - this.previousCipherId = this.cipherId; - } -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 71de8fb5433..fcc81070331 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -53,7 +53,6 @@ export enum FeatureFlag { PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk", CipherKeyEncryption = "cipher-key-encryption", - PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", EndUserNotifications = "pm-10609-end-user-notifications", RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy", PM19315EndUserActivationMvp = "pm-19315-end-user-activation-mvp", @@ -97,7 +96,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE, [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, - [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, [FeatureFlag.RemoveCardItemTypePolicy]: FALSE,