diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d63b17f..cd2383efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the error handling in the biometric authentication registration - Set up SSL for local development ## 2.89.0 - 2024-06-14 diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index 4f80207aa..a809d5ed2 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -20,9 +20,10 @@ import { } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { format, parseISO } from 'date-fns'; import { uniq } from 'lodash'; -import { EMPTY, Subject } from 'rxjs'; +import { EMPTY, Subject, throwError } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; @Component({ @@ -42,6 +43,7 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit { public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateUserSettings: boolean; public isAccessTokenHidden = true; + public isFingerprintSupported = this.doesBrowserSupportAuthn(); public isWebAuthnEnabled: boolean; public language = document.documentElement.lang; public locales = [ @@ -67,6 +69,7 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit { private dataService: DataService, private formBuilder: FormBuilder, private settingsStorageService: SettingsStorageService, + private snackBar: MatSnackBar, private tokenStorageService: TokenStorageService, private userService: UserService, public webAuthnService: WebAuthnService @@ -222,9 +225,15 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit { }); } - public onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { + public async onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) { if (aEvent.checked) { - this.registerDevice(); + try { + await this.registerDevice(); + } catch { + aEvent.source.checked = false; + + this.changeDetectorRef.markForCheck(); + } } else { const confirmation = confirm( $localize`Do you really want to remove this sign in method?` @@ -265,35 +274,54 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit { this.webAuthnService .deregister() .pipe( - takeUntil(this.unsubscribeSubject), catchError(() => { this.update(); return EMPTY; - }) + }), + takeUntil(this.unsubscribeSubject) ) .subscribe(() => { this.update(); }); } - private registerDevice() { - this.webAuthnService - .register() - .pipe( - takeUntil(this.unsubscribeSubject), - catchError(() => { - this.update(); - - return EMPTY; - }) - ) - .subscribe(() => { - this.settingsStorageService.removeSetting(KEY_STAY_SIGNED_IN); - this.settingsStorageService.removeSetting(KEY_TOKEN); + private doesBrowserSupportAuthn() { + // Authn is built on top of PublicKeyCredential: https://stackoverflow.com/a/55868189 + return typeof PublicKeyCredential !== 'undefined'; + } - this.update(); - }); + private registerDevice(): Promise { + return new Promise((resolve, reject) => { + this.webAuthnService + .register() + .pipe( + catchError((error: Error) => { + this.snackBar.open( + $localize`Oops! There was an error setting up biometric authentication.`, + undefined, + { duration: 3000 } + ); + + return throwError(() => { + return error; + }); + }), + takeUntil(this.unsubscribeSubject) + ) + .subscribe({ + next: () => { + this.settingsStorageService.removeSetting(KEY_STAY_SIGNED_IN); + this.settingsStorageService.removeSetting(KEY_TOKEN); + + this.update(); + resolve(); + }, + error: (error) => { + reject(error); + } + }); + }); } private update() { diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index 0dd8ac47e..c369f81bd 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -189,7 +189,9 @@ color="primary" hideIcon="true" [checked]="isWebAuthnEnabled === true" - [disabled]="!hasPermissionToUpdateUserSettings" + [disabled]=" + !hasPermissionToUpdateUserSettings || !isFingerprintSupported + " (change)="onSignInWithFingerprintChange($event)" />