1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00
Files
browser/libs/vault/src/components/copy-cipher-field.directive.ts
Nick Krantz cf9bc7c455 Vault Strict Typing cleanup (#12512)
* remove strict types from `NewDeviceVerificationNotice`
- Add null default value for class properties
- Enforce that the userId is passed
- noticeState$ can return null

* remove strict types from `CopyCipherFieldService`
- refactor title to be a string rather than null

* remove strict types from `PasswordRepromptComponent`
- add guard to exit early on submit but also solves removing null/undefined from typing

* use bang to ensure required input

* remove strict types from `CopyCipherFieldDirective`
- add bang for required types
- add default values for null types

* add bang for constant variables in cipher form stories

* remove strict types from `DeleteAttachmentComponent`
- add bang for required types
- refactor title to be an empty string

* fix tests
2025-01-02 15:37:48 -06:00

105 lines
3.1 KiB
TypeScript

import { Directive, HostBinding, HostListener, Input, OnChanges, Optional } from "@angular/core";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { MenuItemDirective } from "@bitwarden/components";
import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault";
/**
* Directive to copy a specific field from a cipher on click. Uses the `CopyCipherFieldService` to
* handle the copying of the field and any necessary password re-prompting or totp generation.
*
* Automatically disables the host element if the field to copy is not available or null.
*
* If the host element is a menu item, it will be hidden when disabled.
*
* @example
* ```html
* <button appCopyField="username" [cipher]="cipher">Copy Username</button>
* ```
*/
@Directive({
standalone: true,
selector: "[appCopyField]",
})
export class CopyCipherFieldDirective implements OnChanges {
@Input({
alias: "appCopyField",
required: true,
})
action!: Exclude<CopyAction, "hiddenField">;
@Input({ required: true }) cipher!: CipherView;
constructor(
private copyCipherFieldService: CopyCipherFieldService,
@Optional() private menuItemDirective?: MenuItemDirective,
) {}
@HostBinding("attr.disabled")
protected disabled: boolean | null = null;
/**
* Hide the element if it is disabled and is a menu item.
* @private
*/
@HostBinding("class.tw-hidden")
private get hidden() {
return this.disabled && this.menuItemDirective;
}
@HostListener("click")
async copy() {
const value = this.getValueToCopy();
await this.copyCipherFieldService.copy(value ?? "", this.action, this.cipher);
}
async ngOnChanges() {
await this.updateDisabledState();
}
private async updateDisabledState() {
this.disabled =
!this.cipher ||
!this.getValueToCopy() ||
(this.action === "totp" && !(await this.copyCipherFieldService.totpAllowed(this.cipher)))
? true
: null;
// If the directive is used on a menu item, update the menu item to prevent keyboard navigation
if (this.menuItemDirective) {
this.menuItemDirective.disabled = this.disabled ?? false;
}
}
private getValueToCopy() {
switch (this.action) {
case "username":
return this.cipher.login?.username || this.cipher.identity?.username;
case "password":
return this.cipher.login?.password;
case "totp":
return this.cipher.login?.totp;
case "cardNumber":
return this.cipher.card?.number;
case "securityCode":
return this.cipher.card?.code;
case "email":
return this.cipher.identity?.email;
case "phone":
return this.cipher.identity?.phone;
case "address":
return this.cipher.identity?.fullAddressForCopy;
case "secureNote":
return this.cipher.notes;
case "privateKey":
return this.cipher.sshKey?.privateKey;
case "publicKey":
return this.cipher.sshKey?.publicKey;
case "keyFingerprint":
return this.cipher.sshKey?.keyFingerprint;
default:
return null;
}
}
}