diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 1854f1f6ff..28ad7b2d42 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1714,6 +1714,12 @@ "biometricsNotSupportedDesc": { "message": "Browser biometrics is not supported on this device." }, + "biometricsNotUnlockedTitle": { + "message": "User locked or logged out" + }, + "biometricsNotUnlockedDesc": { + "message": "Please unlock this user in the desktop application and try again." + }, "biometricsFailedTitle": { "message": "Biometrics failed" }, diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 51ab301fd1..534a239a81 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -321,6 +321,15 @@ export class NativeMessagingBackground { type: "danger", }); break; + } else if (message.response === "not unlocked") { + this.messagingService.send("showDialog", { + title: { key: "biometricsNotUnlockedTitle" }, + content: { key: "biometricsNotUnlockedDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + break; } else if (message.response === "canceled") { break; } diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index 48bdc60047..06881b7463 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -1,8 +1,10 @@ import { Injectable, NgZone } from "@angular/core"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -43,6 +45,7 @@ export class NativeMessagingService { private nativeMessageHandler: NativeMessageHandlerService, private dialogService: DialogService, private accountService: AccountService, + private authService: AuthService, private ngZone: NgZone, ) {} @@ -137,6 +140,19 @@ export class NativeMessagingService { return this.send({ command: "biometricUnlock", response: "not supported" }, appId); } + const userId = + (message.userId as UserId) ?? + (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)))); + + if (userId == null) { + return this.send({ command: "biometricUnlock", response: "not unlocked" }, appId); + } + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + if (authStatus !== AuthenticationStatus.Unlocked) { + return this.send({ command: "biometricUnlock", response: "not unlocked" }, appId); + } + const biometricUnlockPromise = message.userId == null ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)