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:
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>;
|
||||
@@ -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>(
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user