diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 8e51152be2..e8a660620a 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -21,6 +21,7 @@ import { ToastOptions, ToastService, } from "@bitwarden/components"; +import { BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; @@ -64,6 +65,7 @@ export class AppComponent implements OnInit, OnDestroy { private toastService: ToastService, private accountService: AccountService, private animationControlService: AnimationControlService, + private biometricStateService: BiometricStateService, ) {} async ngOnInit() { @@ -134,6 +136,7 @@ export class AppComponent implements OnInit, OnDestroy { } else if (msg.command === "reloadProcess") { if (this.platformUtilsService.isSafari()) { window.setTimeout(() => { + this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); } diff --git a/libs/key-management/src/angular/lock/components/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts index e9c7d0d607..23f1a7a433 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -70,6 +70,9 @@ const clientTypeToSuccessRouteRecord: Partial> = { [ClientType.Browser]: "/tabs/current", }; +/// The minimum amount of time to wait after a process reload for a biometrics auto prompt to be possible +/// Fixes safari autoprompt behavior +const AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY = 5000; @Component({ selector: "bit-lock", templateUrl: "lock.component.html", @@ -304,7 +307,13 @@ export class LockComponent implements OnInit, OnDestroy { (await this.biometricService.getShouldAutopromptNow()) ) { await this.biometricService.setShouldAutopromptNow(false); - await this.unlockViaBiometrics(); + if ( + (await this.biometricStateService.getLastProcessReload()) == null || + Date.now() - (await this.biometricStateService.getLastProcessReload()).getTime() > + AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY + ) { + await this.unlockViaBiometrics(); + } } } } diff --git a/libs/key-management/src/biometrics/biometric-state.service.ts b/libs/key-management/src/biometrics/biometric-state.service.ts index 138e2589b1..c7f958c97a 100644 --- a/libs/key-management/src/biometrics/biometric-state.service.ts +++ b/libs/key-management/src/biometrics/biometric-state.service.ts @@ -14,6 +14,7 @@ import { PROMPT_AUTOMATICALLY, PROMPT_CANCELLED, FINGERPRINT_VALIDATED, + LAST_PROCESS_RELOAD, } from "./biometric.state"; export abstract class BiometricStateService { @@ -106,6 +107,10 @@ export abstract class BiometricStateService { */ abstract setFingerprintValidated(validated: boolean): Promise; + abstract updateLastProcessReload(): Promise; + + abstract getLastProcessReload(): Promise; + abstract logout(userId: UserId): Promise; } @@ -117,6 +122,7 @@ export class DefaultBiometricStateService implements BiometricStateService { private promptCancelledState: GlobalState>; private promptAutomaticallyState: ActiveUserState; private fingerprintValidatedState: GlobalState; + private lastProcessReloadState: GlobalState; biometricUnlockEnabled$: Observable; encryptedClientKeyHalf$: Observable; requirePasswordOnStart$: Observable; @@ -124,6 +130,7 @@ export class DefaultBiometricStateService implements BiometricStateService { promptCancelled$: Observable; promptAutomatically$: Observable; fingerprintValidated$: Observable; + lastProcessReload$: Observable; constructor(private stateProvider: StateProvider) { this.biometricUnlockEnabledState = this.stateProvider.getActive(BIOMETRIC_UNLOCK_ENABLED); @@ -159,6 +166,9 @@ export class DefaultBiometricStateService implements BiometricStateService { this.fingerprintValidatedState = this.stateProvider.getGlobal(FINGERPRINT_VALIDATED); this.fingerprintValidated$ = this.fingerprintValidatedState.state$.pipe(map(Boolean)); + + this.lastProcessReloadState = this.stateProvider.getGlobal(LAST_PROCESS_RELOAD); + this.lastProcessReload$ = this.lastProcessReloadState.state$; } async setBiometricUnlockEnabled(enabled: boolean): Promise { @@ -270,6 +280,14 @@ export class DefaultBiometricStateService implements BiometricStateService { async setFingerprintValidated(validated: boolean): Promise { await this.fingerprintValidatedState.update(() => validated); } + + async updateLastProcessReload(): Promise { + await this.lastProcessReloadState.update(() => new Date()); + } + + async getLastProcessReload(): Promise { + return await firstValueFrom(this.lastProcessReload$); + } } function encryptedClientKeyHalfToEncString( diff --git a/libs/key-management/src/biometrics/biometric.state.ts b/libs/key-management/src/biometrics/biometric.state.ts index f88bd1da58..c37b7d7370 100644 --- a/libs/key-management/src/biometrics/biometric.state.ts +++ b/libs/key-management/src/biometrics/biometric.state.ts @@ -95,3 +95,14 @@ export const FINGERPRINT_VALIDATED = new KeyDefinition( deserializer: (obj) => obj, }, ); + +/** + * Last process reload time + */ +export const LAST_PROCESS_RELOAD = new KeyDefinition( + BIOMETRIC_SETTINGS_DISK, + "lastProcessReload", + { + deserializer: (obj) => new Date(obj), + }, +);