1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00
Files
browser/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts
Jason Ng 98af7a13ed [PM-19152] Archive in Web (#16686)
* archive and unarchive an individual item

* bulk archive and unachive

* updates to text strings for archive empty state and tooltips

* update translation keys to have an archive verb and noun differentiation

* if premium member loses premium and has archive items. apply filter changes, and item more option changes

* updating unArchive text

* unarchive an archived item on edit if user loses premium

* updates for unarchive btn, refactor archive flag for less churn

* add services to cipher form stories

* add refresh to archive calls in vault, update bulk archive copy

* Do not show archive ability for deleted items

* add archive check for login menu actions

* remove assign to collections for archive filter

* update bulk success message

* add error handling for archive methods

* fix null reference check

* add unarchive icon

---------

Co-authored-by: Nick Krantz <nick@livefront.com>
2025-10-14 16:41:05 -05:00

312 lines
9.0 KiB
TypeScript

// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { CollectionView } from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import {
CipherViewLike,
CipherViewLikeUtils,
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import {
convertToPermission,
getPermissionList,
} from "./../../../admin-console/organizations/shared/components/access-selector/access-selector.models";
import { VaultItemEvent } from "./vault-item-event";
import { RowHeightClass } from "./vault-items.component";
@Component({
selector: "tr[appVaultCipherRow]",
templateUrl: "vault-cipher-row.component.html",
standalone: false,
})
export class VaultCipherRowComponent<C extends CipherViewLike> implements OnInit {
protected RowHeightClass = RowHeightClass;
@Input() disabled: boolean;
@Input() cipher: C;
@Input() showOwner: boolean;
@Input() showCollections: boolean;
@Input() showGroups: boolean;
@Input() showPremiumFeatures: boolean;
@Input() useEvents: boolean;
@Input() cloneable: boolean;
@Input() organizations: Organization[];
@Input() collections: CollectionView[];
@Input() viewingOrgVault: boolean;
@Input() canEditCipher: boolean;
@Input() canAssignCollections: boolean;
@Input() canManageCollection: boolean;
/**
* uses new permission delete logic from PM-15493
*/
@Input() canDeleteCipher: boolean;
/**
* uses new permission restore logic from PM-15493
*/
@Input() canRestoreCipher: boolean;
/**
* user has archive permissions
*/
@Input() userCanArchive: boolean;
/**
* Enforge Org Data Ownership Policy Status
*/
@Input() enforceOrgDataOwnershipPolicy: boolean;
@Output() onEvent = new EventEmitter<VaultItemEvent<C>>();
@Input() checked: boolean;
@Output() checkedToggled = new EventEmitter<void>();
protected CipherType = CipherType;
private permissionList = getPermissionList();
private permissionPriority = [
"manageCollection",
"editItems",
"editItemsHidePass",
"viewItems",
"viewItemsHidePass",
];
protected organization?: Organization;
constructor(private i18nService: I18nService) {}
/**
* Lifecycle hook for component initialization.
*/
async ngOnInit(): Promise<void> {
if (this.cipher.organizationId != null) {
this.organization = this.organizations.find((o) => o.id === this.cipher.organizationId);
}
}
protected get showArchiveButton() {
return (
this.userCanArchive &&
!CipherViewLikeUtils.isArchived(this.cipher) &&
!CipherViewLikeUtils.isDeleted(this.cipher) &&
!this.cipher.organizationId
);
}
// If item is archived always show unarchive button, even if user is not premium
protected get showUnArchiveButton() {
return CipherViewLikeUtils.isArchived(this.cipher);
}
protected get clickAction() {
if (this.decryptionFailure) {
return "showFailedToDecrypt";
}
return "view";
}
protected get showTotpCopyButton() {
const login = CipherViewLikeUtils.getLogin(this.cipher);
const hasTotp = login?.totp ?? false;
return hasTotp && (this.cipher.organizationUseTotp || this.showPremiumFeatures);
}
protected get showFixOldAttachments() {
return this.cipher.hasOldAttachments && this.cipher.organizationId == null;
}
protected get hasAttachments() {
return CipherViewLikeUtils.hasAttachments(this.cipher);
}
// Do not show attachments button if:
// item is archived AND user is not premium user
protected get showAttachments() {
if (CipherViewLikeUtils.isArchived(this.cipher) && !this.userCanArchive) {
return false;
}
return this.canEditCipher || this.hasAttachments;
}
protected get canLaunch() {
return CipherViewLikeUtils.canLaunch(this.cipher);
}
protected get launchUri() {
return CipherViewLikeUtils.getLaunchUri(this.cipher);
}
protected get subtitle() {
return CipherViewLikeUtils.subtitle(this.cipher);
}
protected get isDeleted() {
return CipherViewLikeUtils.isDeleted(this.cipher);
}
protected get decryptionFailure() {
return CipherViewLikeUtils.decryptionFailure(this.cipher);
}
// Do Not show Assign to Collections option if item is archived
protected get showAssignToCollections() {
if (CipherViewLikeUtils.isArchived(this.cipher)) {
return false;
}
return (
this.organizations?.length &&
this.canAssignCollections &&
!CipherViewLikeUtils.isDeleted(this.cipher)
);
}
// Do NOT show clone option if:
// item is archived AND user is not premium user
// item is archived AND enforce org data ownership policy is on
protected get showClone() {
if (
CipherViewLikeUtils.isArchived(this.cipher) &&
(!this.userCanArchive || this.enforceOrgDataOwnershipPolicy)
) {
return false;
}
return this.cloneable && !CipherViewLikeUtils.isDeleted(this.cipher);
}
protected get showEventLogs() {
return this.useEvents && this.cipher.organizationId;
}
protected get isActiveLoginCipher() {
return (
CipherViewLikeUtils.getType(this.cipher) === this.CipherType.Login &&
!CipherViewLikeUtils.isDeleted(this.cipher) &&
!CipherViewLikeUtils.isArchived(this.cipher)
);
}
protected get hasPasswordToCopy() {
return CipherViewLikeUtils.hasCopyableValue(this.cipher, "password");
}
protected get hasUsernameToCopy() {
return CipherViewLikeUtils.hasCopyableValue(this.cipher, "username");
}
protected get permissionText() {
if (!this.cipher.organizationId || this.cipher.collectionIds.length === 0) {
return this.i18nService.t("manageCollection");
}
const filteredCollections = this.collections.filter((collection) => {
if (collection.assigned) {
return this.cipher.collectionIds.find((id) => {
if (collection.id === id) {
return collection;
}
});
}
});
if (filteredCollections?.length === 1) {
return this.i18nService.t(
this.permissionList.find((p) => p.perm === convertToPermission(filteredCollections[0]))
?.labelId,
);
}
if (filteredCollections?.length > 1) {
const labels = filteredCollections.map((collection) => {
return this.permissionList.find((p) => p.perm === convertToPermission(collection))?.labelId;
});
const highestPerm = this.permissionPriority.find((perm) => labels.includes(perm));
return this.i18nService.t(highestPerm);
}
return this.i18nService.t("noAccess");
}
protected get showCopyUsername(): boolean {
const usernameCopy = CipherViewLikeUtils.hasCopyableValue(this.cipher, "username");
return this.isActiveLoginCipher && usernameCopy;
}
protected get showCopyPassword(): boolean {
const passwordCopy = CipherViewLikeUtils.hasCopyableValue(this.cipher, "password");
return this.isActiveLoginCipher && this.cipher.viewPassword && passwordCopy;
}
protected get showCopyTotp(): boolean {
return this.isActiveLoginCipher && this.showTotpCopyButton;
}
protected get showLaunchUri(): boolean {
return this.isActiveLoginCipher && this.canLaunch;
}
protected get isDeletedCanRestore(): boolean {
return CipherViewLikeUtils.isDeleted(this.cipher) && this.canRestoreCipher;
}
protected get hideMenu() {
return !(
this.isDeletedCanRestore ||
this.showCopyUsername ||
this.showCopyPassword ||
this.showCopyTotp ||
this.showLaunchUri ||
this.showAttachments ||
this.showClone ||
this.canEditCipher
);
}
protected copy(field: "username" | "password" | "totp") {
this.onEvent.emit({ type: "copyField", item: this.cipher, field });
}
protected clone() {
this.onEvent.emit({ type: "clone", item: this.cipher });
}
protected events() {
this.onEvent.emit({ type: "viewEvents", item: this.cipher });
}
protected archive() {
this.onEvent.emit({ type: "archive", items: [this.cipher] });
}
protected unarchive() {
this.onEvent.emit({ type: "unarchive", items: [this.cipher] });
}
protected restore() {
this.onEvent.emit({ type: "restore", items: [this.cipher] });
}
protected deleteCipher() {
this.onEvent.emit({ type: "delete", items: [{ cipher: this.cipher }] });
}
protected attachments() {
this.onEvent.emit({ type: "viewAttachments", item: this.cipher });
}
protected assignToCollections() {
this.onEvent.emit({ type: "assignToCollections", items: [this.cipher] });
}
protected get showCheckbox() {
if (!this.viewingOrgVault || !this.organization) {
return true; // Always show checkbox in individual vault or for non-org items
}
return this.organization.canEditAllCiphers || (this.cipher.edit && this.cipher.viewPassword);
}
}