[PM-2414] Angular 16 Upgrade - SetPinComponent (#7214)

* migrate to DialogService

* use static method

* add reactive form dependencies

* begin migrating to reactive forms

* migrate template inputs to use CL

* update set-pin.component.ts file to work with reactive forms

* migrate desktop template and class file to Dialog and ReactiveForms

* update settings page

* remove old properties

* update settings form upon dialog close

* refactor ngOnInit()

* remove duplicate validator (already have a validator in class file)
This commit is contained in:
rr-bw 2023-12-27 10:48:06 -08:00 committed by GitHub
parent 3d30823d2a
commit 00bb814fbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 161 deletions

View File

@ -1,64 +1,29 @@
<div class="modal fade" role="dialog" aria-modal="true"> <form [bitSubmit]="submit" [formGroup]="setPinForm">
<div class="modal-dialog modal-dialog-scrollable" role="document"> <bit-dialog>
<form class="modal-content" #form (ngSubmit)="submit()"> <div class="tw-font-semibold" bitDialogTitle>
<div class="modal-body"> {{ "unlockWithPin" | i18n }}
<div> </div>
{{ "setYourPinCode" | i18n }} <div bitDialogContent>
</div> <p>
<div class="box"> {{ "setYourPinCode" | i18n }}
<div class="box-content"> </p>
<div class="box-content-row box-content-row-flex no-hover no-bg" appBoxRow> <bit-form-field>
<div class="row-main"> <bit-label>{{ "pin" | i18n }}</bit-label>
<label for="pin">{{ "pin" | i18n }}</label> <input class="tw-font-mono" bitInput type="password" formControlName="pin" />
<input <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
id="pin" </bit-form-field>
type="{{ showPin ? 'text' : 'password' }}" <label class="tw-flex tw-items-start tw-gap-2" *ngIf="showMasterPassOnRestart">
name="Pin" <input class="tw-mt-1" type="checkbox" bitCheckbox formControlName="masterPassOnRestart" />
class="monospaced" <span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
[(ngModel)]="pin" </label>
required </div>
appInputVerbatim <div bitDialogFooter>
/> <button type="submit" bitButton bitFormButton buttonType="primary">
</div> <span>{{ "ok" | i18n }}</span>
<div class="action-buttons"> </button>
<button <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
type="button" {{ "cancel" | i18n }}
class="row-btn" </button>
appStopClick </div>
appA11yTitle="{{ 'toggleVisibility' | i18n }}" </bit-dialog>
(click)="toggleVisibility()" </form>
[attr.aria-pressed]="showPin"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPin, 'bwi-eye-slash': showPin }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit">
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,34 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component"; import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
DialogService,
FormFieldModule,
IconButtonModule,
} from "@bitwarden/components";
@Component({ @Component({
standalone: true,
templateUrl: "set-pin.component.html", templateUrl: "set-pin.component.html",
imports: [
DialogModule,
CommonModule,
JslibModule,
ButtonModule,
IconButtonModule,
ReactiveFormsModule,
AsyncActionsModule,
FormFieldModule,
],
}) })
export class SetPinComponent extends BaseSetPinComponent {} export class SetPinComponent extends BaseSetPinComponent {
static open(dialogService: DialogService) {
return dialogService.open<boolean>(SetPinComponent);
}
}

View File

@ -20,7 +20,6 @@ import { AvatarModule } from "@bitwarden/components";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import { AccountComponent } from "../auth/popup/account-switching/account.component"; import { AccountComponent } from "../auth/popup/account-switching/account.component";
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component"; import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
import { SetPinComponent } from "../auth/popup/components/set-pin.component";
import { EnvironmentComponent } from "../auth/popup/environment.component"; import { EnvironmentComponent } from "../auth/popup/environment.component";
import { HintComponent } from "../auth/popup/hint.component"; import { HintComponent } from "../auth/popup/hint.component";
import { HomeComponent } from "../auth/popup/home.component"; import { HomeComponent } from "../auth/popup/home.component";
@ -148,7 +147,6 @@ import "../platform/popup/locales";
SendListComponent, SendListComponent,
SendTypeComponent, SendTypeComponent,
SetPasswordComponent, SetPasswordComponent,
SetPinComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
SsoComponent, SsoComponent,

View File

