1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

Add the ability for custom validation logic to be injected into UserVerificationDialogComponent (#8770)

* Introduce `verificationType`

* Update template to use `verificationType`

* Implement a path for `verificationType = 'custom'`

* Delete `clientSideOnlyVerification`

* Update `EnrollMasterPasswordResetComponent` to include a server-side hash check

* Better describe the custom scenerio through comments

* Add an example of the custom verficiation scenerio

* Move execution of verification function into try/catch

* Migrate existing uses of `clientSideOnlyVerification`

* Use generic type option instead of casting

* Change "given" to "determined" in a comment
This commit is contained in:
Addison Beck
2024-07-01 11:52:39 -04:00
committed by GitHub
parent 9531d1c655
commit 71e8fdb73d
7 changed files with 108 additions and 41 deletions

View File

@@ -89,7 +89,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -105,7 +105,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -122,7 +122,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -135,7 +135,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -198,7 +198,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -214,7 +214,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});
@@ -231,7 +231,7 @@ describe("Fido2UserVerificationService", () => {
);
expect(UserVerificationDialogComponent.open).toHaveBeenCalledWith(dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
expect(result).toBe(true);
});

View File

@@ -54,7 +54,7 @@ export class Fido2UserVerificationService {
private async showUserVerificationDialog(): Promise<boolean> {
const result = await UserVerificationDialogComponent.open(this.dialogService, {
clientSideOnlyVerification: true,
verificationType: "client",
});
if (result.userAction === "cancel") {

View File

@@ -2,6 +2,8 @@ import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification";
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";
@@ -26,6 +28,7 @@ export class EnrollMasterPasswordReset {
i18nService: I18nService,
syncService: SyncService,
logService: LogService,
userVerificationService: UserVerificationService,
) {
const result = await UserVerificationDialogComponent.open(dialogService, {
title: "enrollAccountRecovery",
@@ -33,36 +36,42 @@ export class EnrollMasterPasswordReset {
text: "resetPasswordEnrollmentWarning",
type: "warning",
},
verificationType: {
type: "custom",
verificationFn: async (secret: VerificationWithSecret) => {
const request =
await userVerificationService.buildRequest<OrganizationUserResetPasswordEnrollmentRequest>(
secret,
);
request.resetPasswordKey = await resetPasswordService.buildRecoveryKey(
data.organization.id,
);
// Process the enrollment request, which is an endpoint that is
// gated by a server-side check of the master password hash
await organizationUserService.putOrganizationUserResetPasswordEnrollment(
data.organization.id,
data.organization.userId,
request,
);
return true;
},
},
});
// Handle the result of the dialog based on user action and verification success
// User canceled enrollment
if (result.userAction === "cancel") {
return;
}
// User confirmed the dialog so check verification success
// Enrollment failed
if (!result.verificationSuccess) {
// verification failed
return;
}
// Verification succeeded
// Enrollment succeeded
try {
// This object is missing most of the properties in the
// `OrganizationUserResetPasswordEnrollmentRequest()`, but those
// properties don't carry over to the server model anyway and are
// never used by this flow.
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.resetPasswordKey = await resetPasswordService.buildRecoveryKey(data.organization.id);
await organizationUserService.putOrganizationUserResetPasswordEnrollment(
data.organization.id,
data.organization.userId,
request,
);
platformUtilsService.showToast("success", null, i18nService.t("enrollPasswordResetSuccess"));
await syncService.fullSync(true);
} catch (e) {
logService.error(e);

View File

@@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
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";
@@ -48,6 +49,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
private dialogService: DialogService,
private resetPasswordService: OrganizationUserResetPasswordService,
private userVerificationService: UserVerificationService,
) {}
async ngOnInit() {
@@ -155,6 +157,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
this.i18nService,
this.syncService,
this.logService,
this.userVerificationService,
);
} else {
// Remove reset password