From c09b446e63210f68bcbfd91f5e2f3b5c4dedd609 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Fri, 8 Mar 2024 09:15:07 -0500 Subject: [PATCH] [PM-5054] migrate emergency access to CL (#7850) Co-authored-by: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> --- .../emergency-access-confirm.component.html | 87 ++- .../emergency-access-confirm.component.ts | 53 +- .../emergency-access-add-edit.component.html | 192 ++----- .../emergency-access-add-edit.component.ts | 98 +++- .../emergency-access.component.html | 506 +++++++++--------- .../emergency-access.component.ts | 139 +++-- .../emergency-access-takeover.component.html | 129 ++--- .../emergency-access-takeover.component.ts | 62 ++- .../view/emergency-access-view.component.html | 77 +-- apps/web/src/locales/en/messages.json | 3 + 10 files changed, 671 insertions(+), 675 deletions(-) diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html index 7762067784..a8a4bd53d9 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.html @@ -1,52 +1,37 @@ - + {{ "learnMore" | i18n }} +

+

+ {{ fingerprint }} +

+ + + + {{ "dontAskFingerprintAgain" | i18n }} + + +
+ + +
+ + diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index 711a7ba9a5..4afc60c9be 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -1,39 +1,52 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, OnInit, Inject } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { DialogService } from "@bitwarden/components"; +export enum EmergencyAccessConfirmDialogResult { + Confirmed = "confirmed", +} +type EmergencyAccessConfirmDialogData = { + /** display name of the account requesting emergency access */ + name: string; + /** identifies the account requesting emergency access */ + userId: string; + /** traces a unique emergency request */ + emergencyAccessId: string; +}; @Component({ selector: "emergency-access-confirm", templateUrl: "emergency-access-confirm.component.html", }) export class EmergencyAccessConfirmComponent implements OnInit { - @Input() name: string; - @Input() userId: string; - @Input() emergencyAccessId: string; - @Input() formPromise: Promise; - @Output() onConfirmed = new EventEmitter(); - - dontAskAgain = false; loading = true; fingerprint: string; + confirmForm = this.formBuilder.group({ + dontAskAgain: [false], + }); constructor( + @Inject(DIALOG_DATA) protected params: EmergencyAccessConfirmDialogData, + private formBuilder: FormBuilder, private apiService: ApiService, private cryptoService: CryptoService, private stateService: StateService, private logService: LogService, + private dialogRef: DialogRef, ) {} async ngOnInit() { try { - const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId); + const publicKeyResponse = await this.apiService.getUserPublicKey(this.params.userId); if (publicKeyResponse != null) { const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey); + const fingerprint = await this.cryptoService.getFingerprint(this.params.userId, publicKey); if (fingerprint != null) { this.fingerprint = fingerprint.join("-"); } @@ -44,19 +57,33 @@ export class EmergencyAccessConfirmComponent implements OnInit { this.loading = false; } - async submit() { + submit = async () => { if (this.loading) { return; } - if (this.dontAskAgain) { + if (this.confirmForm.get("dontAskAgain").value) { await this.stateService.setAutoConfirmFingerprints(true); } try { - this.onConfirmed.emit(); + this.dialogRef.close(EmergencyAccessConfirmDialogResult.Confirmed); } catch (e) { this.logService.error(e); } + }; + /** + * Strongly typed helper to open a EmergencyAccessConfirmComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open( + dialogService: DialogService, + config: DialogConfig, + ) { + return dialogService.open( + EmergencyAccessConfirmComponent, + config, + ); } } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html index 43961d6a13..1e61585e42 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html @@ -1,142 +1,68 @@ - + + + {{ "view" | i18n }} + {{ "viewDesc" | i18n }} + + + + {{ "takeover" | i18n }} + {{ "takeoverDesc" | i18n }} + + + + + {{ "waitTime" | i18n }} + + + + {{ "waitTimeDesc" | i18n }} + + + + + + + + + diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index 4aec390a56..d99c693e73 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -1,45 +1,59 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; import { EmergencyAccessService } from "../../emergency-access"; import { EmergencyAccessType } from "../../emergency-access/enums/emergency-access-type"; +export type EmergencyAccessAddEditDialogData = { + /** display name of the account requesting emergency access */ + name: string; + /** traces a unique emergency request */ + emergencyAccessId: string; + /** A boolean indicating whether the emergency access request is in read-only mode (true for view-only, false for editing). */ + readOnly: boolean; +}; + +export enum EmergencyAccessAddEditDialogResult { + Saved = "saved", + Canceled = "canceled", + Deleted = "deleted", +} @Component({ selector: "emergency-access-add-edit", templateUrl: "emergency-access-add-edit.component.html", }) export class EmergencyAccessAddEditComponent implements OnInit { - @Input() name: string; - @Input() emergencyAccessId: string; - @Output() onSaved = new EventEmitter(); - @Output() onDeleted = new EventEmitter(); - loading = true; readOnly = false; editMode = false; title: string; - email: string; type: EmergencyAccessType = EmergencyAccessType.View; - formPromise: Promise; - emergencyAccessType = EmergencyAccessType; waitTimes: { name: string; value: number }[]; - waitTime: number; + addEditForm = this.formBuilder.group({ + email: ["", [Validators.email, Validators.required]], + emergencyAccessType: [this.emergencyAccessType.View], + waitTime: [{ value: null, disabled: this.readOnly }, [Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected params: EmergencyAccessAddEditDialogData, + private formBuilder: FormBuilder, private emergencyAccessService: EmergencyAccessService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private logService: LogService, + private dialogRef: DialogRef, ) {} - async ngOnInit() { - this.editMode = this.loading = this.emergencyAccessId != null; - + this.editMode = this.loading = this.params.emergencyAccessId != null; this.waitTimes = [ { name: this.i18nService.t("oneDay"), value: 1 }, { name: this.i18nService.t("days", "2"), value: 2 }, @@ -50,46 +64,72 @@ export class EmergencyAccessAddEditComponent implements OnInit { ]; if (this.editMode) { - this.editMode = true; this.title = this.i18nService.t("editEmergencyContact"); try { const emergencyAccess = await this.emergencyAccessService.getEmergencyAccess( - this.emergencyAccessId, + this.params.emergencyAccessId, ); - this.type = emergencyAccess.type; - this.waitTime = emergencyAccess.waitTimeDays; + this.addEditForm.patchValue({ + email: emergencyAccess.email, + waitTime: emergencyAccess.waitTimeDays, + emergencyAccessType: emergencyAccess.type, + }); } catch (e) { this.logService.error(e); } } else { this.title = this.i18nService.t("inviteEmergencyContact"); - this.waitTime = this.waitTimes[2].value; + this.addEditForm.patchValue({ waitTime: this.waitTimes[2].value }); } this.loading = false; } - async submit() { + submit = async () => { + if (this.addEditForm.invalid) { + this.addEditForm.markAllAsTouched(); + return; + } try { if (this.editMode) { - await this.emergencyAccessService.update(this.emergencyAccessId, this.type, this.waitTime); + await this.emergencyAccessService.update( + this.params.emergencyAccessId, + this.addEditForm.value.emergencyAccessType, + this.addEditForm.value.waitTime, + ); } else { - await this.emergencyAccessService.invite(this.email, this.type, this.waitTime); + await this.emergencyAccessService.invite( + this.addEditForm.value.email, + this.addEditForm.value.emergencyAccessType, + this.addEditForm.value.waitTime, + ); } - - await this.formPromise; this.platformUtilsService.showToast( "success", null, - this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name), + this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.params.name), ); - this.onSaved.emit(); + this.dialogRef.close(EmergencyAccessAddEditDialogResult.Saved); } catch (e) { this.logService.error(e); } - } + }; - async delete() { - this.onDeleted.emit(); - } + delete = async () => { + this.dialogRef.close(EmergencyAccessAddEditDialogResult.Deleted); + }; + /** + * Strongly typed helper to open a EmergencyAccessAddEditComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open = ( + dialogService: DialogService, + config: DialogConfig, + ) => { + return dialogService.open( + EmergencyAccessAddEditComponent, + config, + ); + }; } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index 27bfc673ec..41c62db609 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -1,264 +1,276 @@ - -

- {{ "emergencyAccessDesc" | i18n }} - - {{ "learnMore" | i18n }}. - -

- -

- {{ "warning" | i18n }}: {{ "emergencyAccessOwnerWarning" | i18n }} -

- -