@ -316,14 +316,15 @@ export class SettingsComponent implements OnInit {
async updatePin(value: boolean) { async updatePin(value: boolean) {
if (value) { if (value) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true }); const dialogRef = SetPinComponent.open(this.dialogService);
if (ref == null) { if (dialogRef == null) {
this.form.controls.pin.setValue(false, { emitEvent: false }); this.form.controls.pin.setValue(false, { emitEvent: false });
return; return;
} }
this.form.controls.pin.setValue(await ref.onClosedPromise(), { emitEvent: false }); const userHasPinSet = await firstValueFrom(dialogRef.closed);
this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false });
} else { } else {
await this.vaultTimeoutSettingsService.clear(); await this.vaultTimeoutSettingsService.clear();
} }

View File

@ -409,16 +409,17 @@ export class SettingsComponent implements OnInit {
async updatePin(value: boolean) { async updatePin(value: boolean) {
if (value) { if (value) {
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true }); const dialogRef = SetPinComponent.open(this.dialogService);
if (ref == null) { if (dialogRef == null) {
this.form.controls.pin.setValue(false, { emitEvent: false }); this.form.controls.pin.setValue(false, { emitEvent: false });
return; return;
} }
this.userHasPinSet = await ref.onClosedPromise(); this.userHasPinSet = await firstValueFrom(dialogRef.closed);
this.form.controls.pin.setValue(this.userHasPinSet, { emitEvent: false }); this.form.controls.pin.setValue(this.userHasPinSet, { emitEvent: false });
} }
if (!value) { if (!value) {
// If user turned off PIN without having a MP and has biometric + require MP/PIN on restart enabled // If user turned off PIN without having a MP and has biometric + require MP/PIN on restart enabled
if (this.form.value.requirePasswordOnStart && !this.userHasMasterPassword) { if (this.form.value.requirePasswordOnStart && !this.userHasMasterPassword) {
@ -429,6 +430,7 @@ export class SettingsComponent implements OnInit {
await this.vaultTimeoutSettingsService.clear(); await this.vaultTimeoutSettingsService.clear();
} }
this.messagingService.send("redrawMenu"); this.messagingService.send("redrawMenu");
} }

View File

@ -10,7 +10,6 @@ import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"
import { DialogModule } from "@bitwarden/components"; import { DialogModule } from "@bitwarden/components";
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
import { SetPinComponent } from "../auth/components/set-pin.component";
import { DeleteAccountComponent } from "../auth/delete-account.component"; import { DeleteAccountComponent } from "../auth/delete-account.component";
import { EnvironmentComponent } from "../auth/environment.component"; import { EnvironmentComponent } from "../auth/environment.component";
import { HintComponent } from "../auth/hint.component"; import { HintComponent } from "../auth/hint.component";
@ -92,7 +91,6 @@ import { SendComponent } from "./tools/send/send.component";
SendAddEditComponent, SendAddEditComponent,
SendComponent, SendComponent,
SetPasswordComponent, SetPasswordComponent,
SetPinComponent,
SettingsComponent, SettingsComponent,
ShareComponent, ShareComponent,
SsoComponent, SsoComponent,

View File

@ -1,65 +1,29 @@
<div class="modal fade" role="dialog" aria-modal="true"> <form [bitSubmit]="submit" [formGroup]="setPinForm">
<div class="modal-dialog modal-dialog-scrollable set-pin-modal" role="document"> <bit-dialog>
<form class="modal-content" #form (ngSubmit)="submit()"> <div class="tw-font-semibold" bitDialogTitle>
<div class="modal-body"> {{ "unlockWithPin" | i18n }}
<div> </div>
{{ "setYourPinCode" | i18n }} <div bitDialogContent>
</div> <p>
<div class="box"> {{ "setYourPinCode" | i18n }}
<div class="box-content"> </p>
<div class="box-content-row box-content-row-flex" appBoxRow> <bit-form-field>
<div class="row-main"> <bit-label>{{ "pin" | i18n }}</bit-label>
<label for="pin">{{ "pin" | i18n }}</label> <input class="tw-font-mono" bitInput type="password" formControlName="pin" />
<input <button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
id="pin" </bit-form-field>
type="{{ showPin ? 'text' : 'password' }}" <label class="tw-flex tw-items-start tw-gap-2" *ngIf="showMasterPassOnRestart">
name="Pin" <input class="tw-mt-1" type="checkbox" bitCheckbox formControlName="masterPassOnRestart" />
class="monospaced" <span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
[(ngModel)]="pin" </label>
required </div>
appInputVerbatim <div bitDialogFooter>
appAutofocus <button type="submit" bitButton bitFormButton buttonType="primary">
/> <span>{{ "ok" | i18n }}</span>
</div> </button>
<div class="action-buttons"> <button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
<button {{ "cancel" | i18n }}
type="button" </button>
class="row-btn" </div>
appStopClick </bit-dialog>
appA11yTitle="{{ 'toggleVisibility' | i18n }}" </form>
[attr.aria-pressed]="showPin"
(click)="toggleVisibility()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPin, 'bwi-eye-slash': showPin }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="checkbox" *ngIf="showMasterPassOnRestart">
<label for="masterPasswordOnRestart">
<input
type="checkbox"
id="masterPasswordOnRestart"
name="MasterPasswordOnRestart"
[(ngModel)]="masterPassOnRestart"
/>
<span>{{ "lockWithMasterPassOnRestart" | i18n }}</span>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary btn-submit">
<span>{{ "ok" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "cancel" | i18n }}
</button>
</div>
</form>
</div>
</div>

View File

@ -1,8 +1,33 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component"; import { SetPinComponent as BaseSetPinComponent } from "@bitwarden/angular/auth/components/set-pin.component";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import {
AsyncActionsModule,
ButtonModule,
DialogModule,
DialogService,
FormFieldModule,
IconButtonModule,
} from "@bitwarden/components";
@Component({ @Component({
standalone: true,
templateUrl: "set-pin.component.html", templateUrl: "set-pin.component.html",
imports: [
DialogModule,
CommonModule,
JslibModule,
ButtonModule,
IconButtonModule,
ReactiveFormsModule,
AsyncActionsModule,
FormFieldModule,
],
}) })
export class SetPinComponent extends BaseSetPinComponent {} export class SetPinComponent extends BaseSetPinComponent {
static open(dialogService: DialogService) {
return dialogService.open<boolean>(SetPinComponent);
}
}

View File

@ -1,57 +1,63 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Directive, OnInit } from "@angular/core"; import { Directive, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ModalRef } from "../../components/modal/modal.ref";
@Directive() @Directive()
export class SetPinComponent implements OnInit { export class SetPinComponent implements OnInit {
pin = "";
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true; showMasterPassOnRestart = true;
setPinForm = this.formBuilder.group({
pin: ["", [Validators.required]],
masterPassOnRestart: true,
});
constructor( constructor(
private modalRef: ModalRef, private dialogRef: DialogRef,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private userVerificationService: UserVerificationService, private userVerificationService: UserVerificationService,
private stateService: StateService, private stateService: StateService,
private formBuilder: FormBuilder,
) {} ) {}
async ngOnInit() { async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart = const hasMasterPassword = await this.userVerificationService.hasMasterPassword();
await this.userVerificationService.hasMasterPassword();
this.setPinForm.controls.masterPassOnRestart.setValue(hasMasterPassword);
this.showMasterPassOnRestart = hasMasterPassword;
} }
toggleVisibility() { submit = async () => {
this.showPin = !this.showPin; const pin = this.setPinForm.get("pin").value;
} const masterPassOnRestart = this.setPinForm.get("masterPassOnRestart").value;
async submit() { if (Utils.isNullOrWhitespace(pin)) {
if (Utils.isNullOrWhitespace(this.pin)) { this.dialogRef.close(false);
this.modalRef.close(false);
return; return;
} }
const pinKey = await this.cryptoService.makePinKey( const pinKey = await this.cryptoService.makePinKey(
this.pin, pin,
await this.stateService.getEmail(), await this.stateService.getEmail(),
await this.stateService.getKdfType(), await this.stateService.getKdfType(),
await this.stateService.getKdfConfig(), await this.stateService.getKdfConfig(),
); );
const userKey = await this.cryptoService.getUserKey(); const userKey = await this.cryptoService.getUserKey();
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey); const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
const encPin = await this.cryptoService.encrypt(this.pin, userKey); const encPin = await this.cryptoService.encrypt(pin, userKey);
await this.stateService.setProtectedPin(encPin.encryptedString); await this.stateService.setProtectedPin(encPin.encryptedString);
if (this.masterPassOnRestart) {
if (masterPassOnRestart) {
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(pinProtectedKey); await this.stateService.setPinKeyEncryptedUserKeyEphemeral(pinProtectedKey);
} else { } else {
await this.stateService.setPinKeyEncryptedUserKey(pinProtectedKey); await this.stateService.setPinKeyEncryptedUserKey(pinProtectedKey);
} }
this.modalRef.close(true); this.dialogRef.close(true);
} };
} }