This commit is contained in:
Addison Beck 2024-04-25 20:31:33 -04:00 committed by GitHub
commit 25bc1edeca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 71 additions and 33 deletions

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,41 @@ export class EnrollMasterPasswordReset {
text: "resetPasswordEnrollmentWarning",
type: "warning",
},
verificationType: {
type: "custom",
verificationFn: async (secret: VerificationWithSecret) => {
const request = (await userVerificationService.buildRequest(
secret,
)) as OrganizationUserResetPasswordEnrollmentRequest;
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

View File

@ -9,8 +9,8 @@
<!-- Show optional content when verification is server side or client side and verification methods were found. -->
<ng-container
*ngIf="
!dialogOptions.clientSideOnlyVerification ||
(dialogOptions.clientSideOnlyVerification &&
dialogOptions.verificationType !== 'client' ||
(dialogOptions.verificationType === 'client' &&
activeClientVerificationOption !== ActiveClientVerificationOption.None)
"
>
@ -29,7 +29,7 @@
<!-- Shown when client side verification methods picked and no verification methods found -->
<ng-container
*ngIf="
dialogOptions.clientSideOnlyVerification &&
dialogOptions.verificationType === 'client' &&
activeClientVerificationOption === ActiveClientVerificationOption.None
"
>
@ -41,7 +41,7 @@
<app-user-verification-form-input
[(invalidSecret)]="invalidSecret"
formControlName="secret"
[verificationType]="dialogOptions.clientSideOnlyVerification ? 'client' : 'server'"
[verificationType]="dialogOptions.verificationType === 'client' ? 'client' : 'server'"
(activeClientVerificationOptionChange)="handleActiveClientVerificationOptionChange($event)"
(biometricsVerificationResultChange)="handleBiometricsVerificationResultChange($event)"
></app-user-verification-form-input>
@ -50,8 +50,8 @@
<!-- Confirm button container - shown for server side validation but hidden if client side validation + biometrics -->
<ng-container
*ngIf="
!dialogOptions.clientSideOnlyVerification ||
(dialogOptions.clientSideOnlyVerification &&
dialogOptions.verificationType !== 'client' ||
(dialogOptions.verificationType === 'client' &&
activeClientVerificationOption !== ActiveClientVerificationOption.Biometrics)
"
>
@ -85,10 +85,12 @@
<ng-container
*ngIf="activeClientVerificationOption === ActiveClientVerificationOption.None"
>
<!-- For no client verifications found, show set a pin confirm button.
Note: this doesn't make sense for web as web doesn't support PINs, but this is how we are handling it for now
as the expectation is that only browser and desktop will use the new clientSideOnlyVerification flow.
We might genericize this in the future to just tell the user they need to configure a valid user verification option like PIN or Biometrics. -->
<!--
For no client verifications found, show set a pin confirm button.
Note: this doesn't make sense for web as web doesn't support PINs, but this is how we are handling it for now
as the expectation is that only browser and desktop will use the new verificationType 'client' flow.
We might genericize this in the future to just tell the user they need to configure a valid user verification option like PIN or Biometrics.
-->
<button type="submit" bitButton bitFormButton buttonType="primary">
{{ "setPin" | i18n }}
</button>

View File

@ -201,6 +201,17 @@ export class UserVerificationDialogComponent {
return;
}
if (
typeof this.dialogOptions.verificationType === "object" &&
this.dialogOptions.verificationType.type === "custom"
) {
const success = await this.dialogOptions.verificationType.verificationFn(this.secret.value);
this.close({
userAction: "confirm",
verificationSuccess: success,
});
}
try {
// TODO: once we migrate all user verification scenarios to use this new implementation,
// we should consider refactoring the user verification service handling of the

View File

@ -1,3 +1,4 @@
import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification";
import { ButtonType } from "@bitwarden/components";
/**
@ -60,12 +61,25 @@ export type UserVerificationDialogOptions = {
*/
confirmButtonOptions?: UserVerificationConfirmButtonOptions;
/**
* Indicates whether the verification is only performed client-side. Includes local MP verification, PIN, and Biometrics.
* Optional.
* **Important:** Only for use on desktop and browser platforms as when there are no client verification methods, the user is instructed to set a pin (which is not supported on web)
/** The validation method used to verify the secret.
*
* Possible values:
*
* - "default": Perform the default validation operation for a given secret
* type. This would, for example, validate master passwords locally but
* OTPs on the server.
* - "client": Only do a client-side verification with no possible server
* request. Includes local MP verification, PIN, and Biometrics.
* **Important:** This option is only for use on desktop and browser
* platforms. When there are no client verification methods the user is
* instructed to set a pin, and this is not supported on web.
* - "custom": Custom validation is done to verify the secret. This is
* passed in from callers when opening the dialog.
*/
clientSideOnlyVerification?: boolean;
verificationType?:
| "default"
| "client"
| { type: "custom"; verificationFn: (secret: VerificationWithSecret) => Promise<boolean> };
};
/**