mirror of
https://github.com/bitwarden/browser
synced 2026-02-22 20:34:04 +00:00
refactor(emergency-access-takeover): [PM-18721][PM-21271] Integrate InputPasswordComponent in EmergencyAccessTakeoverDialogComponent (#14636)
Integrates the `InputPasswordComponent` within the `EmergencyAccessTakeoverDialogComponent` Feature Flag: `PM16117_ChangeExistingPasswordRefactor`
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormBuilder, Validators, FormControl } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
ToastService,
|
||||
Translation,
|
||||
} from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import {
|
||||
DEFAULT_KDF_CONFIG,
|
||||
KdfConfig,
|
||||
@@ -53,31 +55,46 @@ import { PasswordInputResult } from "./password-input-result";
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum InputPasswordFlow {
|
||||
/**
|
||||
* Form elements displayed:
|
||||
* - [Input] New password
|
||||
* - [Input] New password confirm
|
||||
* - [Input] New password hint
|
||||
* - [Checkbox] Check for breaches
|
||||
* Form Fields: `[newPassword, newPasswordConfirm, newPasswordHint, checkForBreaches]`
|
||||
*
|
||||
* Note: this flow does not receive an active account `userId` as an `@Input`
|
||||
*/
|
||||
SetInitialPasswordAccountRegistration,
|
||||
/**
|
||||
* Form Fields: `[newPassword, newPasswordConfirm, newPasswordHint, checkForBreaches]`
|
||||
*/
|
||||
AccountRegistration, // important: this flow does not involve an activeAccount/userId
|
||||
SetInitialPasswordAuthedUser,
|
||||
/*
|
||||
* All form elements above, plus: [Input] Current password (as the first element in the UI)
|
||||
/**
|
||||
* Form Fields: `[currentPassword, newPassword, newPasswordConfirm, newPasswordHint, checkForBreaches]`
|
||||
*/
|
||||
ChangePassword,
|
||||
/**
|
||||
* All form elements above, plus: [Checkbox] Rotate account encryption key (as the last element in the UI)
|
||||
* Form Fields: `[currentPassword, newPassword, newPasswordConfirm, newPasswordHint, checkForBreaches, rotateUserKey]`
|
||||
*/
|
||||
ChangePasswordWithOptionalUserKeyRotation,
|
||||
/**
|
||||
* This flow is used when a user changes the password for another user's account, such as:
|
||||
* - Emergency Access Takeover
|
||||
* - Account Recovery
|
||||
*
|
||||
* Since both of those processes use a dialog, the `InputPasswordComponent` will not display
|
||||
* buttons for `ChangePasswordDelegation` because the dialog will have its own buttons.
|
||||
*
|
||||
* Form Fields: `[newPassword, newPasswordConfirm]`
|
||||
*
|
||||
* Note: this flow does not receive an active account `userId` or `email` as `@Input`s
|
||||
*/
|
||||
ChangePasswordDelegation,
|
||||
}
|
||||
|
||||
interface InputPasswordForm {
|
||||
currentPassword?: FormControl<string>;
|
||||
|
||||
newPassword: FormControl<string>;
|
||||
newPasswordConfirm: FormControl<string>;
|
||||
newPasswordHint: FormControl<string>;
|
||||
checkForBreaches: FormControl<boolean>;
|
||||
newPasswordHint?: FormControl<string>;
|
||||
|
||||
currentPassword?: FormControl<string>;
|
||||
checkForBreaches?: FormControl<boolean>;
|
||||
rotateUserKey?: FormControl<boolean>;
|
||||
}
|
||||
|
||||
@@ -91,20 +108,25 @@ interface InputPasswordForm {
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
InputModule,
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
JslibModule,
|
||||
PasswordCalloutComponent,
|
||||
PasswordStrengthV2Component,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
SharedModule,
|
||||
],
|
||||
})
|
||||
export class InputPasswordComponent implements OnInit {
|
||||
@ViewChild(PasswordStrengthV2Component) passwordStrengthComponent:
|
||||
| PasswordStrengthV2Component
|
||||
| undefined = undefined;
|
||||
|
||||
@Output() onPasswordFormSubmit = new EventEmitter<PasswordInputResult>();
|
||||
@Output() onSecondaryButtonClick = new EventEmitter<void>();
|
||||
@Output() isSubmitting = new EventEmitter<boolean>();
|
||||
|
||||
@Input({ required: true }) flow!: InputPasswordFlow;
|
||||
@Input({ required: true, transform: (val: string) => val.trim().toLowerCase() }) email!: string;
|
||||
|
||||
@Input({ transform: (val: string) => val?.trim().toLowerCase() }) email?: string;
|
||||
@Input() userId?: UserId;
|
||||
@Input() loading = false;
|
||||
@Input() masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null;
|
||||
@@ -132,11 +154,6 @@ export class InputPasswordComponent implements OnInit {
|
||||
Validators.minLength(this.minPasswordLength),
|
||||
]),
|
||||
newPasswordConfirm: this.formBuilder.nonNullable.control("", Validators.required),
|
||||
newPasswordHint: this.formBuilder.nonNullable.control("", [
|
||||
Validators.minLength(this.minHintLength),
|
||||
Validators.maxLength(this.maxHintLength),
|
||||
]),
|
||||
checkForBreaches: this.formBuilder.nonNullable.control(true),
|
||||
},
|
||||
{
|
||||
validators: [
|
||||
@@ -146,12 +163,6 @@ export class InputPasswordComponent implements OnInit {
|
||||
"newPasswordConfirm",
|
||||
this.i18nService.t("masterPassDoesntMatch"),
|
||||
),
|
||||
compareInputs(
|
||||
ValidationGoal.InputsShouldNotMatch,
|
||||
"newPassword",
|
||||
"newPasswordHint",
|
||||
this.i18nService.t("hintEqualsPassword"),
|
||||
),
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -176,9 +187,11 @@ export class InputPasswordComponent implements OnInit {
|
||||
private kdfConfigService: KdfConfigService,
|
||||
private keyService: KeyService,
|
||||
private masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private policyService: PolicyService,
|
||||
private toastService: ToastService,
|
||||
private validationService: ValidationService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -187,6 +200,27 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
|
||||
private addFormFieldsIfNecessary() {
|
||||
if (this.flow !== InputPasswordFlow.ChangePasswordDelegation) {
|
||||
this.formGroup.addControl(
|
||||
"newPasswordHint",
|
||||
this.formBuilder.nonNullable.control("", [
|
||||
Validators.minLength(this.minHintLength),
|
||||
Validators.maxLength(this.maxHintLength),
|
||||
]),
|
||||
);
|
||||
|
||||
this.formGroup.addValidators([
|
||||
compareInputs(
|
||||
ValidationGoal.InputsShouldNotMatch,
|
||||
"newPassword",
|
||||
"newPasswordHint",
|
||||
this.i18nService.t("hintEqualsPassword"),
|
||||
),
|
||||
]);
|
||||
|
||||
this.formGroup.addControl("checkForBreaches", this.formBuilder.nonNullable.control(true));
|
||||
}
|
||||
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
@@ -227,160 +261,201 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
protected submit = async () => {
|
||||
this.verifyFlowAndUserId();
|
||||
submit = async () => {
|
||||
try {
|
||||
this.isSubmitting.emit(true);
|
||||
|
||||
this.formGroup.markAllAsTouched();
|
||||
this.verifyFlow();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
this.showErrorSummary = true;
|
||||
return;
|
||||
}
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (!this.email) {
|
||||
throw new Error("Email is required to create master key.");
|
||||
}
|
||||
|
||||
const currentPassword = this.formGroup.controls.currentPassword?.value ?? "";
|
||||
const newPassword = this.formGroup.controls.newPassword.value;
|
||||
const newPasswordHint = this.formGroup.controls.newPasswordHint.value;
|
||||
const checkForBreaches = this.formGroup.controls.checkForBreaches.value;
|
||||
|
||||
// 1. Determine kdfConfig
|
||||
if (this.flow === InputPasswordFlow.AccountRegistration) {
|
||||
this.kdfConfig = DEFAULT_KDF_CONFIG;
|
||||
} else {
|
||||
if (!this.userId) {
|
||||
throw new Error("userId not passed down");
|
||||
}
|
||||
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
|
||||
}
|
||||
|
||||
if (this.kdfConfig == null) {
|
||||
throw new Error("KdfConfig is required to create master key.");
|
||||
}
|
||||
|
||||
// 2. Verify current password is correct (if necessary)
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
const currentPasswordVerified = await this.verifyCurrentPassword(
|
||||
currentPassword,
|
||||
this.kdfConfig,
|
||||
);
|
||||
if (!currentPasswordVerified) {
|
||||
if (this.formGroup.invalid) {
|
||||
this.showErrorSummary = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPassword = this.formGroup.controls.currentPassword?.value ?? "";
|
||||
const newPassword = this.formGroup.controls.newPassword.value;
|
||||
const newPasswordHint = this.formGroup.controls.newPasswordHint?.value ?? "";
|
||||
const checkForBreaches = this.formGroup.controls.checkForBreaches?.value ?? true;
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordDelegation) {
|
||||
await this.handleChangePasswordDelegationFlow(newPassword);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.email) {
|
||||
throw new Error("Email is required to create master key.");
|
||||
}
|
||||
|
||||
// 1. Determine kdfConfig
|
||||
if (this.flow === InputPasswordFlow.SetInitialPasswordAccountRegistration) {
|
||||
this.kdfConfig = DEFAULT_KDF_CONFIG;
|
||||
} else {
|
||||
if (!this.userId) {
|
||||
throw new Error("userId not passed down");
|
||||
}
|
||||
this.kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId));
|
||||
}
|
||||
|
||||
if (this.kdfConfig == null) {
|
||||
throw new Error("KdfConfig is required to create master key.");
|
||||
}
|
||||
|
||||
// 2. Verify current password is correct (if necessary)
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
const currentPasswordVerified = await this.verifyCurrentPassword(
|
||||
currentPassword,
|
||||
this.kdfConfig,
|
||||
);
|
||||
if (!currentPasswordVerified) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Verify new password
|
||||
const newPasswordVerified = await this.verifyNewPassword(
|
||||
newPassword,
|
||||
this.passwordStrengthScore,
|
||||
checkForBreaches,
|
||||
);
|
||||
if (!newPasswordVerified) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Create cryptographic keys and build a PasswordInputResult object
|
||||
const newMasterKey = await this.keyService.makeMasterKey(
|
||||
newPassword,
|
||||
this.email,
|
||||
this.kdfConfig,
|
||||
);
|
||||
|
||||
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
|
||||
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
const passwordInputResult: PasswordInputResult = {
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
newServerMasterKeyHash,
|
||||
newLocalMasterKeyHash,
|
||||
newPasswordHint,
|
||||
kdfConfig: this.kdfConfig,
|
||||
};
|
||||
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
const currentMasterKey = await this.keyService.makeMasterKey(
|
||||
currentPassword,
|
||||
this.email,
|
||||
this.kdfConfig,
|
||||
);
|
||||
|
||||
const currentServerMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
currentPassword,
|
||||
currentMasterKey,
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
|
||||
const currentLocalMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
currentPassword,
|
||||
currentMasterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
passwordInputResult.currentPassword = currentPassword;
|
||||
passwordInputResult.currentMasterKey = currentMasterKey;
|
||||
passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash;
|
||||
passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash;
|
||||
}
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
|
||||
}
|
||||
|
||||
// 5. Emit cryptographic keys and other password related properties
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
} finally {
|
||||
this.isSubmitting.emit(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We cannot mark the `userId` or `email` `@Input`s as required because some flows
|
||||
* require them, and some do not. This method enforces that:
|
||||
* - Certain flows MUST have a `userId` and/or `email` passed down
|
||||
* - Certain flows must NOT have a `userId` and/or `email` passed down
|
||||
*/
|
||||
private verifyFlow() {
|
||||
/** UserId checks */
|
||||
|
||||
// These flows require that an active account userId must NOT be passed down
|
||||
if (
|
||||
this.flow === InputPasswordFlow.SetInitialPasswordAccountRegistration ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordDelegation
|
||||
) {
|
||||
if (this.userId) {
|
||||
throw new Error("There should be no active account userId passed down in a this flow.");
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Verify new password
|
||||
// All other flows require that an active account userId MUST be passed down
|
||||
if (
|
||||
this.flow !== InputPasswordFlow.SetInitialPasswordAccountRegistration &&
|
||||
this.flow !== InputPasswordFlow.ChangePasswordDelegation
|
||||
) {
|
||||
if (!this.userId) {
|
||||
throw new Error("This flow requires that an active account userId be passed down.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Email checks */
|
||||
|
||||
// This flow requires that an email must NOT be passed down
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordDelegation) {
|
||||
if (this.email) {
|
||||
throw new Error("There should be no email passed down in this flow.");
|
||||
}
|
||||
}
|
||||
|
||||
// All other flows require that an email MUST be passed down
|
||||
if (this.flow !== InputPasswordFlow.ChangePasswordDelegation) {
|
||||
if (!this.email) {
|
||||
throw new Error("This flow requires that an email be passed down.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleChangePasswordDelegationFlow(newPassword: string) {
|
||||
const newPasswordVerified = await this.verifyNewPassword(
|
||||
newPassword,
|
||||
this.passwordStrengthScore,
|
||||
checkForBreaches,
|
||||
false,
|
||||
);
|
||||
if (!newPasswordVerified) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Create cryptographic keys and build a PasswordInputResult object
|
||||
const newMasterKey = await this.keyService.makeMasterKey(
|
||||
newPassword,
|
||||
this.email,
|
||||
this.kdfConfig,
|
||||
);
|
||||
|
||||
const newServerMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
|
||||
const newLocalMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
const passwordInputResult: PasswordInputResult = {
|
||||
newPassword,
|
||||
newMasterKey,
|
||||
newServerMasterKeyHash,
|
||||
newLocalMasterKeyHash,
|
||||
newPasswordHint,
|
||||
kdfConfig: this.kdfConfig,
|
||||
};
|
||||
|
||||
if (
|
||||
this.flow === InputPasswordFlow.ChangePassword ||
|
||||
this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation
|
||||
) {
|
||||
const currentMasterKey = await this.keyService.makeMasterKey(
|
||||
currentPassword,
|
||||
this.email,
|
||||
this.kdfConfig,
|
||||
);
|
||||
|
||||
const currentServerMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
currentPassword,
|
||||
currentMasterKey,
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
|
||||
const currentLocalMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
currentPassword,
|
||||
currentMasterKey,
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
passwordInputResult.currentPassword = currentPassword;
|
||||
passwordInputResult.currentMasterKey = currentMasterKey;
|
||||
passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash;
|
||||
passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash;
|
||||
}
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) {
|
||||
passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value;
|
||||
}
|
||||
|
||||
// 5. Emit cryptographic keys and other password related properties
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
};
|
||||
|
||||
/**
|
||||
* This method prevents a dev from passing down the wrong `InputPasswordFlow`
|
||||
* from the parent component or from failing to pass down a `userId` for flows
|
||||
* that require it.
|
||||
*
|
||||
* We cannot mark the `userId` `@Input` as required because in an account registration
|
||||
* flow we will not have an active account `userId` to pass down.
|
||||
*/
|
||||
private verifyFlowAndUserId() {
|
||||
/**
|
||||
* There can be no active account (and thus no userId) in an account registration
|
||||
* flow. If there is a userId, it means the dev passed down the wrong InputPasswordFlow
|
||||
* from the parent component.
|
||||
*/
|
||||
if (this.flow === InputPasswordFlow.AccountRegistration) {
|
||||
if (this.userId) {
|
||||
throw new Error(
|
||||
"There can be no userId in an account registration flow. Please pass down the appropriate InputPasswordFlow from the parent component.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There MUST be an active account (and thus a userId) in all other flows.
|
||||
* If no userId is passed down, it means the dev either:
|
||||
* (a) passed down the wrong InputPasswordFlow, or
|
||||
* (b) passed down the correct InputPasswordFlow but failed to pass down a userId
|
||||
*/
|
||||
if (this.flow !== InputPasswordFlow.AccountRegistration) {
|
||||
if (!this.userId) {
|
||||
throw new Error("The selected InputPasswordFlow requires that a userId be passed down");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,16 +466,19 @@ export class InputPasswordComponent implements OnInit {
|
||||
currentPassword: string,
|
||||
kdfConfig: KdfConfig,
|
||||
): Promise<boolean> {
|
||||
if (!this.email) {
|
||||
throw new Error("Email is required to verify current password.");
|
||||
}
|
||||
if (!this.userId) {
|
||||
throw new Error("userId is required to verify current password.");
|
||||
}
|
||||
|
||||
const currentMasterKey = await this.keyService.makeMasterKey(
|
||||
currentPassword,
|
||||
this.email,
|
||||
kdfConfig,
|
||||
);
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Error("userId not passed down");
|
||||
}
|
||||
|
||||
const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
|
||||
currentMasterKey,
|
||||
this.userId,
|
||||
@@ -549,4 +627,33 @@ export class InputPasswordComponent implements OnInit {
|
||||
protected getPasswordStrengthScore(score: PasswordStrengthScore) {
|
||||
this.passwordStrengthScore = score;
|
||||
}
|
||||
|
||||
protected async generatePassword() {
|
||||
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
|
||||
this.formGroup.patchValue({
|
||||
newPassword: await this.passwordGenerationService.generatePassword(options),
|
||||
});
|
||||
|
||||
if (!this.passwordStrengthComponent) {
|
||||
throw new Error("PasswordStrengthComponent is not initialized");
|
||||
}
|
||||
|
||||
this.passwordStrengthComponent.updatePasswordStrength(
|
||||
this.formGroup.controls.newPassword.value,
|
||||
);
|
||||
}
|
||||
|
||||
protected copy() {
|
||||
const value = this.formGroup.value.newPassword;
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.toastService.showToast({
|
||||
variant: "info",
|
||||
title: "",
|
||||
message: this.i18nService.t("valueCopied", this.i18nService.t("password")),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user