mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
refactor(account-recovery): [PM-18721][PM-21272] Integrate InputPasswordComponent in AccountRecoveryDialogComponent (#14662)
Integrates the `InputPasswordComponent` within the new `AccountRecoveryDialogComponent`. Feature flag: `PM16117_ChangeExistingPasswordRefactor`
This commit is contained in:
@@ -86,7 +86,7 @@ export abstract class BaseMembersComponent<UserView extends UserViewTypes> {
|
||||
protected i18nService: I18nService,
|
||||
protected keyService: KeyService,
|
||||
protected validationService: ValidationService,
|
||||
private logService: LogService,
|
||||
protected logService: LogService,
|
||||
protected userNamePipe: UserNamePipe,
|
||||
protected dialogService: DialogService,
|
||||
protected organizationManagementPreferencesService: OrganizationManagementPreferencesService,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<bit-dialog [title]="'recoverAccount' | i18n" [subtitle]="dialogData.name">
|
||||
<ng-container bitDialogContent>
|
||||
<bit-callout type="warning"
|
||||
>{{ "resetPasswordLoggedOutWarning" | i18n: loggedOutWarningName }}
|
||||
</bit-callout>
|
||||
|
||||
<auth-input-password
|
||||
[flow]="inputPasswordFlow"
|
||||
[masterPasswordPolicyOptions]="masterPasswordPolicyOptions$ | async"
|
||||
></auth-input-password>
|
||||
</ng-container>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton buttonType="primary" [bitAction]="handlePrimaryButtonClick">
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
@@ -0,0 +1,146 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, ViewChild } from "@angular/core";
|
||||
import { switchMap } from "rxjs";
|
||||
|
||||
import { InputPasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
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 { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { OrganizationUserResetPasswordService } from "../../services/organization-user-reset-password/organization-user-reset-password.service";
|
||||
|
||||
/**
|
||||
* Encapsulates a few key data inputs needed to initiate an account recovery
|
||||
* process for the organization user in question.
|
||||
*/
|
||||
export type AccountRecoveryDialogData = {
|
||||
/**
|
||||
* The organization user's full name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The organization user's email address
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* The `organizationUserId` for the user
|
||||
*/
|
||||
organizationUserId: string;
|
||||
|
||||
/**
|
||||
* The organization's `organizationId`
|
||||
*/
|
||||
organizationId: OrganizationId;
|
||||
};
|
||||
|
||||
export const AccountRecoveryDialogResultType = {
|
||||
Ok: "ok",
|
||||
} as const;
|
||||
|
||||
export type AccountRecoveryDialogResultType =
|
||||
(typeof AccountRecoveryDialogResultType)[keyof typeof AccountRecoveryDialogResultType];
|
||||
|
||||
/**
|
||||
* Used in a dialog for initiating the account recovery process against a
|
||||
* given organization user. An admin will access this form when they want to
|
||||
* reset a user's password and log them out of sessions.
|
||||
*/
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-account-recovery-dialog",
|
||||
templateUrl: "account-recovery-dialog.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
CommonModule,
|
||||
DialogModule,
|
||||
I18nPipe,
|
||||
InputPasswordComponent,
|
||||
],
|
||||
})
|
||||
export class AccountRecoveryDialogComponent {
|
||||
@ViewChild(InputPasswordComponent)
|
||||
inputPasswordComponent: InputPasswordComponent | undefined = undefined;
|
||||
|
||||
masterPasswordPolicyOptions$ = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)),
|
||||
);
|
||||
|
||||
inputPasswordFlow = InputPasswordFlow.ChangePasswordDelegation;
|
||||
|
||||
get loggedOutWarningName() {
|
||||
return this.dialogData.name != null ? this.dialogData.name : this.i18nService.t("thisUser");
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected dialogData: AccountRecoveryDialogData,
|
||||
private accountService: AccountService,
|
||||
private dialogRef: DialogRef<AccountRecoveryDialogResultType>,
|
||||
private i18nService: I18nService,
|
||||
private policyService: PolicyService,
|
||||
private resetPasswordService: OrganizationUserResetPasswordService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
handlePrimaryButtonClick = async () => {
|
||||
if (!this.inputPasswordComponent) {
|
||||
throw new Error("InputPasswordComponent is not initialized");
|
||||
}
|
||||
|
||||
const passwordInputResult = await this.inputPasswordComponent.submit();
|
||||
if (!passwordInputResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.resetPasswordService.resetMasterPassword(
|
||||
passwordInputResult.newPassword,
|
||||
this.dialogData.email,
|
||||
this.dialogData.organizationUserId,
|
||||
this.dialogData.organizationId,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("resetPasswordSuccess"),
|
||||
});
|
||||
|
||||
this.dialogRef.close(AccountRecoveryDialogResultType.Ok);
|
||||
};
|
||||
|
||||
/**
|
||||
* Strongly typed helper to open an `AccountRecoveryDialogComponent`
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param dialogConfig Configuration for the dialog
|
||||
*/
|
||||
static open = (
|
||||
dialogService: DialogService,
|
||||
dialogConfig: DialogConfig<
|
||||
AccountRecoveryDialogData,
|
||||
DialogRef<AccountRecoveryDialogResultType, unknown>
|
||||
>,
|
||||
) => {
|
||||
return dialogService.open<AccountRecoveryDialogResultType, AccountRecoveryDialogData>(
|
||||
AccountRecoveryDialogComponent,
|
||||
dialogConfig,
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./account-recovery-dialog.component";
|
||||
@@ -13,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
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 { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
@@ -47,7 +48,7 @@ export type ResetPasswordDialogData = {
|
||||
/**
|
||||
* The organization's `organizationId`
|
||||
*/
|
||||
organizationId: string;
|
||||
organizationId: OrganizationId;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
@@ -56,16 +57,18 @@ export enum ResetPasswordDialogResult {
|
||||
Ok = "ok",
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in a dialog for initiating the account recovery process against a
|
||||
* given organization user. An admin will access this form when they want to
|
||||
* reset a user's password and log them out of sessions.
|
||||
*
|
||||
* @deprecated Use the `AccountRecoveryDialogComponent` instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: "app-reset-password",
|
||||
templateUrl: "reset-password.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
/**
|
||||
* Used in a dialog for initiating the account recovery process against a
|
||||
* given organization user. An admin will access this form when they want to
|
||||
* reset a user's password and log them out of sessions.
|
||||
*/
|
||||
export class ResetPasswordComponent implements OnInit, OnDestroy {
|
||||
formGroup = this.formBuilder.group({
|
||||
newPassword: ["", Validators.required],
|
||||
|
||||
@@ -52,6 +52,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
@@ -66,6 +67,10 @@ import { GroupApiService } from "../core";
|
||||
import { OrganizationUserView } from "../core/views/organization-user.view";
|
||||
import { openEntityEventsDialog } from "../manage/entity-events.component";
|
||||
|
||||
import {
|
||||
AccountRecoveryDialogComponent,
|
||||
AccountRecoveryDialogResultType,
|
||||
} from "./components/account-recovery/account-recovery-dialog.component";
|
||||
import { BulkConfirmDialogComponent } from "./components/bulk/bulk-confirm-dialog.component";
|
||||
import { BulkDeleteDialogComponent } from "./components/bulk/bulk-delete-dialog.component";
|
||||
import { BulkEnableSecretsManagerDialogComponent } from "./components/bulk/bulk-enable-sm-dialog.component";
|
||||
@@ -749,11 +754,44 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
|
||||
}
|
||||
|
||||
async resetPassword(user: OrganizationUserView) {
|
||||
const changePasswordRefactorFlag = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM16117_ChangeExistingPasswordRefactor,
|
||||
);
|
||||
|
||||
if (changePasswordRefactorFlag) {
|
||||
if (!user || !user.email || !user.id) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: this.i18nService.t("errorOccurred"),
|
||||
message: this.i18nService.t("orgUserDetailsNotFound"),
|
||||
});
|
||||
this.logService.error("Org user details not found when attempting account recovery");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = AccountRecoveryDialogComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(user),
|
||||
email: user.email,
|
||||
organizationId: this.organization.id as OrganizationId,
|
||||
organizationUserId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
if (result === AccountRecoveryDialogResultType.Ok) {
|
||||
await this.load();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = ResetPasswordComponent.open(this.dialogService, {
|
||||
data: {
|
||||
name: this.userNamePipe.transform(user),
|
||||
email: user != null ? user.email : null,
|
||||
organizationId: this.organization.id,
|
||||
organizationId: this.organization.id as OrganizationId,
|
||||
id: user != null ? user.id : null,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
import {
|
||||
Argon2KdfConfig,
|
||||
@@ -96,7 +96,7 @@ export class OrganizationUserResetPasswordService
|
||||
newMasterPassword: string,
|
||||
email: string,
|
||||
orgUserId: string,
|
||||
orgId: string,
|
||||
orgId: OrganizationId,
|
||||
): Promise<void> {
|
||||
const response = await this.organizationUserApiService.getOrganizationUserResetPasswordDetails(
|
||||
orgId,
|
||||
|
||||
@@ -2225,6 +2225,9 @@
|
||||
"disable": {
|
||||
"message": "Turn off"
|
||||
},
|
||||
"orgUserDetailsNotFound": {
|
||||
"message": "Member details not found."
|
||||
},
|
||||
"revokeAccess": {
|
||||
"message": "Revoke access"
|
||||
},
|
||||
|
||||
@@ -261,7 +261,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
submit = async (): Promise<PasswordInputResult | undefined> => {
|
||||
try {
|
||||
this.isSubmitting.emit(true);
|
||||
|
||||
@@ -280,8 +280,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
const checkForBreaches = this.formGroup.controls.checkForBreaches?.value ?? true;
|
||||
|
||||
if (this.flow === InputPasswordFlow.ChangePasswordDelegation) {
|
||||
await this.handleChangePasswordDelegationFlow(newPassword);
|
||||
return;
|
||||
return await this.handleChangePasswordDelegationFlow(newPassword);
|
||||
}
|
||||
|
||||
if (!this.email) {
|
||||
@@ -388,6 +387,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
|
||||
// 5. Emit cryptographic keys and other password related properties
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
return passwordInputResult;
|
||||
} catch (e) {
|
||||
this.validationService.showError(e);
|
||||
} finally {
|
||||
@@ -441,7 +441,9 @@ export class InputPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleChangePasswordDelegationFlow(newPassword: string) {
|
||||
private async handleChangePasswordDelegationFlow(
|
||||
newPassword: string,
|
||||
): Promise<PasswordInputResult | undefined> {
|
||||
const newPasswordVerified = await this.verifyNewPassword(
|
||||
newPassword,
|
||||
this.passwordStrengthScore,
|
||||
@@ -456,6 +458,7 @@ export class InputPasswordComponent implements OnInit {
|
||||
};
|
||||
|
||||
this.onPasswordFormSubmit.emit(passwordInputResult);
|
||||
return passwordInputResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user