diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts
index 8ff79c7a151..895ff347f24 100644
--- a/apps/desktop/src/app/services/services.module.ts
+++ b/apps/desktop/src/app/services/services.module.ts
@@ -12,6 +12,7 @@ import {
OBSERVABLE_DISK_STORAGE,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
+import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
@@ -114,6 +115,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
PlatformUtilsServiceAbstraction,
RELOAD_CALLBACK,
StateServiceAbstraction,
+ VaultTimeoutSettingsService,
],
},
{
diff --git a/libs/angular/src/auth/components/set-pin.component.ts b/libs/angular/src/auth/components/set-pin.component.ts
index 2190b111e00..6b5bd40fd35 100644
--- a/libs/angular/src/auth/components/set-pin.component.ts
+++ b/libs/angular/src/auth/components/set-pin.component.ts
@@ -3,7 +3,6 @@ import { Directive, OnInit } from "@angular/core";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { KeySuffixOptions } from "@bitwarden/common/platform/enums/key-suffix-options.enum";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ModalRef } from "../../components/modal/modal.ref";
@@ -52,7 +51,6 @@ export class SetPinComponent implements OnInit {
} else {
await this.stateService.setPinKeyEncryptedUserKey(pinProtectedKey);
}
- await this.cryptoService.clearDeprecatedKeys(KeySuffixOptions.Pin);
this.modalRef.close(true);
}
diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts
index 542d4ec5214..987e13c2ea5 100644
--- a/libs/common/src/platform/abstractions/state.service.ts
+++ b/libs/common/src/platform/abstractions/state.service.ts
@@ -547,4 +547,5 @@ export abstract class StateService {
* @param options Defines the storage options for the URL; Defaults to session Storage.
*/
setDeepLinkRedirectUrl: (url: string, options?: StorageOptions) => Promise;
+ nextUpActiveUser: () => Promise;
}
diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts
index 65b9ef6ae11..740afe5592b 100644
--- a/libs/common/src/platform/services/state.service.ts
+++ b/libs/common/src/platform/services/state.service.ts
@@ -96,7 +96,7 @@ export class StateService<
activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable();
private hasBeenInited = false;
- private isRecoveredSession = false;
+ protected isRecoveredSession = false;
protected accountDiskCache = new BehaviorSubject>({});
@@ -159,7 +159,7 @@ export class StateService<
(await this.storageService.get(keys.authenticatedAccounts)) ?? [];
for (const i in state.authenticatedAccounts) {
if (i != null) {
- await this.syncAccountFromDisk(state.authenticatedAccounts[i]);
+ state = await this.syncAccountFromDisk(state.authenticatedAccounts[i]);
}
}
const storedActiveUser = await this.storageService.get(keys.activeUserId);
@@ -186,25 +186,37 @@ export class StateService<
});
}
- async syncAccountFromDisk(userId: string) {
+ async syncAccountFromDisk(userId: string): Promise> {
if (userId == null) {
return;
}
- await this.updateState(async (state) => {
+ const diskAccount = await this.getAccountFromDisk({ userId: userId });
+ const state = await this.updateState(async (state) => {
if (state.accounts == null) {
state.accounts = {};
}
state.accounts[userId] = this.createAccount();
- const diskAccount = await this.getAccountFromDisk({ userId: userId });
state.accounts[userId].profile = diskAccount.profile;
- // TODO: Temporary update to avoid routing all account status changes through account service for now.
- await this.accountService.addAccount(userId as UserId, {
- status: AuthenticationStatus.Locked,
- name: diskAccount.profile.name,
- email: diskAccount.profile.email,
- });
return state;
});
+
+ // TODO: Temporary update to avoid routing all account status changes through account service for now.
+ // The determination of state should be handled by the various services that control those values.
+ const token = await this.getAccessToken({ userId: userId });
+ const autoKey = await this.getUserKeyAutoUnlock({ userId: userId });
+ const accountStatus =
+ token == null
+ ? AuthenticationStatus.LoggedOut
+ : autoKey == null
+ ? AuthenticationStatus.Locked
+ : AuthenticationStatus.Unlocked;
+ await this.accountService.addAccount(userId as UserId, {
+ status: accountStatus,
+ name: diskAccount.profile.name,
+ email: diskAccount.profile.email,
+ });
+
+ return state;
}
async addAccount(account: TAccount) {
@@ -3033,7 +3045,7 @@ export class StateService<
}
protected async saveAccountToMemory(account: TAccount): Promise {
- if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) {
+ if ((await this.getAccountFromMemory({ userId: account.profile.userId })) !== null) {
await this.updateState((state) => {
return new Promise((resolve) => {
state.accounts[account.profile.userId] = account;
@@ -3320,10 +3332,9 @@ export class StateService<
await this.removeAccountFromSecureStorage(userId);
}
- protected async dynamicallySetActiveUser() {
+ async nextUpActiveUser() {
const accounts = (await this.state())?.accounts;
if (accounts == null || Object.keys(accounts).length < 1) {
- await this.setActiveUser(null);
return null;
}
@@ -3338,6 +3349,11 @@ export class StateService<
}
newActiveUser = null;
}
+ return newActiveUser as UserId;
+ }
+
+ protected async dynamicallySetActiveUser() {
+ const newActiveUser = await this.nextUpActiveUser();
await this.setActiveUser(newActiveUser);
return newActiveUser;
}
@@ -3369,20 +3385,23 @@ export class StateService<
return state;
}
- private async setState(state: State): Promise {
+ private async setState(
+ state: State,
+ ): Promise> {
await this.memoryStorageService.save(keys.state, state);
+ return state;
}
protected async updateState(
stateUpdater: (state: State) => Promise>,
- ) {
- await this.state().then(async (state) => {
+ ): Promise> {
+ return await this.state().then(async (state) => {
const updatedState = await stateUpdater(state);
if (updatedState == null) {
throw new Error("Attempted to update state to null value");
}
- await this.setState(updatedState);
+ return await this.setState(updatedState);
});
}
diff --git a/libs/common/src/platform/services/system.service.ts b/libs/common/src/platform/services/system.service.ts
index 5e5d838156d..beb37be73ab 100644
--- a/libs/common/src/platform/services/system.service.ts
+++ b/libs/common/src/platform/services/system.service.ts
@@ -1,7 +1,9 @@
-import { firstValueFrom } from "rxjs";
+import { firstValueFrom, timeout } from "rxjs";
+import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
import { AuthService } from "../../auth/abstractions/auth.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
+import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
import { MessagingService } from "../abstractions/messaging.service";
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { StateService } from "../abstractions/state.service";
@@ -18,6 +20,7 @@ export class SystemService implements SystemServiceAbstraction {
private platformUtilsService: PlatformUtilsService,
private reloadCallback: () => Promise = null,
private stateService: StateService,
+ private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
) {}
async startProcessReload(authService: AuthService): Promise {
@@ -54,6 +57,19 @@ export class SystemService implements SystemServiceAbstraction {
if (!biometricLockedFingerprintValidated) {
clearInterval(this.reloadInterval);
this.reloadInterval = null;
+
+ const currentUser = await firstValueFrom(this.stateService.activeAccount$.pipe(timeout(500)));
+ // Replace current active user if they will be logged out on reload
+ if (currentUser != null) {
+ const timeoutAction = await firstValueFrom(
+ this.vaultTimeoutSettingsService.vaultTimeoutAction$().pipe(timeout(500)),
+ );
+ if (timeoutAction === VaultTimeoutAction.LogOut) {
+ const nextUser = await this.stateService.nextUpActiveUser();
+ await this.stateService.setActiveUser(nextUser);
+ }
+ }
+
this.messagingService.send("reloadProcess");
if (this.reloadCallback != null) {
await this.reloadCallback();
diff --git a/libs/components/tailwind.config.js b/libs/components/tailwind.config.js
index b9a0cab3c05..987b969e8f0 100644
--- a/libs/components/tailwind.config.js
+++ b/libs/components/tailwind.config.js
@@ -3,6 +3,7 @@ const config = require("./tailwind.config.base");
config.content = [
"libs/components/src/**/*.{html,ts,mdx}",
+ "libs/auth/src/**/*.{html,ts,mdx}",
"apps/web/src/**/*.{html,ts,mdx}",
"bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
".storybook/preview.tsx",