diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index ca19964619d..90f6bdc32d3 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2162,7 +2162,16 @@
"message": "Too many invalid PIN entry attempts. Logging out."
},
"syncUnlockWithDesktop": {
- "message": "Synchronize unlock state with desktop app."
+ "message": "Unlock with desktop integration"
+ },
+ "lockScreenSynchronizedNote": {
+ "message": "This user's account is synchronized with the desktop app."
+ },
+ "lockScreenContinueInDesktop": {
+ "message": "Continue in desktop app"
+ },
+ "lockScreenDesktopNotRunning": {
+ "message": "This user's account is synchronized with the desktop app, but the desktop app is not running."
},
"unlockWithBiometrics": {
"message": "Unlock with biometrics"
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html
index da3d27b4468..584ef82a72e 100644
--- a/apps/browser/src/auth/popup/settings/account-security.component.html
+++ b/apps/browser/src/auth/popup/settings/account-security.component.html
@@ -11,7 +11,7 @@
{{ "unlockMethods" | i18n }}
-
+
{{
@@ -37,7 +36,7 @@
@@ -63,8 +62,7 @@
disableMargin
*ngIf="
this.form.value.pin &&
- showMasterPasswordOnClientRestartOption &&
- !this.form.value.syncUnlockWithDesktop
+ showMasterPasswordOnClientRestartOption
"
>
-
+
{{ "vaultTimeoutAction1" | i18n }}
{{ "vaultTimeoutPolicyAffectingOptions" | i18n }}
-
- The desktop app's vault timeout settings will be used to lock the vault on this device.
-
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts
index a9b044c5b3b..7ef126d26e5 100644
--- a/apps/browser/src/auth/popup/settings/account-security.component.ts
+++ b/apps/browser/src/auth/popup/settings/account-security.component.ts
@@ -9,7 +9,6 @@ import {
combineLatest,
concatMap,
distinctUntilChanged,
- filter,
firstValueFrom,
map,
Observable,
@@ -250,11 +249,35 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
takeUntil(this.destroy$),
)
.subscribe();
+ this.syncedUnlockStateService.syncedUnlockEnabled$
+ .pipe(
+ map((enabled) => {
+ if (enabled) {
+ this.form.controls.pin.disable({ emitEvent: false });
+ this.form.controls.pinLockWithMasterPassword.disable({ emitEvent: false });
+ this.form.controls.biometric.disable({ emitEvent: false });
+ this.form.controls.enableAutoBiometricsPrompt.disable({ emitEvent: false });
+ this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
+ } else {
+ this.form.controls.pin.enable({ emitEvent: false });
+ this.form.controls.pinLockWithMasterPassword.enable({ emitEvent: false });
+ this.form.controls.biometric.enable({ emitEvent: false });
+ this.form.controls.enableAutoBiometricsPrompt.enable({ emitEvent: false });
+ this.form.controls.vaultTimeoutAction.enable({ emitEvent: false });
+ }
+ }),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
timer(0, 1000)
.pipe(
- filter(() => !this.form.controls.syncUnlockWithDesktop.value),
switchMap(async () => {
+ if (this.form.controls.syncUnlockWithDesktop.value) {
+ this.form.controls.biometric.disable({ emitEvent: false });
+ return;
+ }
+
const status = await this.biometricsService.getBiometricsStatusForUser(activeAccount.id);
const biometricSettingAvailable = await this.biometricsService.canEnableBiometricUnlock();
if (!biometricSettingAvailable) {
@@ -394,6 +417,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
takeUntil(this.destroy$),
)
.subscribe(([availableActions, policy]) => {
+ if (this.form.controls.syncUnlockWithDesktop.value) {
+ this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
+ return;
+ }
+
if (policy?.data?.action || availableActions.length <= 1) {
this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
} else {
@@ -403,6 +431,10 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
}
async saveVaultTimeout(previousValue: VaultTimeout, newValue: VaultTimeout) {
+ if (newValue == null) {
+ return;
+ }
+
if (newValue === VaultTimeoutStringType.Never) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "warning" },
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 f7f5c80ddc8..5c16f662123 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
@@ -5,7 +5,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { SyncedUnlockService } from "@bitwarden/common/key-management/synced-unlock/abstractions/synced-unlock.service";
-import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
+import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout/services/vault-timeout.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserId } from "@bitwarden/common/types/guid";
@@ -33,7 +33,10 @@ export class BackgroundSyncedUnlockService extends SyncedUnlockService {
timer(0, 1000)
.pipe(
concatMap(async () => {
- if (this.nativeMessagingBackground().connected) {
+ const isConnected = await this.isConnected();
+ // Needed to resolve dependency cycle
+ this.vaultTimeoutService.setDesktopAppConnected(isConnected);
+ if (isConnected) {
if (!(await firstValueFrom(this.syncedUnlockStateService.syncedUnlockEnabled$))) {
return;
}
diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
index 82bc53bb147..874b87fbcc2 100644
--- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
+++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
@@ -14,7 +14,7 @@ import {
ValidationErrors,
Validator,
} from "@angular/forms";
-import { filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs";
+import { filter, map, Observable, Subject, switchMap, take, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -110,6 +110,7 @@ export class VaultTimeoutInputComponent
});
@Input() vaultTimeoutOptions: VaultTimeoutOption[];
+ @Input() disabled: boolean;
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
@@ -189,6 +190,12 @@ export class VaultTimeoutInputComponent
}
ngOnChanges() {
+ if (this.disabled) {
+ this.form.disable();
+ } else {
+ this.form.enable();
+ }
+
if (
!this.vaultTimeoutOptions.find((p) => p.value === VaultTimeoutInputComponent.CUSTOM_VALUE)
) {
diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
index 1bf07fbc944..6be6571317d 100644
--- a/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
+++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout.service.ts
@@ -21,12 +21,14 @@ import { UserId } from "../../../types/guid";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
+import { SyncedUnlockService } from "../../synced-unlock/abstractions/synced-unlock.service";
import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout-settings.service";
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vault-timeout.service";
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private inited = false;
+ private isDesktopAppConnected = false;
constructor(
private accountService: AccountService,
@@ -44,7 +46,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
private taskSchedulerService: TaskSchedulerService,
protected logService: LogService,
private biometricService: BiometricsService,
- private syncedUnlockService: SyncedUnlockStateServiceAbstraction,
+ private syncedUnlockStateService: SyncedUnlockStateServiceAbstraction,
private lockedCallback: (userId?: string) => Promise = null,
private loggedOutCallback: (
logoutReason: LogoutReason,
@@ -76,10 +78,16 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
);
}
+ // This is needed to prevent dependency cycle between vault timeout and synced unlock service
+ setDesktopAppConnected(isConnected: boolean): void {
+ this.isDesktopAppConnected = isConnected;
+ }
+
async checkVaultTimeout(): Promise {
if (
- (await firstValueFrom(this.syncedUnlockService.syncedUnlockEnabled$)) &&
- this.platformUtilsService.getClientType() === ClientType.Browser
+ (await firstValueFrom(this.syncedUnlockStateService.syncedUnlockEnabled$)) &&
+ this.platformUtilsService.getClientType() === ClientType.Browser &&
+ this.isDesktopAppConnected
) {
return;
}
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 46817e2168a..b56a2ea2ff9 100644
--- a/libs/key-management-ui/src/lock/components/lock.component.html
+++ b/libs/key-management-ui/src/lock/components/lock.component.html
@@ -8,18 +8,17 @@
-
- This account has unlock synchronization enabled, but the desktop app is not running.
+
+ {{ "lockScreenDesktopNotRunning" | i18n }}
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 bcf9ebc25e7..b4ab3b5fce4 100644
--- a/libs/key-management-ui/src/lock/components/lock.component.ts
+++ b/libs/key-management-ui/src/lock/components/lock.component.ts
@@ -138,7 +138,7 @@ export class LockComponent implements OnInit, OnDestroy {
unlockViaDesktop = false;
isDesktopOpen = false;
- showLocalUnlockOptions = false;
+ showLocalUnlockOptions = true;
desktopUnlockFormGroup: FormGroup = new FormGroup({});
constructor(
@@ -189,6 +189,9 @@ export class LockComponent implements OnInit, OnDestroy {
await this.desktopOnInit();
} else if (this.clientType === ClientType.Browser) {
this.biometricUnlockBtnText = this.lockComponentService.getBiometricsUnlockBtnText();
+ if (await firstValueFrom(this.syncedUnlockStateService.syncedUnlockEnabled$)) {
+ this.showLocalUnlockOptions = false;
+ }
}
}