mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-2169] Update Password Reprompt Modal to use Component Library (#5720)
* update password reprompt to use the dialog CL * Override showPasswordPrompt and submit method on web child classes from base classes to allow dialog work on web and modal on other clients * Override showPasswordPrompt and submit method on web child classes from base classes to allow dialog work on web and modal on other clients * Fixed lint issues * Corrected comments * Refactored passwored reprompt to use dialog service after changes to make the dialog service work on the desktop and browser * Changed access modifier from protected to protected * Refactored passwprd reprompt component to a stand alone component and fixed all references * fix merge changes * fix import aliases in password-reprompt.component.ts * fix alias typo in browser tsconfig * import from root vault alias * revert tsconfig changes * remove service abstraction and update imports * remove component from imports * Removed unneccesary show password toggle * renamed selector to use vault prefix * removed unnecessary data dismiss * merged and fixed conflicts * remove reintroduced file * Added appAutoFocus to reprompt dialog * delayed validation until submit happens --------- Co-authored-by: William Martin <contact@willmartian.com>
This commit is contained in:
@@ -136,7 +136,6 @@ import {
|
||||
FolderService as FolderServiceAbstraction,
|
||||
InternalFolderService,
|
||||
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction";
|
||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
@@ -156,13 +155,13 @@ import {
|
||||
ImportService,
|
||||
ImportServiceAbstraction,
|
||||
} from "@bitwarden/importer";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { UnauthGuard } from "../auth/guards/unauth.guard";
|
||||
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
||||
import { BroadcasterService } from "../platform/services/broadcaster.service";
|
||||
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
||||
import { PasswordRepromptService } from "../vault/services/password-reprompt.service";
|
||||
|
||||
import {
|
||||
LOCALES_DIRECTORY,
|
||||
@@ -186,6 +185,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
AuthGuard,
|
||||
UnauthGuard,
|
||||
ModalService,
|
||||
PasswordRepromptService,
|
||||
|
||||
{ provide: WINDOW, useValue: window },
|
||||
{
|
||||
provide: LOCALE_ID,
|
||||
@@ -603,7 +604,6 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
UserVerificationApiServiceAbstraction,
|
||||
],
|
||||
},
|
||||
{ provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService },
|
||||
{
|
||||
provide: OrganizationServiceAbstraction,
|
||||
useClass: OrganizationService,
|
||||
|
||||
@@ -21,7 +21,6 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
@@ -34,6 +33,7 @@ import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
@Directive()
|
||||
export class AddEditComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { ModalRef } from "../../components/modal/modal.ref";
|
||||
|
||||
/**
|
||||
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
|
||||
* See UserVerificationComponent for any other situation where you need to verify the user's identity.
|
||||
*/
|
||||
@Directive()
|
||||
export class PasswordRepromptComponent {
|
||||
showPassword = false;
|
||||
masterPassword = "";
|
||||
|
||||
constructor(
|
||||
private modalRef: ModalRef,
|
||||
private cryptoService: CryptoService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
togglePassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
const storedMasterKey = await this.cryptoService.getOrDeriveMasterKey(this.masterPassword);
|
||||
if (!(await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, storedMasterKey))) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalRef.close(true);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
||||
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { PasswordRepromptService } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||
import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view";
|
||||
@@ -35,6 +34,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
const BroadcasterSubscriptionId = "ViewComponent";
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export abstract class PasswordRepromptService {
|
||||
protectedFields: () => string[];
|
||||
showPasswordPrompt: () => Promise<boolean>;
|
||||
enabled: () => Promise<boolean>;
|
||||
}
|
||||
31
libs/vault/src/components/password-reprompt.component.html
Normal file
31
libs/vault/src/components/password-reprompt.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>
|
||||
{{ "passwordConfirmation" | i18n }}
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
{{ "passwordConfirmationDesc" | i18n }}
|
||||
|
||||
<bit-form-field disableMargin class="tw-mt-6">
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
appAutofocus
|
||||
id="masterPassword"
|
||||
type="password"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" bitFormButton type="submit">
|
||||
<span>{{ "ok" | i18n }}</span>
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" bitDialogClose type="button">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
68
libs/vault/src/components/password-reprompt.component.ts
Normal file
68
libs/vault/src/components/password-reprompt.component.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
/**
|
||||
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
|
||||
* See UserVerificationComponent for any other situation where you need to verify the user's identity.
|
||||
*/
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "vault-password-reprompt",
|
||||
imports: [
|
||||
JslibModule,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: "password-reprompt.component.html",
|
||||
})
|
||||
export class PasswordRepromptComponent {
|
||||
formGroup = this.formBuilder.group({
|
||||
masterPassword: ["", { validators: [Validators.required], updateOn: "submit" }],
|
||||
});
|
||||
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected i18nService: I18nService,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected dialogRef: DialogRef
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
const storedMasterKey = await this.cryptoService.getOrDeriveMasterKey(
|
||||
this.formGroup.value.masterPassword
|
||||
);
|
||||
if (
|
||||
!(await this.cryptoService.compareAndUpdateKeyHash(
|
||||
this.formGroup.value.masterPassword,
|
||||
storedMasterKey
|
||||
))
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialogRef.close(true);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { PasswordRepromptService } from "./services/password-reprompt.service";
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
|
||||
import { ModalService } from "../../services/modal.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/src/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { PasswordRepromptService } from "./password-reprompt.service";
|
||||
|
||||
@@ -10,13 +9,13 @@ describe("PasswordRepromptService", () => {
|
||||
let passwordRepromptService: PasswordRepromptService;
|
||||
|
||||
let userVerificationService: MockProxy<UserVerificationService>;
|
||||
let modalService: MockProxy<ModalService>;
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
|
||||
beforeEach(() => {
|
||||
modalService = mock<ModalService>();
|
||||
dialogService = mock<DialogService>();
|
||||
userVerificationService = mock<UserVerificationService>();
|
||||
|
||||
passwordRepromptService = new PasswordRepromptService(modalService, userVerificationService);
|
||||
passwordRepromptService = new PasswordRepromptService(dialogService, userVerificationService);
|
||||
});
|
||||
|
||||
describe("enabled()", () => {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/vault/abstractions/password-reprompt.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { ModalService } from "../../services/modal.service";
|
||||
import { PasswordRepromptComponent } from "../components/password-reprompt.component";
|
||||
|
||||
/**
|
||||
@@ -11,11 +11,9 @@ import { PasswordRepromptComponent } from "../components/password-reprompt.compo
|
||||
* See UserVerificationService for any other situation where you need to verify the user's identity.
|
||||
*/
|
||||
@Injectable()
|
||||
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
|
||||
protected component = PasswordRepromptComponent;
|
||||
|
||||
export class PasswordRepromptService {
|
||||
constructor(
|
||||
private modalService: ModalService,
|
||||
private dialogService: DialogService,
|
||||
private userVerificationService: UserVerificationService
|
||||
) {}
|
||||
|
||||
@@ -28,13 +26,12 @@ export class PasswordRepromptService implements PasswordRepromptServiceAbstracti
|
||||
return true;
|
||||
}
|
||||
|
||||
const ref = this.modalService.open(this.component, { allowMultipleModals: true });
|
||||
const dialog = this.dialogService.open<boolean>(PasswordRepromptComponent, {
|
||||
ariaModal: true,
|
||||
});
|
||||
|
||||
if (ref == null) {
|
||||
return false;
|
||||
}
|
||||
const result = await lastValueFrom(dialog.closed);
|
||||
|
||||
const result = await ref.onClosedPromise();
|
||||
return result === true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user