1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-07 20:24:01 +00:00

Apply more fixes

This commit is contained in:
Bernd Schoolmann
2025-05-30 14:30:04 +02:00
parent ab5eb30929
commit 8a4feb4b5b
10 changed files with 88 additions and 16 deletions

View File

@@ -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"
},

View File

@@ -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,

View File

@@ -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<boolean> {
return !this.hasTrustDenied && this.isConnected();
}
}

View File

@@ -69,4 +69,15 @@ export class ForegroundSyncedUnlockService extends SyncedUnlockService {
throw response.error;
}
}
async isConnectionTrusted(): Promise<boolean> {
const response = await BrowserApi.sendMessageWithResponse<{
result: boolean;
error: string;
}>(SyncedUnlockStateCommands.IsConnectionTrusted);
if (response.result == null) {
throw response.error;
}
return response.result;
}
}

View File

@@ -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;

View File

@@ -8,4 +8,5 @@ export abstract class SyncedUnlockService {
abstract getUserStatusFromDesktop(userId: UserId): Promise<AuthenticationStatus>;
abstract getUserKeyFromDesktop(userId: UserId): Promise<UserKey | null>;
abstract focusDesktopApp(): Promise<void>;
abstract isConnectionTrusted(): Promise<boolean>;
}

View File

@@ -24,4 +24,8 @@ export class NoopSyncedUnlockService extends SyncedUnlockService {
focusDesktopApp(): Promise<void> {
return Promise.resolve();
}
isConnectionTrusted(): Promise<boolean> {
return Promise.resolve(false);
}
}

View File

@@ -16,11 +16,8 @@
</form>
</ng-container>
<ng-container *ngIf="showLocalUnlockOptions">
<bit-hint class="tw-text-center tw-mb-3" *ngIf="unlockViaDesktop && !activeUserLoggedOut">
{{ "lockScreenDesktopNotRunning" | i18n }}
</bit-hint>
<bit-hint class="tw-text-center tw-mb-3" *ngIf="unlockViaDesktop && activeUserLoggedOut">
{{ "lockScreenDesktopRunningButLoggedOut" | i18n }}
<bit-hint class="tw-text-center tw-mb-3" *ngIf="unlockViaDesktop">
{{ synchronizedUnlockUnavailabilityReason }}
</bit-hint>
<!-- Biometrics Unlock -->

View File

@@ -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);
}

View File

@@ -6,4 +6,5 @@ export enum SyncedUnlockStateCommands {
GetUserKeyFromDesktop = "getUserKeyFromDesktop",
GetUserStatusFromDesktop = "getUserStatusFromDesktop",
FocusDesktopApp = "focusDesktopApp",
IsConnectionTrusted = "isConnectionTrusted",
}