From a22ef4d36c75229a0e3df66ad359514ffa55d7e5 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 29 Jul 2022 21:49:58 +0200 Subject: [PATCH] [EC-317] Desktop client delete user account (#3151) * [EC-317] feat: add delete account section in settings * [EC-317] feat: add new delete account modal * [EC-317] feat: add ability to replace top-most modal * [EC-317] chore: remove unecessary lint ignore * [EC-317] fix: so delete account is closed if export vault is opened * [EC-317] feat: inital delete account design without i18n * [EC-317] feat: disabled but basic working delete functionality * [EC-317] feat: implement according to new design * [EC-317] feat: use translations * [EC-317] feat: implement working deletion * [EC-317] feat: add loading state and error messages * [EC-317] feat: add menu bar item * [EC-317] feat: update form to support typed reactive forms * [EC-317] chore: update translation text after design review * [EC-317] feat: move deletion logic to service * [EC-317] refactor: update web deletion * [EC-317] feat: disable submit if secret is empty * [EC-317] fix: handle errors in components as well * [EC-317] fix: use abstraction as interface * [EC-317] refactor: extract deleteAccount from api service * [EC-317] fix: typo in translations * [EC-317] chore: rename to accountApiService --- .../accounts/delete-account.component.html | 38 +++++++++++++++ .../app/accounts/delete-account.component.ts | 48 +++++++++++++++++++ .../src/app/accounts/settings.component.html | 8 ++++ .../src/app/accounts/settings.component.ts | 6 +++ apps/desktop/src/app/app.component.ts | 28 ++++------- apps/desktop/src/app/app.module.ts | 2 + apps/desktop/src/locales/en/messages.json | 17 ++++++- apps/desktop/src/main/menu/menu.account.ts | 15 ++++++ apps/desktop/src/scss/box.scss | 4 ++ apps/desktop/src/scss/misc.scss | 19 ++++++++ apps/desktop/src/scss/pages.scss | 4 -- .../settings/delete-account.component.html | 3 +- .../app/settings/delete-account.component.ts | 23 +++++---- apps/web/src/locales/en/messages.json | 2 +- .../components/user-verification.component.ts | 3 +- .../src/services/jslib-services.module.ts | 19 ++++++++ libs/angular/src/services/modal.service.ts | 20 ++++++-- .../account-api.service.abstraction.ts | 5 ++ .../account/account.service.abstraction.ts | 5 ++ libs/common/src/abstractions/api.service.ts | 1 - .../services/account/account-api.service.ts | 11 +++++ .../src/services/account/account.service.ts | 27 +++++++++++ libs/common/src/services/api.service.ts | 4 -- 23 files changed, 264 insertions(+), 48 deletions(-) create mode 100644 apps/desktop/src/app/accounts/delete-account.component.html create mode 100644 apps/desktop/src/app/accounts/delete-account.component.ts create mode 100644 libs/common/src/abstractions/account/account-api.service.abstraction.ts create mode 100644 libs/common/src/abstractions/account/account.service.abstraction.ts create mode 100644 libs/common/src/services/account/account-api.service.ts create mode 100644 libs/common/src/services/account/account.service.ts diff --git a/apps/desktop/src/app/accounts/delete-account.component.html b/apps/desktop/src/app/accounts/delete-account.component.html new file mode 100644 index 0000000000..1371cee162 --- /dev/null +++ b/apps/desktop/src/app/accounts/delete-account.component.html @@ -0,0 +1,38 @@ + diff --git a/apps/desktop/src/app/accounts/delete-account.component.ts b/apps/desktop/src/app/accounts/delete-account.component.ts new file mode 100644 index 0000000000..c708ba5741 --- /dev/null +++ b/apps/desktop/src/app/accounts/delete-account.component.ts @@ -0,0 +1,48 @@ +import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { AccountService } from "@bitwarden/common/abstractions/account/account.service.abstraction"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; + +import { Verification } from "../../../../../libs/common/src/types/verification"; + +@Component({ + selector: "app-delete-account", + templateUrl: "delete-account.component.html", +}) +export class DeleteAccountComponent { + formPromise: Promise; + + deleteForm = this.formBuilder.group({ + verification: undefined as Verification | undefined, + }); + + constructor( + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private formBuilder: FormBuilder, + private accountService: AccountService, + private logService: LogService + ) {} + + get secret() { + return this.deleteForm.get("verification")?.value?.secret; + } + + async submit() { + try { + const verification = this.deleteForm.get("verification").value; + this.formPromise = this.accountService.delete(verification); + await this.formPromise; + this.platformUtilsService.showToast( + "success", + this.i18nService.t("accountDeleted"), + this.i18nService.t("accountDeletedDesc") + ); + } catch (e) { + this.logService.error(e); + } + } +} diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index e9b509e6be..f775eb9bb4 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -108,6 +108,14 @@ + +
+ + + {{ "deleteAccountDesc" | i18n }} + {{ "deleteAccount" | i18n }} + +
diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 6d5f46fe03..94f49c07f8 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -18,6 +18,8 @@ import { isWindowsStore } from "@bitwarden/electron/utils"; import { SetPinComponent } from "../components/set-pin.component"; +import { DeleteAccountComponent } from "./delete-account.component"; + @Component({ selector: "app-settings", templateUrl: "settings.component.html", @@ -437,4 +439,8 @@ export class SettingsComponent implements OnInit { this.enableBrowserIntegrationFingerprint ); } + + async openDeleteAccount() { + this.modalService.open(DeleteAccountComponent, { replaceTopModal: true }); + } } diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 41d1a62601..ebcc86847a 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -40,6 +40,7 @@ import { CipherType } from "@bitwarden/common/enums/cipherType"; import { MenuUpdateRequest } from "../main/menu/menu.updater"; +import { DeleteAccountComponent } from "./accounts/delete-account.component"; import { PremiumComponent } from "./accounts/premium.component"; import { SettingsComponent } from "./accounts/settings.component"; import { ExportComponent } from "./vault/export.component"; @@ -153,9 +154,7 @@ export class AppComponent implements OnInit { this.systemService.cancelProcessReload(); break; case "loggedOut": - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); this.notificationsService.updateConnection(); this.updateAppMenu(); await this.systemService.clearPendingClipboard(); @@ -180,9 +179,7 @@ export class AppComponent implements OnInit { } break; case "locked": - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); if ( message.userId == null || message.userId === (await this.stateService.getUserId()) @@ -223,6 +220,9 @@ export class AppComponent implements OnInit { } break; } + case "deleteAccount": + this.modalService.open(DeleteAccountComponent, { replaceTopModal: true }); + break; case "openPasswordHistory": await this.openModal( PasswordGeneratorHistoryComponent, @@ -368,9 +368,7 @@ export class AppComponent implements OnInit { } async openExportVault() { - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); const [modal, childComponent] = await this.modalService.openViewRef( ExportComponent, @@ -388,9 +386,7 @@ export class AppComponent implements OnInit { } async addFolder() { - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); const [modal, childComponent] = await this.modalService.openViewRef( FolderAddEditComponent, @@ -410,9 +406,7 @@ export class AppComponent implements OnInit { } async openGenerator() { - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); [this.modal] = await this.modalService.openViewRef( GeneratorComponent, @@ -542,9 +536,7 @@ export class AppComponent implements OnInit { } private async openModal(type: Type, ref: ViewContainerRef) { - if (this.modal != null) { - this.modal.close(); - } + this.modalService.closeAll(); [this.modal] = await this.modalService.openViewRef(type, ref); diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 23e44d85a2..a7e46b711a 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -57,6 +57,7 @@ import localeZhTw from "@angular/common/locales/zh-Hant"; import { NgModule } from "@angular/core"; import { AccessibilityCookieComponent } from "./accounts/accessibility-cookie.component"; +import { DeleteAccountComponent } from "./accounts/delete-account.component"; import { EnvironmentComponent } from "./accounts/environment.component"; import { HintComponent } from "./accounts/hint.component"; import { LockComponent } from "./accounts/lock.component"; @@ -165,6 +166,7 @@ registerLocaleData(localeZhTw, "zh-TW"); AttachmentsComponent, CiphersComponent, CollectionsComponent, + DeleteAccountComponent, EnvironmentComponent, ExportComponent, FolderAddEditComponent, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 53fcb31879..170ddb7214 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1393,6 +1393,21 @@ "lockWithMasterPassOnRestart": { "message": "Lock with master password on restart" }, + "deleteAccount": { + "message": "Delete account" + }, + "deleteAccountDesc": { + "message": "Proceed below to delete your account and all vault data." + }, + "deleteAccountWarning": { + "message": "Deleting your account is permanent. It cannot be undone." + }, + "accountDeleted": { + "message": "Account deleted" + }, + "accountDeletedDesc": { + "message": "Your account has been closed and all associated data has been deleted." + }, "preferences": { "message": "Preferences" }, @@ -1975,5 +1990,5 @@ }, "cardBrandMir": { "message": "Mir" - } + } } diff --git a/apps/desktop/src/main/menu/menu.account.ts b/apps/desktop/src/main/menu/menu.account.ts index ef85f7462f..b7c4b1f693 100644 --- a/apps/desktop/src/main/menu/menu.account.ts +++ b/apps/desktop/src/main/menu/menu.account.ts @@ -19,6 +19,8 @@ export class AccountMenu implements IMenubarMenu { this.changeMasterPassword, this.twoStepLogin, this.fingerprintPhrase, + this.separator, + this.deleteAccount, ]; } @@ -105,6 +107,19 @@ export class AccountMenu implements IMenubarMenu { }; } + private get deleteAccount(): MenuItemConstructorOptions { + return { + label: this.localize("deleteAccount"), + id: "deleteAccount", + click: () => this.sendMessage("deleteAccount"), + enabled: !this._isLocked, + }; + } + + private get separator(): MenuItemConstructorOptions { + return { type: "separator" }; + } + private localize(s: string) { return this._i18nService.t(s); } diff --git a/apps/desktop/src/scss/box.scss b/apps/desktop/src/scss/box.scss index 4f05b873b6..aa11b408a4 100644 --- a/apps/desktop/src/scss/box.scss +++ b/apps/desktop/src/scss/box.scss @@ -102,6 +102,10 @@ color: themed("mutedColor"); } } + + &.last { + margin-bottom: 15px; + } } .box-content-row { diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index 7982129316..5e5e341b30 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -336,6 +336,25 @@ form, @include themify($themes) { color: themed("mutedColor"); } + + a { + @extend .btn; + @extend .link; + + padding: 0; + font-size: inherit; + font-weight: bold; + + @include themify($themes) { + color: themed("mutedColor"); + } + + &:hover { + @include themify($themes) { + color: darken(themed("mutedColor"), 6%); + } + } + } } } diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss index c3626c40c8..6c839826ea 100644 --- a/apps/desktop/src/scss/pages.scss +++ b/apps/desktop/src/scss/pages.scss @@ -71,10 +71,6 @@ .box { margin-bottom: 20px; - - &.last { - margin-bottom: 15px; - } } .buttons { diff --git a/apps/web/src/app/settings/delete-account.component.html b/apps/web/src/app/settings/delete-account.component.html index 6d53b4170f..77b9df007b 100644 --- a/apps/web/src/app/settings/delete-account.component.html +++ b/apps/web/src/app/settings/delete-account.component.html @@ -6,6 +6,7 @@ (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate + [formGroup]="deleteForm" >