diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html index cd8cbf85b7b..42bacf28d8d 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html @@ -9,6 +9,13 @@ "emergencyAccessLoggedOutWarning" | i18n: dialogData.grantorName }} + + diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts index 25dedde4a24..08b38b1b783 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts @@ -1,18 +1,33 @@ import { CommonModule } from "@angular/common"; -import { Component, Inject } from "@angular/core"; +import { Component, Inject, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; -import { ChangePasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular"; +import { + ChangePasswordComponent, + InputPasswordComponent, + InputPasswordFlow, + PasswordInputResult, +} from "@bitwarden/auth/angular"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +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 { ButtonModule, CalloutModule, DIALOG_DATA, DialogConfig, DialogModule, + DialogRef, DialogService, FormFieldModule, + ToastService, } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { EmergencyAccessService } from "../../../emergency-access"; + type EmergencyAccessTakeoverDialogData = { grantorName: string; grantorEmail: string; @@ -43,12 +58,58 @@ export enum EmergencyAccessTakeoverDialogResultType { DialogModule, FormFieldModule, I18nPipe, + InputPasswordComponent, ], }) -export class EmergencyAccessTakeoverDialogComponent { +export class EmergencyAccessTakeoverDialogComponent implements OnInit { inputPasswordFlow = InputPasswordFlow.ChangePasswordDelegation; - constructor(@Inject(DIALOG_DATA) protected dialogData: EmergencyAccessTakeoverDialogData) {} + initializing = true; + masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; + + constructor( + @Inject(DIALOG_DATA) protected dialogData: EmergencyAccessTakeoverDialogData, + private accountService: AccountService, + private dialogRef: DialogRef, + private emergencyAccessService: EmergencyAccessService, + private i18nService: I18nService, + private logService: LogService, + private policyService: PolicyService, + private toastService: ToastService, + ) {} + + async ngOnInit() { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const userId = activeAccount?.id; + + const grantorPolicies = await this.emergencyAccessService.getGrantorPolicies( + this.dialogData.emergencyAccessId, + ); + + this.masterPasswordPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$(userId, grantorPolicies), + ); + } + + async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { + try { + await this.emergencyAccessService.takeover( + this.dialogData.emergencyAccessId, + passwordInputResult.newPassword, + this.dialogData.grantorEmail, + ); + } catch (e) { + this.logService.error(e); + + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("unexpectedError"), + }); + } + + this.dialogRef.close(EmergencyAccessTakeoverDialogResultType.Done); + } /** * Strongly typed helper to open a EmergencyAccessTakeoverDialogComponent diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index a23a4c67199..e73cbf677f0 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -108,8 +108,8 @@ export class InputPasswordComponent implements OnInit { @Output() onSecondaryButtonClick = new EventEmitter(); @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; @@ -247,7 +247,7 @@ export class InputPasswordComponent implements OnInit { } protected submit = async () => { - this.verifyFlowAndUserId(); + this.verifyFlow(); this.formGroup.markAllAsTouched(); @@ -375,13 +375,16 @@ export class InputPasswordComponent implements OnInit { * 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() { + private verifyFlow() { /** * 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.flow === InputPasswordFlow.AccountRegistration || + this.flow === InputPasswordFlow.ChangePasswordDelegation + ) { 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.", @@ -395,11 +398,30 @@ export class InputPasswordComponent implements OnInit { * (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.flow !== InputPasswordFlow.AccountRegistration && + this.flow !== InputPasswordFlow.ChangePasswordDelegation + ) { if (!this.userId) { throw new Error("The selected InputPasswordFlow requires that a userId be passed down"); } } + + if (this.flow === InputPasswordFlow.ChangePasswordDelegation) { + if (this.email) { + throw new Error( + "There should be no email in a ChangePasswordDelegation flow. Please pass down the appropriate InputPasswordFlow from the parent component.", + ); + } + } + + if (this.flow !== InputPasswordFlow.ChangePasswordDelegation) { + if (!this.email) { + throw new Error( + "The selected InputPasswordFlow requires that an email be passed down to create a master key.", + ); + } + } } /**