[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
This commit is contained in:
parent
cc91b79a15
commit
a22ef4d36c
|
@ -0,0 +1,38 @@
|
||||||
|
<div class="modal fade" role="dialog" aria-modal="true" attr.aria-label="{{ 'settings' | i18n }}">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<form
|
||||||
|
class="modal-content"
|
||||||
|
#form
|
||||||
|
[appApiAction]="formPromise"
|
||||||
|
(ngSubmit)="submit()"
|
||||||
|
[formGroup]="deleteForm"
|
||||||
|
>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="modal-text">{{ "deleteAccountDesc" | i18n }}</p>
|
||||||
|
<app-callout type="warning" title="{{ 'warning' | i18n }}">
|
||||||
|
{{ "deleteAccountWarning" | i18n }}
|
||||||
|
</app-callout>
|
||||||
|
<div class="box last">
|
||||||
|
<div class="box-header">{{ "deleteAccount" | i18n }}</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<app-user-verification
|
||||||
|
ngDefaultControl
|
||||||
|
formControlName="verification"
|
||||||
|
name="verification"
|
||||||
|
>
|
||||||
|
</app-user-verification>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="danger" [disabled]="form.loading || !secret">
|
||||||
|
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||||
|
<span [hidden]="form.loading">{{ "deleteAccount" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" data-dismiss="modal" [disabled]="form.loading">
|
||||||
|
{{ "cancel" | i18n }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -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<void>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,6 +108,14 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ "deleteAccount" | i18n }}</label>
|
||||||
|
<small class="help-block">
|
||||||
|
{{ "deleteAccountDesc" | i18n }}
|
||||||
|
<a (click)="openDeleteAccount()">{{ "deleteAccount" | i18n }}</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { isWindowsStore } from "@bitwarden/electron/utils";
|
||||||
|
|
||||||
import { SetPinComponent } from "../components/set-pin.component";
|
import { SetPinComponent } from "../components/set-pin.component";
|
||||||
|
|
||||||
|
import { DeleteAccountComponent } from "./delete-account.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-settings",
|
selector: "app-settings",
|
||||||
templateUrl: "settings.component.html",
|
templateUrl: "settings.component.html",
|
||||||
|
@ -437,4 +439,8 @@ export class SettingsComponent implements OnInit {
|
||||||
this.enableBrowserIntegrationFingerprint
|
this.enableBrowserIntegrationFingerprint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async openDeleteAccount() {
|
||||||
|
this.modalService.open(DeleteAccountComponent, { replaceTopModal: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||||
|
|
||||||
import { MenuUpdateRequest } from "../main/menu/menu.updater";
|
import { MenuUpdateRequest } from "../main/menu/menu.updater";
|
||||||
|
|
||||||
|
import { DeleteAccountComponent } from "./accounts/delete-account.component";
|
||||||
import { PremiumComponent } from "./accounts/premium.component";
|
import { PremiumComponent } from "./accounts/premium.component";
|
||||||
import { SettingsComponent } from "./accounts/settings.component";
|
import { SettingsComponent } from "./accounts/settings.component";
|
||||||
import { ExportComponent } from "./vault/export.component";
|
import { ExportComponent } from "./vault/export.component";
|
||||||
|
@ -153,9 +154,7 @@ export class AppComponent implements OnInit {
|
||||||
this.systemService.cancelProcessReload();
|
this.systemService.cancelProcessReload();
|
||||||
break;
|
break;
|
||||||
case "loggedOut":
|
case "loggedOut":
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
this.notificationsService.updateConnection();
|
this.notificationsService.updateConnection();
|
||||||
this.updateAppMenu();
|
this.updateAppMenu();
|
||||||
await this.systemService.clearPendingClipboard();
|
await this.systemService.clearPendingClipboard();
|
||||||
|
@ -180,9 +179,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "locked":
|
case "locked":
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
message.userId == null ||
|
message.userId == null ||
|
||||||
message.userId === (await this.stateService.getUserId())
|
message.userId === (await this.stateService.getUserId())
|
||||||
|
@ -223,6 +220,9 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "deleteAccount":
|
||||||
|
this.modalService.open(DeleteAccountComponent, { replaceTopModal: true });
|
||||||
|
break;
|
||||||
case "openPasswordHistory":
|
case "openPasswordHistory":
|
||||||
await this.openModal<PasswordGeneratorHistoryComponent>(
|
await this.openModal<PasswordGeneratorHistoryComponent>(
|
||||||
PasswordGeneratorHistoryComponent,
|
PasswordGeneratorHistoryComponent,
|
||||||
|
@ -368,9 +368,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async openExportVault() {
|
async openExportVault() {
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||||
ExportComponent,
|
ExportComponent,
|
||||||
|
@ -388,9 +386,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFolder() {
|
async addFolder() {
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const [modal, childComponent] = await this.modalService.openViewRef(
|
const [modal, childComponent] = await this.modalService.openViewRef(
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
|
@ -410,9 +406,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async openGenerator() {
|
async openGenerator() {
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
[this.modal] = await this.modalService.openViewRef(
|
[this.modal] = await this.modalService.openViewRef(
|
||||||
GeneratorComponent,
|
GeneratorComponent,
|
||||||
|
@ -542,9 +536,7 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openModal<T>(type: Type<T>, ref: ViewContainerRef) {
|
private async openModal<T>(type: Type<T>, ref: ViewContainerRef) {
|
||||||
if (this.modal != null) {
|
this.modalService.closeAll();
|
||||||
this.modal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
[this.modal] = await this.modalService.openViewRef(type, ref);
|
[this.modal] = await this.modalService.openViewRef(type, ref);
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ import localeZhTw from "@angular/common/locales/zh-Hant";
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { AccessibilityCookieComponent } from "./accounts/accessibility-cookie.component";
|
import { AccessibilityCookieComponent } from "./accounts/accessibility-cookie.component";
|
||||||
|
import { DeleteAccountComponent } from "./accounts/delete-account.component";
|
||||||
import { EnvironmentComponent } from "./accounts/environment.component";
|
import { EnvironmentComponent } from "./accounts/environment.component";
|
||||||
import { HintComponent } from "./accounts/hint.component";
|
import { HintComponent } from "./accounts/hint.component";
|
||||||
import { LockComponent } from "./accounts/lock.component";
|
import { LockComponent } from "./accounts/lock.component";
|
||||||
|
@ -165,6 +166,7 @@ registerLocaleData(localeZhTw, "zh-TW");
|
||||||
AttachmentsComponent,
|
AttachmentsComponent,
|
||||||
CiphersComponent,
|
CiphersComponent,
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
|
DeleteAccountComponent,
|
||||||
EnvironmentComponent,
|
EnvironmentComponent,
|
||||||
ExportComponent,
|
ExportComponent,
|
||||||
FolderAddEditComponent,
|
FolderAddEditComponent,
|
||||||
|
|
|
@ -1393,6 +1393,21 @@
|
||||||
"lockWithMasterPassOnRestart": {
|
"lockWithMasterPassOnRestart": {
|
||||||
"message": "Lock with master password on restart"
|
"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": {
|
"preferences": {
|
||||||
"message": "Preferences"
|
"message": "Preferences"
|
||||||
},
|
},
|
||||||
|
@ -1975,5 +1990,5 @@
|
||||||
},
|
},
|
||||||
"cardBrandMir": {
|
"cardBrandMir": {
|
||||||
"message": "Mir"
|
"message": "Mir"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ export class AccountMenu implements IMenubarMenu {
|
||||||
this.changeMasterPassword,
|
this.changeMasterPassword,
|
||||||
this.twoStepLogin,
|
this.twoStepLogin,
|
||||||
this.fingerprintPhrase,
|
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) {
|
private localize(s: string) {
|
||||||
return this._i18nService.t(s);
|
return this._i18nService.t(s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,10 @@
|
||||||
color: themed("mutedColor");
|
color: themed("mutedColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.last {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-content-row {
|
.box-content-row {
|
||||||
|
|
|
@ -336,6 +336,25 @@ form,
|
||||||
@include themify($themes) {
|
@include themify($themes) {
|
||||||
color: themed("mutedColor");
|
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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,10 +71,6 @@
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
&.last {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
(ngSubmit)="submit()"
|
(ngSubmit)="submit()"
|
||||||
[appApiAction]="formPromise"
|
[appApiAction]="formPromise"
|
||||||
ngNativeValidate
|
ngNativeValidate
|
||||||
|
[formGroup]="deleteForm"
|
||||||
>
|
>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 class="modal-title" id="deleteAccountTitle">{{ "deleteAccount" | i18n }}</h2>
|
<h2 class="modal-title" id="deleteAccountTitle">{{ "deleteAccount" | i18n }}</h2>
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>{{ "deleteAccountDesc" | i18n }}</p>
|
<p>{{ "deleteAccountDesc" | i18n }}</p>
|
||||||
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
|
<app-callout type="warning">{{ "deleteAccountWarning" | i18n }}</app-callout>
|
||||||
<app-user-verification [(ngModel)]="masterPassword" ngDefaultControl name="secret">
|
<app-user-verification ngDefaultControl formControlName="verification" name="verification">
|
||||||
</app-user-verification>
|
</app-user-verification>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
|
import { FormBuilder } from "@angular/forms";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { AccountService } from "@bitwarden/common/abstractions/account/account.service.abstraction";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
|
|
||||||
import { Verification } from "@bitwarden/common/types/verification";
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -13,30 +12,30 @@ import { Verification } from "@bitwarden/common/types/verification";
|
||||||
templateUrl: "delete-account.component.html",
|
templateUrl: "delete-account.component.html",
|
||||||
})
|
})
|
||||||
export class DeleteAccountComponent {
|
export class DeleteAccountComponent {
|
||||||
masterPassword: Verification;
|
formPromise: Promise<void>;
|
||||||
formPromise: Promise<any>;
|
|
||||||
|
deleteForm = this.formBuilder.group({
|
||||||
|
verification: undefined as Verification | undefined,
|
||||||
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private userVerificationService: UserVerificationService,
|
private formBuilder: FormBuilder,
|
||||||
private messagingService: MessagingService,
|
private accountService: AccountService,
|
||||||
private logService: LogService
|
private logService: LogService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
try {
|
try {
|
||||||
this.formPromise = this.userVerificationService
|
const verification = this.deleteForm.get("verification").value;
|
||||||
.buildRequest(this.masterPassword)
|
this.formPromise = this.accountService.delete(verification);
|
||||||
.then((request) => this.apiService.deleteAccount(request));
|
|
||||||
await this.formPromise;
|
await this.formPromise;
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"success",
|
"success",
|
||||||
this.i18nService.t("accountDeleted"),
|
this.i18nService.t("accountDeleted"),
|
||||||
this.i18nService.t("accountDeletedDesc")
|
this.i18nService.t("accountDeletedDesc")
|
||||||
);
|
);
|
||||||
this.messagingService.send("logout");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logService.error(e);
|
this.logService.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1088,7 +1088,7 @@
|
||||||
"message": "Account Deleted"
|
"message": "Account Deleted"
|
||||||
},
|
},
|
||||||
"accountDeletedDesc": {
|
"accountDeletedDesc": {
|
||||||
"message": "Your Bitwarden account and vault data were permanently deleted."
|
"message": "Your account has been closed and all associated data has been deleted."
|
||||||
},
|
},
|
||||||
"myAccount": {
|
"myAccount": {
|
||||||
"message": "My Account"
|
"message": "My Account"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { ControlValueAccessor, UntypedFormControl, NG_VALUE_ACCESSOR } from "@an
|
||||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
|
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
|
||||||
import { VerificationType } from "@bitwarden/common/enums/verificationType";
|
import { VerificationType } from "@bitwarden/common/enums/verificationType";
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { Verification } from "@bitwarden/common/types/verification";
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +91,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||||
|
|
||||||
this.onChange({
|
this.onChange({
|
||||||
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
||||||
secret: secret,
|
secret: Utils.isNullOrWhitespace(secret) ? null : secret,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
|
||||||
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
|
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||||
|
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||||
|
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/abstractions/account/account.service.abstraction";
|
||||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
||||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||||
|
@ -49,6 +51,8 @@ import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarde
|
||||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||||
import { Account } from "@bitwarden/common/models/domain/account";
|
import { Account } from "@bitwarden/common/models/domain/account";
|
||||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||||
|
import { AccountApiService } from "@bitwarden/common/services/account/account-api.service";
|
||||||
|
import { AccountService } from "@bitwarden/common/services/account/account.service";
|
||||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
|
@ -234,6 +238,21 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
|
||||||
useClass: FolderApiService,
|
useClass: FolderApiService,
|
||||||
deps: [FolderServiceAbstraction, ApiServiceAbstraction],
|
deps: [FolderServiceAbstraction, ApiServiceAbstraction],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AccountApiServiceAbstraction,
|
||||||
|
useClass: AccountApiService,
|
||||||
|
deps: [ApiServiceAbstraction],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AccountServiceAbstraction,
|
||||||
|
useClass: AccountService,
|
||||||
|
deps: [
|
||||||
|
AccountApiServiceAbstraction,
|
||||||
|
UserVerificationServiceAbstraction,
|
||||||
|
MessagingServiceAbstraction,
|
||||||
|
LogService,
|
||||||
|
],
|
||||||
|
},
|
||||||
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
|
{ provide: LogService, useFactory: () => new ConsoleLogService(false) },
|
||||||
{
|
{
|
||||||
provide: CollectionServiceAbstraction,
|
provide: CollectionServiceAbstraction,
|
||||||
|
|
|
@ -17,7 +17,8 @@ import { ModalRef } from "../components/modal/modal.ref";
|
||||||
|
|
||||||
export class ModalConfig<D = any> {
|
export class ModalConfig<D = any> {
|
||||||
data?: D;
|
data?: D;
|
||||||
allowMultipleModals = false;
|
allowMultipleModals?: boolean;
|
||||||
|
replaceTopModal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -63,13 +64,18 @@ export class ModalService {
|
||||||
return [modalRef, modalComponentRef.instance.componentRef.instance];
|
return [modalRef, modalComponentRef.instance.componentRef.instance];
|
||||||
}
|
}
|
||||||
|
|
||||||
open(componentType: Type<any>, config?: ModalConfig) {
|
open(componentType: Type<any>, config: ModalConfig = {}) {
|
||||||
if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) {
|
const { replaceTopModal = false, allowMultipleModals = false } = config;
|
||||||
|
|
||||||
|
if (this.modalCount > 0 && replaceTopModal) {
|
||||||
|
this.topModal.instance.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modalCount > 0 && !allowMultipleModals) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line
|
const [modalRef] = this.openInternal(componentType, config, true);
|
||||||
const [modalRef, _] = this.openInternal(componentType, config, true);
|
|
||||||
|
|
||||||
return modalRef;
|
return modalRef;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +95,10 @@ export class ModalService {
|
||||||
return this.componentFactoryResolver.resolveComponentFactory(componentType);
|
return this.componentFactoryResolver.resolveComponentFactory(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeAll(): void {
|
||||||
|
this.modalList.forEach((modal) => modal.instance.close());
|
||||||
|
}
|
||||||
|
|
||||||
protected openInternal(
|
protected openInternal(
|
||||||
componentType: Type<any>,
|
componentType: Type<any>,
|
||||||
config?: ModalConfig,
|
config?: ModalConfig,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
||||||
|
|
||||||
|
export abstract class AccountApiService {
|
||||||
|
abstract deleteAccount(request: SecretVerificationRequest): Promise<void>;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Verification } from "../../types/verification";
|
||||||
|
|
||||||
|
export abstract class AccountService {
|
||||||
|
abstract delete(verification: Verification): Promise<any>;
|
||||||
|
}
|
|
@ -209,7 +209,6 @@ export abstract class ApiService {
|
||||||
setPassword: (request: SetPasswordRequest) => Promise<any>;
|
setPassword: (request: SetPasswordRequest) => Promise<any>;
|
||||||
postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise<any>;
|
postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise<any>;
|
||||||
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
|
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
|
||||||
deleteAccount: (request: SecretVerificationRequest) => Promise<any>;
|
|
||||||
getAccountRevisionDate: () => Promise<number>;
|
getAccountRevisionDate: () => Promise<number>;
|
||||||
postPasswordHint: (request: PasswordHintRequest) => Promise<any>;
|
postPasswordHint: (request: PasswordHintRequest) => Promise<any>;
|
||||||
postRegister: (request: RegisterRequest) => Promise<any>;
|
postRegister: (request: RegisterRequest) => Promise<any>;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||||
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
|
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
||||||
|
|
||||||
|
export class AccountApiService implements AccountApiServiceAbstraction {
|
||||||
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
|
deleteAccount(request: SecretVerificationRequest): Promise<void> {
|
||||||
|
return this.apiService.send("DELETE", "/accounts", request, true, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||||
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
|
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
|
||||||
|
|
||||||
|
import { AccountService as AccountServiceAbstraction } from "../../abstractions/account/account.service.abstraction";
|
||||||
|
import { Verification } from "../../types/verification";
|
||||||
|
|
||||||
|
export class AccountService implements AccountServiceAbstraction {
|
||||||
|
constructor(
|
||||||
|
private accountApiService: AccountApiService,
|
||||||
|
private userVerificationService: UserVerificationService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private logService: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async delete(verification: Verification): Promise<any> {
|
||||||
|
try {
|
||||||
|
const verificationRequest = await this.userVerificationService.buildRequest(verification);
|
||||||
|
await this.accountApiService.deleteAccount(verificationRequest);
|
||||||
|
this.messagingService.send("logout");
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -350,10 +350,6 @@ export class ApiService implements ApiServiceAbstraction {
|
||||||
return this.send("POST", "/accounts/security-stamp", request, true, false);
|
return this.send("POST", "/accounts/security-stamp", request, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteAccount(request: SecretVerificationRequest): Promise<any> {
|
|
||||||
return this.send("DELETE", "/accounts", request, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAccountRevisionDate(): Promise<number> {
|
async getAccountRevisionDate(): Promise<number> {
|
||||||
const r = await this.send("GET", "/accounts/revision-date", null, true, true);
|
const r = await this.send("GET", "/accounts/revision-date", null, true, true);
|
||||||
return r as number;
|
return r as number;
|
||||||
|
|
Loading…
Reference in New Issue