1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-07 20:24:01 +00:00

[PM-2014] feat: add delete dialog

This commit is contained in:
Andreas Coroiu
2023-05-10 14:32:46 +02:00
parent 355a52f43f
commit be0aaf8334
7 changed files with 123 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
import { Injectable, Optional } from "@angular/core";
import { BehaviorSubject, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap, tap } from "rxjs";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -105,6 +105,15 @@ export class WebauthnService {
}
}
getCredential$(credentialId: string): Observable<WebauthnCredentialView> {
return this.credentials$.pipe(
map(
(credentials) => credentials.find((c) => c.id === credentialId),
filter((c) => c !== undefined)
)
);
}
private getCredentials$(): Observable<WebauthnCredentialView[]> {
return from(this.apiService.getCredentials()).pipe(map((response) => response.data));
}

View File

@@ -0,0 +1,34 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle
>{{ "removePasskey" | i18n }}
<span *ngIf="credential" class="tw-text-sm tw-normal-case tw-text-muted">{{
credential.name
}}</span>
</span>
<ng-container bitDialogContent>
<ng-container *ngIf="!credential">
<i class="bwi bwi-spinner bwi-spin tw-ml-1" aria-hidden="true"></i>
</ng-container>
<ng-container *ngIf="credential">
<p bitTypography="body1">{{ "removePasskeyInfo" | i18n }}</p>
<bit-form-field disableMargin>
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<input type="password" bitInput formControlName="masterPassword" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
</ng-container>
</ng-container>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "remove" | i18n }}
</button>
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -0,0 +1,60 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
import { WebauthnService } from "../../../core";
import { WebauthnCredentialView } from "../../../core/views/webauth-credential.view";
export interface DeleteCredentialDialogParams {
credentialId: string;
}
@Component({
templateUrl: "delete-credential-dialog.component.html",
})
export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected formGroup = this.formBuilder.group({
masterPassword: ["", [Validators.required]],
});
protected credential?: WebauthnCredentialView;
constructor(
@Inject(DIALOG_DATA) private params: DeleteCredentialDialogParams,
private formBuilder: FormBuilder,
private dialogRef: DialogRef,
private webauthnService: WebauthnService
) {}
ngOnInit(): void {
this.webauthnService
.getCredential$(this.params.credentialId)
.pipe(takeUntil(this.destroy$))
.subscribe((credential) => (this.credential = credential));
}
submit = async () => {
// empty
};
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
/**
* Strongly typed helper to open a DeleteCredentialDialogComponent
* @param dialogService Instance of the dialog service that will be used to open the dialog
* @param config Configuration for the dialog
*/
export const openDeleteCredentialDialogComponent = (
dialogService: DialogServiceAbstraction,
config: DialogConfig<DeleteCredentialDialogParams>
) => {
return dialogService.open<unknown>(DeleteCredentialDialogComponent, config);
};

View File

@@ -29,6 +29,7 @@
bitLink
[disabled]="loading"
[attr.aria-label]="('remove' | i18n) + ' ' + credential.name"
(click)="deleteCredential(credential.id)"
>
{{ "remove" | i18n }}
</button>

View File

@@ -7,6 +7,7 @@ import { WebauthnService } from "../../core";
import { WebauthnCredentialView } from "../../core/views/webauth-credential.view";
import { openCreateCredentialDialog } from "./create-credential-dialog/create-credential-dialog.component";
import { openDeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
@Component({
selector: "app-fido2-login-settings",
@@ -54,7 +55,11 @@ export class Fido2LoginSettingsComponent implements OnInit, OnDestroy {
this.destroy$.complete();
}
protected async createCredential() {
protected createCredential() {
openCreateCredentialDialog(this.dialogService, {});
}
protected deleteCredential(credentialId: string) {
openDeleteCredentialDialogComponent(this.dialogService, { data: { credentialId } });
}
}

View File

@@ -4,11 +4,16 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { SharedModule } from "../../../shared/shared.module";
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
import { Fido2LoginSettingsComponent } from "./fido2-login-settings.component";
@NgModule({
imports: [SharedModule, FormsModule, ReactiveFormsModule],
declarations: [Fido2LoginSettingsComponent, CreateCredentialDialogComponent],
declarations: [
Fido2LoginSettingsComponent,
CreateCredentialDialogComponent,
DeleteCredentialDialogComponent,
],
exports: [Fido2LoginSettingsComponent],
})
export class Fido2LoginSettingsModule {}

View File

@@ -673,6 +673,12 @@
}
}
},
"removePasskey": {
"message": "Remove passkey"
},
"removePasskeyInfo": {
"message": "If all passkeys are removed, you will be unable to log into new devices without your master password."
},
"tryAgain": {
"message": "Try again"
},