1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[PM-2135] [BEEEP] Refactor and refresh web user verification components (#5377)

* [PM-2135] feat: create new user-verification module

* [PM-2136] feat: add ability to remove form field bottom margin

(cherry picked from commit 05925ff77ed47f3865c2aecade8271390d9e2fa6)

* [PM-2135] feat: refactor user-verification component

* [PM-2135] feat: refactor user-verification-prompt

* [PM-2135] feat: use form validation in prompt

* [PM-2135] feat: change autofocus target

* [PM-2135] chore: clean up old code

* [PM-2135] feat: allow user verification to show invalid password error

* [PM-2135] feat: hack mark as touched to get error to display

* [PM-2135] chore: move to auth

* [PM-2135] fix: hardcoded dialog buttons

* [PM-2135] feat: add onDestroy handler

* [PM-2135] fix: remove unecessary directive input

* [PM-2135] feat: add password toggle

* [PM-2135] chore: add hack comment

* [PM-2135] chore: move services to auth folder and rename

* [PM-2135] fix: show correct error messages

* [PM-2135] fix: re-add non-existant files to whitelist

I honestly don't know why the linter is complaining about this

* Fix capital letters whitelist

* [PM-2135] chore: remove rows that were mistakenly added during merge from master

* [PM-2135] chore: remove rows that were mistakenly added during merge from master

* [PM-2135] feat: disable built-in browser validations

* Revert "[PM-2135] feat: disable built-in browser validations"

This reverts commit 969f75822a.

---------

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Andreas Coroiu
2023-06-28 15:02:22 +02:00
committed by GitHub
parent 6ef6140b29
commit a73525a80c
48 changed files with 288 additions and 173 deletions

View File

@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";

View File

@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";

View File

@@ -1,9 +1,10 @@
import { Directive } from "@angular/core";
import { FormBuilder, FormControl } from "@angular/forms";
import { FormBuilder } from "@angular/forms";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Verification } from "@bitwarden/common/types/verification";
import { ModalRef } from "../../components/modal/modal.ref";
import { ModalConfig } from "../../services/modal.service";
@@ -16,7 +17,12 @@ export class UserVerificationPromptComponent {
confirmDescription = this.config.data.confirmDescription;
confirmButtonText = this.config.data.confirmButtonText;
modalTitle = this.config.data.modalTitle;
secret = new FormControl();
formGroup = this.formBuilder.group({
secret: this.formBuilder.control<Verification | null>(null),
});
protected invalidSecret = false;
constructor(
private modalRef: ModalRef,
@@ -27,19 +33,31 @@ export class UserVerificationPromptComponent {
private i18nService: I18nService
) {}
async submit() {
try {
//Incorrect secret will throw an invalid password error.
await this.userVerificationService.verifyUser(this.secret.value);
} catch (e) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
get secret() {
return this.formGroup.controls.secret;
}
submit = async () => {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
return;
}
this.modalRef.close(true);
try {
//Incorrect secret will throw an invalid password error.
await this.userVerificationService.verifyUser(this.secret.value);
this.invalidSecret = false;
} catch (e) {
this.invalidSecret = true;
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
return;
}
this.close(true);
};
close(success: boolean) {
this.modalRef.close(success);
}
}

View File

@@ -1,9 +1,11 @@
import { Directive, OnInit } from "@angular/core";
import { ControlValueAccessor, FormControl } from "@angular/forms";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { ControlValueAccessor, FormControl, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { Verification } from "@bitwarden/common/types/verification";
@@ -17,29 +19,65 @@ import { Verification } from "@bitwarden/common/types/verification";
selector: "app-user-verification",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
usesKeyConnector = false;
export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy {
private _invalidSecret = false;
@Input()
get invalidSecret() {
return this._invalidSecret;
}
set invalidSecret(value: boolean) {
this._invalidSecret = value;
this.invalidSecretChange.emit(value);
// ISSUE: This is pretty hacky but unfortunately there is no way of knowing if the parent
// control has been marked as touched, see: https://github.com/angular/angular/issues/10887
// When that functionality has been added we should also look into forwarding reactive form
// controls errors so that we don't need a separate input/output `invalidSecret`.
if (value) {
this.secret.markAsTouched();
}
this.secret.updateValueAndValidity({ emitEvent: false });
}
@Output() invalidSecretChange = new EventEmitter<boolean>();
usesKeyConnector = true;
disableRequestOTP = false;
sentCode = false;
secret = new FormControl("");
secret = new FormControl("", [
Validators.required,
() => {
if (this.invalidSecret) {
return {
invalidSecret: {
message: this.usesKeyConnector
? this.i18nService.t("incorrectCode")
: this.i18nService.t("incorrectPassword"),
},
};
}
},
]);
private onChange: (value: Verification) => void;
private destroy$ = new Subject<void>();
constructor(
private keyConnectorService: KeyConnectorService,
private userVerificationService: UserVerificationService
private userVerificationService: UserVerificationService,
private i18nService: I18nService
) {}
async ngOnInit() {
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
this.secret.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((secret: string) => this.processChanges(secret));
}
async requestOTP() {
requestOTP = async () => {
if (this.usesKeyConnector) {
this.disableRequestOTP = true;
try {
@@ -49,7 +87,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
this.disableRequestOTP = false;
}
}
}
};
writeValue(obj: any): void {
this.secret.setValue(obj);
@@ -72,7 +110,14 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
}
}
private processChanges(secret: string) {
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
protected processChanges(secret: string) {
this.invalidSecret = false;
if (this.onChange == null) {
return;
}

View File

@@ -18,8 +18,6 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -48,6 +46,8 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";

View File

@@ -3,9 +3,9 @@ import { UntypedFormBuilder, Validators } from "@angular/forms";
import { merge, startWith, Subject, takeUntil } from "rxjs";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";

View File

@@ -1,4 +1,4 @@
import { VerifyOTPRequest } from "../../auth/models/request/verify-otp.request";
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
export abstract class UserVerificationApiServiceAbstraction {
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;

View File

@@ -1,5 +1,5 @@
import { SecretVerificationRequest } from "../../auth/models/request/secret-verification.request";
import { Verification } from "../../types/verification";
import { Verification } from "../../../types/verification";
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
export abstract class UserVerificationService {
buildRequest: <T extends SecretVerificationRequest>(

View File

@@ -1,9 +1,9 @@
import { ApiService } from "../../abstractions/api.service";
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
import { LogService } from "../../platform/abstractions/log.service";
import { Verification } from "../../types/verification";
import { AccountApiService } from "../abstractions/account-api.service";
import { InternalAccountService } from "../abstractions/account.service";
import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction";
export class AccountApiServiceImplementation implements AccountApiService {
constructor(

View File

@@ -1,5 +1,5 @@
import { ApiService } from "../../../abstractions/api.service";
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {

View File

@@ -1,8 +1,8 @@
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../../abstractions/userVerification/userVerification.service.abstraction";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { Verification } from "../../../types/verification";
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "../../enums/verification-type";
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";

View File

@@ -1,8 +1,11 @@
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import {
AfterContentChecked,
Component,
ContentChild,
ContentChildren,
HostBinding,
Input,
QueryList,
ViewChild,
} from "@angular/core";
@@ -17,9 +20,6 @@ import { BitSuffixDirective } from "./suffix.directive";
@Component({
selector: "bit-form-field",
templateUrl: "./form-field.component.html",
host: {
class: "tw-mb-6 tw-block",
},
})
export class BitFormFieldComponent implements AfterContentChecked {
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
@@ -30,6 +30,19 @@ export class BitFormFieldComponent implements AfterContentChecked {
@ContentChildren(BitPrefixDirective) prefixChildren: QueryList<BitPrefixDirective>;
@ContentChildren(BitSuffixDirective) suffixChildren: QueryList<BitSuffixDirective>;
private _disableMargin = false;
@Input() set disableMargin(value: boolean | "") {
this._disableMargin = coerceBooleanProperty(value);
}
get disableMargin() {
return this._disableMargin;
}
@HostBinding("class")
get classList() {
return ["tw-block"].concat(this.disableMargin ? [] : ["tw-mb-6"]);
}
ngAfterContentChecked(): void {
if (this.error) {
this.input.ariaDescribedBy = this.error.id;