mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 15:23:33 +00:00
[PM-6688] Use AccountService as account source (#8893)
* Use account service to track accounts and active account * Remove state service active account Observables. * Add email verified to account service * Do not store account info on logged out accounts * Add account activity tracking to account service * Use last account activity from account service * migrate or replicate account service data * Add `AccountActivityService` that handles storing account last active data * Move active and next active user to account service * Remove authenticated accounts from state object * Fold account activity into account service * Fix builds * Fix desktop app switch * Fix logging out non active user * Expand helper to handle new authenticated accounts location * Prefer view observable to tons of async pipes * Fix `npm run test:types` * Correct user activity sorting test * Be more precise about log out messaging * Fix dev compare errors All stored values are serializable, the next step wasn't necessary and was erroring on some types that lack `toString`. * If the account in unlocked on load of lock component, navigate away from lock screen * Handle no users case for auth service statuses * Specify account to switch to * Filter active account out of inactive accounts * Prefer constructor init * Improve comparator * Use helper methods internally * Fixup component tests * Clarify name * Ensure accounts object has only valid userIds * Capitalize const values * Prefer descriptive, single-responsibility guards * Update libs/common/src/state-migrations/migrate.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix merge * Add user Id validation activity for undefined was being set, which was resulting in requests for the auth status of `"undefined"` (string) userId, due to key enumeration. These changes stop that at both locations, as well as account add for good measure. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { MockProxy, any, mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
import { AccountInfo } from "../../auth/abstractions/account.service";
|
||||
import { AuthService } from "../../auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service";
|
||||
@@ -13,7 +14,6 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "../../platform/abstractions/state.service";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
import { Account } from "../../platform/models/domain/account";
|
||||
import { StateEventRunnerService } from "../../platform/state";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||
@@ -39,7 +39,6 @@ describe("VaultTimeoutService", () => {
|
||||
let lockedCallback: jest.Mock<Promise<void>, [userId: string]>;
|
||||
let loggedOutCallback: jest.Mock<Promise<void>, [expired: boolean, userId?: string]>;
|
||||
|
||||
let accountsSubject: BehaviorSubject<Record<string, Account>>;
|
||||
let vaultTimeoutActionSubject: BehaviorSubject<VaultTimeoutAction>;
|
||||
let availableVaultTimeoutActionsSubject: BehaviorSubject<VaultTimeoutAction[]>;
|
||||
|
||||
@@ -65,10 +64,6 @@ describe("VaultTimeoutService", () => {
|
||||
lockedCallback = jest.fn();
|
||||
loggedOutCallback = jest.fn();
|
||||
|
||||
accountsSubject = new BehaviorSubject(null);
|
||||
|
||||
stateService.accounts$ = accountsSubject;
|
||||
|
||||
vaultTimeoutActionSubject = new BehaviorSubject(VaultTimeoutAction.Lock);
|
||||
|
||||
vaultTimeoutSettingsService.vaultTimeoutAction$.mockReturnValue(vaultTimeoutActionSubject);
|
||||
@@ -127,21 +122,39 @@ describe("VaultTimeoutService", () => {
|
||||
return Promise.resolve(accounts[userId]?.vaultTimeout);
|
||||
});
|
||||
|
||||
stateService.getLastActive.mockImplementation((options) => {
|
||||
return Promise.resolve(accounts[options.userId]?.lastActive);
|
||||
});
|
||||
|
||||
stateService.getUserId.mockResolvedValue(globalSetups?.userId);
|
||||
|
||||
stateService.activeAccount$ = new BehaviorSubject<string>(globalSetups?.userId);
|
||||
|
||||
// Set desired user active and known users on accounts service : note the only thing that matters here is that the ID are set
|
||||
if (globalSetups?.userId) {
|
||||
accountService.activeAccountSubject.next({
|
||||
id: globalSetups.userId as UserId,
|
||||
email: null,
|
||||
emailVerified: false,
|
||||
name: null,
|
||||
});
|
||||
}
|
||||
accountService.accounts$ = of(
|
||||
Object.entries(accounts).reduce(
|
||||
(agg, [id]) => {
|
||||
agg[id] = {
|
||||
email: "",
|
||||
emailVerified: true,
|
||||
name: "",
|
||||
};
|
||||
return agg;
|
||||
},
|
||||
{} as Record<string, AccountInfo>,
|
||||
),
|
||||
);
|
||||
accountService.accountActivity$ = of(
|
||||
Object.entries(accounts).reduce(
|
||||
(agg, [id, info]) => {
|
||||
agg[id] = info.lastActive ? new Date(info.lastActive) : null;
|
||||
return agg;
|
||||
},
|
||||
{} as Record<string, Date>,
|
||||
),
|
||||
);
|
||||
|
||||
platformUtilsService.isViewOpen.mockResolvedValue(globalSetups?.isViewOpen ?? false);
|
||||
|
||||
@@ -158,16 +171,6 @@ describe("VaultTimeoutService", () => {
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
const accountsSubjectValue: Record<string, Account> = Object.keys(accounts).reduce(
|
||||
(agg, key) => {
|
||||
const newPartial: Record<string, unknown> = {};
|
||||
newPartial[key] = null; // No values actually matter on this other than the key
|
||||
return Object.assign(agg, newPartial);
|
||||
},
|
||||
{} as Record<string, Account>,
|
||||
);
|
||||
accountsSubject.next(accountsSubjectValue);
|
||||
};
|
||||
|
||||
const expectUserToHaveLocked = (userId: string) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { firstValueFrom, timeout } from "rxjs";
|
||||
import { combineLatest, firstValueFrom, switchMap } from "rxjs";
|
||||
|
||||
import { SearchService } from "../../abstractions/search.service";
|
||||
import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/vault-timeout-settings.service";
|
||||
@@ -64,14 +64,25 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
// Get whether or not the view is open a single time so it can be compared for each user
|
||||
const isViewOpen = await this.platformUtilsService.isViewOpen();
|
||||
|
||||
const activeUserId = await firstValueFrom(this.stateService.activeAccount$.pipe(timeout(500)));
|
||||
|
||||
const accounts = await firstValueFrom(this.stateService.accounts$);
|
||||
for (const userId in accounts) {
|
||||
if (userId != null && (await this.shouldLock(userId, activeUserId, isViewOpen))) {
|
||||
await this.executeTimeoutAction(userId);
|
||||
}
|
||||
}
|
||||
await firstValueFrom(
|
||||
combineLatest([
|
||||
this.accountService.activeAccount$,
|
||||
this.accountService.accountActivity$,
|
||||
]).pipe(
|
||||
switchMap(async ([activeAccount, accountActivity]) => {
|
||||
const activeUserId = activeAccount?.id;
|
||||
for (const userIdString in accountActivity) {
|
||||
const userId = userIdString as UserId;
|
||||
if (
|
||||
userId != null &&
|
||||
(await this.shouldLock(userId, accountActivity[userId], activeUserId, isViewOpen))
|
||||
) {
|
||||
await this.executeTimeoutAction(userId);
|
||||
}
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async lock(userId?: string): Promise<void> {
|
||||
@@ -123,6 +134,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
|
||||
private async shouldLock(
|
||||
userId: string,
|
||||
lastActive: Date,
|
||||
activeUserId: string,
|
||||
isViewOpen: boolean,
|
||||
): Promise<boolean> {
|
||||
@@ -146,13 +158,12 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lastActive = await this.stateService.getLastActive({ userId: userId });
|
||||
if (lastActive == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const vaultTimeoutSeconds = vaultTimeout * 60;
|
||||
const diffSeconds = (new Date().getTime() - lastActive) / 1000;
|
||||
const diffSeconds = (new Date().getTime() - lastActive.getTime()) / 1000;
|
||||
return diffSeconds >= vaultTimeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user