diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index d11c2d5a6ba..4446dd4f487 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2180,6 +2180,9 @@ "lockScreenDesktopRunningButLoggedOut": { "message": "User account synchronization is enabled, but the active account is logged out in the desktop app." }, + "lockScreenDesktopRunningButNotTrusted": { + "message": "The connection fingerprint verification was denied in the desktop app." + }, "unlockWithBiometrics": { "message": "Unlock with biometrics" }, diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index b62bb1106c9..992cd65a554 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -85,6 +85,7 @@ export default class RuntimeBackground { SyncedUnlockStateCommands.GetUserKeyFromDesktop, SyncedUnlockStateCommands.GetUserStatusFromDesktop, SyncedUnlockStateCommands.FocusDesktopApp, + SyncedUnlockStateCommands.IsConnectionTrusted, "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag", "getUserPremiumStatus", ]; @@ -225,6 +226,9 @@ export default class RuntimeBackground { case SyncedUnlockStateCommands.FocusDesktopApp: { return await this.main.syncedUnlockService.focusDesktopApp(); } + case SyncedUnlockStateCommands.IsConnectionTrusted: { + return await this.main.syncedUnlockService.isConnectionTrusted(); + } case "getUseTreeWalkerApiForPageDetailsCollectionFeatureFlag": { return await this.configService.getFeatureFlag( FeatureFlag.UseTreeWalkerApiForPageDetailsCollection, diff --git a/apps/browser/src/key-management/synced-unlock/background-synced-unlock.service.ts b/apps/browser/src/key-management/synced-unlock/background-synced-unlock.service.ts index 5c16f662123..f1f9285f835 100644 --- a/apps/browser/src/key-management/synced-unlock/background-synced-unlock.service.ts +++ b/apps/browser/src/key-management/synced-unlock/background-synced-unlock.service.ts @@ -18,8 +18,14 @@ import { import { NativeMessagingBackground } from "../../background/nativeMessaging.background"; +const SYNC_INTERVAL = 1000; // 1 second +const TRUST_DENIED_TIMEOUT = 30000; // 30 seconds +const STATUS_TIMEOUT = 5000; // 5 seconds + @Injectable() export class BackgroundSyncedUnlockService extends SyncedUnlockService { + private hasTrustDenied = false; + constructor( private nativeMessagingBackground: () => NativeMessagingBackground, private logService: LogService, @@ -30,7 +36,7 @@ export class BackgroundSyncedUnlockService extends SyncedUnlockService { private syncedUnlockStateService: SyncedUnlockStateServiceAbstraction, ) { super(); - timer(0, 1000) + timer(0, SYNC_INTERVAL) .pipe( concatMap(async () => { const isConnected = await this.isConnected(); @@ -47,15 +53,12 @@ export class BackgroundSyncedUnlockService extends SyncedUnlockService { const desktopAccountStatus = await Promise.race([ this.getUserStatusFromDesktop(activeAccount.id), new Promise((resolve) => - setTimeout(() => resolve(AuthenticationStatus.Locked), 5000), + setTimeout(() => resolve(AuthenticationStatus.Locked), STATUS_TIMEOUT), ), ]); const localAccountStatus = await firstValueFrom( this.authService.authStatusFor$(activeAccount.id), ); - this.logService.info( - `Synced unlock: ${activeAccount.id} - Desktop: ${desktopAccountStatus} - Local: ${localAccountStatus}`, - ); if ( desktopAccountStatus === AuthenticationStatus.Locked && localAccountStatus === AuthenticationStatus.Unlocked @@ -66,9 +69,16 @@ export class BackgroundSyncedUnlockService extends SyncedUnlockService { desktopAccountStatus === AuthenticationStatus.Unlocked && localAccountStatus === AuthenticationStatus.Locked ) { + this.logService.info("Asking for user key from desktop"); const userKey = await this.getUserKeyFromDesktop(activeAccount.id); - if (userKey) { + if (userKey != null) { await this.keyService.setUserKey(userKey, activeAccount.id); + } else { + this.hasTrustDenied = true; + // this means the user has denied access to the key on connection fingerprint verification + // Wait 30 seconds + await new Promise((resolve) => setTimeout(resolve, TRUST_DENIED_TIMEOUT)); + this.hasTrustDenied = false; } } } @@ -117,4 +127,8 @@ export class BackgroundSyncedUnlockService extends SyncedUnlockService { command: SyncedUnlockStateCommands.FocusDesktopApp, }); } + + async isConnectionTrusted(): Promise { + return !this.hasTrustDenied && this.isConnected(); + } } diff --git a/apps/browser/src/key-management/synced-unlock/foreground-synced-unlock.service.ts b/apps/browser/src/key-management/synced-unlock/foreground-synced-unlock.service.ts index 0e229e8113a..44d6d290341 100644 --- a/apps/browser/src/key-management/synced-unlock/foreground-synced-unlock.service.ts +++ b/apps/browser/src/key-management/synced-unlock/foreground-synced-unlock.service.ts @@ -69,4 +69,15 @@ export class ForegroundSyncedUnlockService extends SyncedUnlockService { throw response.error; } } + + async isConnectionTrusted(): Promise { + const response = await BrowserApi.sendMessageWithResponse<{ + result: boolean; + error: string; + }>(SyncedUnlockStateCommands.IsConnectionTrusted); + if (response.result == null) { + throw response.error; + } + return response.result; + } } diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 5f96e012bf9..c56cb5b7fad 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -276,6 +276,14 @@ export class BiometricMessageHandlerService { case SyncedUnlockStateCommands.GetUserKeyFromDesktop: { if (!(await this.validateFingerprint(appId))) { this.logService.info("[Native Messaging IPC] Fingerprint validation failed."); + return await this.send( + { + command: SyncedUnlockStateCommands.GetUserKeyFromDesktop, + messageId, + response: null, + }, + appId, + ); } const userId = message.userId as UserId; diff --git a/libs/common/src/key-management/synced-unlock/abstractions/synced-unlock.service.ts b/libs/common/src/key-management/synced-unlock/abstractions/synced-unlock.service.ts index 90491a640c8..0c5105e634f 100644 --- a/libs/common/src/key-management/synced-unlock/abstractions/synced-unlock.service.ts +++ b/libs/common/src/key-management/synced-unlock/abstractions/synced-unlock.service.ts @@ -8,4 +8,5 @@ export abstract class SyncedUnlockService { abstract getUserStatusFromDesktop(userId: UserId): Promise; abstract getUserKeyFromDesktop(userId: UserId): Promise; abstract focusDesktopApp(): Promise; + abstract isConnectionTrusted(): Promise; } diff --git a/libs/common/src/key-management/synced-unlock/noop-synced-unlock.service.ts b/libs/common/src/key-management/synced-unlock/noop-synced-unlock.service.ts index 0028c45b7b9..4223486eccc 100644 --- a/libs/common/src/key-management/synced-unlock/noop-synced-unlock.service.ts +++ b/libs/common/src/key-management/synced-unlock/noop-synced-unlock.service.ts @@ -24,4 +24,8 @@ export class NoopSyncedUnlockService extends SyncedUnlockService { focusDesktopApp(): Promise { return Promise.resolve(); } + + isConnectionTrusted(): Promise { + return Promise.resolve(false); + } } diff --git a/libs/key-management-ui/src/lock/components/lock.component.html b/libs/key-management-ui/src/lock/components/lock.component.html index 588ea26b3c5..f0d40565f36 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.html +++ b/libs/key-management-ui/src/lock/components/lock.component.html @@ -16,11 +16,8 @@ - - {{ "lockScreenDesktopNotRunning" | i18n }} - - - {{ "lockScreenDesktopRunningButLoggedOut" | i18n }} + + {{ synchronizedUnlockUnavailabilityReason }} diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index b6ce21cb167..23475de6cd0 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -139,8 +139,10 @@ export class LockComponent implements OnInit, OnDestroy { unlockViaDesktop = false; isDesktopOpen = false; + isDesktopConnectionTrusted = false; activeUserLoggedOut = false; showLocalUnlockOptions = true; + synchronizedUnlockUnavailabilityReason: string = ""; desktopUnlockFormGroup: FormGroup = new FormGroup({}); constructor( @@ -224,14 +226,41 @@ export class LockComponent implements OnInit, OnDestroy { } this.isDesktopOpen = await this.syncedUnlockService.isConnected(); + this.isDesktopConnectionTrusted = await this.syncedUnlockService.isConnectionTrusted(); this.activeUserLoggedOut = (await this.syncedUnlockService.getUserStatusFromDesktop(activeAccount.id)) === AuthenticationStatus.LoggedOut; - this.showLocalUnlockOptions = !( - this.isDesktopOpen && - this.unlockViaDesktop && - !this.activeUserLoggedOut - ); + + // Synchronized unlock not enabled + if (!this.unlockViaDesktop) { + this.showLocalUnlockOptions = true; + this.synchronizedUnlockUnavailabilityReason = ""; + return; + } + + // Synchronized unlock enabled, but cannot be used + if (!this.isDesktopOpen) { + this.showLocalUnlockOptions = true; + this.synchronizedUnlockUnavailabilityReason = this.i18nService.t( + "lockScreenDesktopNotRunning", + ); + return; + } else if (this.activeUserLoggedOut) { + this.showLocalUnlockOptions = true; + this.synchronizedUnlockUnavailabilityReason = this.i18nService.t( + "lockScreenDesktopRunningButLoggedOut", + ); + return; + } else if (!this.isDesktopConnectionTrusted) { + this.showLocalUnlockOptions = true; + this.synchronizedUnlockUnavailabilityReason = this.i18nService.t( + "lockScreenDesktopRunningButNotTrusted", + ); + return; + } + + this.showLocalUnlockOptions = false; + this.synchronizedUnlockUnavailabilityReason = ""; } catch (e) { this.logService.error(e); } diff --git a/libs/key-management/src/biometrics/synced-unlock-commands.ts b/libs/key-management/src/biometrics/synced-unlock-commands.ts index 48763e3f35d..ecf325a4c21 100644 --- a/libs/key-management/src/biometrics/synced-unlock-commands.ts +++ b/libs/key-management/src/biometrics/synced-unlock-commands.ts @@ -6,4 +6,5 @@ export enum SyncedUnlockStateCommands { GetUserKeyFromDesktop = "getUserKeyFromDesktop", GetUserStatusFromDesktop = "getUserStatusFromDesktop", FocusDesktopApp = "focusDesktopApp", + IsConnectionTrusted = "isConnectionTrusted", }