1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

Use account service for account enumeration. (#9023)

This commit is contained in:
Matt Gibson
2024-05-03 14:24:30 -04:00
committed by GitHub
parent 0b02d2ee1c
commit a4d5717283
7 changed files with 33 additions and 68 deletions

View File

@@ -1,5 +1,3 @@
import { BehaviorSubject } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@@ -15,21 +13,13 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service"; import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
import { BrowserStateService } from "./abstractions/browser-state.service"; import { BrowserStateService } from "./abstractions/browser-state.service";
@browserSession
export class DefaultBrowserStateService export class DefaultBrowserStateService
extends BaseStateService<GlobalState, Account> extends BaseStateService<GlobalState, Account>
implements BrowserStateService implements BrowserStateService
{ {
@sessionSync({
initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account
initializeAs: "record",
})
protected accountsSubject: BehaviorSubject<{ [userId: string]: Account }>;
protected accountDeserializer = Account.fromJSON; protected accountDeserializer = Account.fromJSON;
constructor( constructor(

View File

@@ -218,8 +218,10 @@ export class AppComponent implements OnInit, OnDestroy {
await this.vaultTimeoutService.lock(message.userId); await this.vaultTimeoutService.lock(message.userId);
break; break;
case "lockAllVaults": { case "lockAllVaults": {
const currentUser = await this.stateService.getUserId(); const currentUser = await firstValueFrom(
const accounts = await firstValueFrom(this.stateService.accounts$); this.accountService.activeAccount$.pipe(map((a) => a.id)),
);
const accounts = await firstValueFrom(this.accountService.accounts$);
await this.vaultTimeoutService.lock(currentUser); await this.vaultTimeoutService.lock(currentUser);
for (const account of Object.keys(accounts)) { for (const account of Object.keys(accounts)) {
if (account === currentUser) { if (account === currentUser) {
@@ -690,7 +692,7 @@ export class AppComponent implements OnInit, OnDestroy {
} }
private async checkForSystemTimeout(timeout: number): Promise<void> { private async checkForSystemTimeout(timeout: number): Promise<void> {
const accounts = await firstValueFrom(this.stateService.accounts$); const accounts = await firstValueFrom(this.accountService.accounts$);
for (const userId in accounts) { for (const userId in accounts) {
if (userId == null) { if (userId == null) {
continue; continue;

View File

@@ -221,7 +221,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({ safeProvider({
provide: EncryptedMessageHandlerService, provide: EncryptedMessageHandlerService,
deps: [ deps: [
StateServiceAbstraction, AccountServiceAbstraction,
AuthServiceAbstraction, AuthServiceAbstraction,
CipherServiceAbstraction, CipherServiceAbstraction,
PolicyServiceAbstraction, PolicyServiceAbstraction,

View File

@@ -1,12 +1,13 @@
import { firstValueFrom } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -28,7 +29,7 @@ import { UserStatusErrorResponse } from "../models/native-messaging/encrypted-me
export class EncryptedMessageHandlerService { export class EncryptedMessageHandlerService {
constructor( constructor(
private stateService: StateService, private accountService: AccountService,
private authService: AuthService, private authService: AuthService,
private cipherService: CipherService, private cipherService: CipherService,
private policyService: PolicyService, private policyService: PolicyService,
@@ -62,7 +63,9 @@ export class EncryptedMessageHandlerService {
} }
private async checkUserStatus(userId: string): Promise<string> { private async checkUserStatus(userId: string): Promise<string> {
const activeUserId = await this.stateService.getUserId(); const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (userId !== activeUserId) { if (userId !== activeUserId) {
return "not-active-user"; return "not-active-user";
@@ -77,17 +80,19 @@ export class EncryptedMessageHandlerService {
} }
private async statusCommandHandler(): Promise<AccountStatusResponse[]> { private async statusCommandHandler(): Promise<AccountStatusResponse[]> {
const accounts = await firstValueFrom(this.stateService.accounts$); const accounts = await firstValueFrom(this.accountService.accounts$);
const activeUserId = await this.stateService.getUserId(); const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
if (!accounts || !Object.keys(accounts)) { if (!accounts || !Object.keys(accounts)) {
return []; return [];
} }
return Promise.all( return Promise.all(
Object.keys(accounts).map(async (userId) => { Object.keys(accounts).map(async (userId: UserId) => {
const authStatus = await this.authService.getAuthStatus(userId); const authStatus = await this.authService.getAuthStatus(userId);
const email = await this.stateService.getEmail({ userId }); const email = accounts[userId].email;
return { return {
id: userId, id: userId,
@@ -107,7 +112,9 @@ export class EncryptedMessageHandlerService {
} }
const ciphersResponse: CipherResponse[] = []; const ciphersResponse: CipherResponse[] = [];
const activeUserId = await this.stateService.getUserId(); const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const authStatus = await this.authService.getAuthStatus(activeUserId); const authStatus = await this.authService.getAuthStatus(activeUserId);
if (authStatus !== AuthenticationStatus.Unlocked) { if (authStatus !== AuthenticationStatus.Unlocked) {

View File

@@ -1,6 +1,7 @@
import { Injectable, NgZone } from "@angular/core"; import { Injectable, NgZone } from "@angular/core";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -41,6 +42,7 @@ export class NativeMessagingService {
private biometricStateService: BiometricStateService, private biometricStateService: BiometricStateService,
private nativeMessageHandler: NativeMessageHandlerService, private nativeMessageHandler: NativeMessageHandlerService,
private dialogService: DialogService, private dialogService: DialogService,
private accountService: AccountService,
private ngZone: NgZone, private ngZone: NgZone,
) {} ) {}
@@ -51,9 +53,7 @@ export class NativeMessagingService {
private async messageHandler(msg: LegacyMessageWrapper | Message) { private async messageHandler(msg: LegacyMessageWrapper | Message) {
const outerMessage = msg as Message; const outerMessage = msg as Message;
if (outerMessage.version) { if (outerMessage.version) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.nativeMessageHandler.handleMessage(outerMessage);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.nativeMessageHandler.handleMessage(outerMessage);
return; return;
} }
@@ -64,7 +64,7 @@ export class NativeMessagingService {
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey); const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey);
// Validate the UserId to ensure we are logged into the same account. // Validate the UserId to ensure we are logged into the same account.
const accounts = await firstValueFrom(this.stateService.accounts$); const accounts = await firstValueFrom(this.accountService.accounts$);
const userIds = Object.keys(accounts); const userIds = Object.keys(accounts);
if (!userIds.includes(rawMessage.userId)) { if (!userIds.includes(rawMessage.userId)) {
ipc.platform.nativeMessaging.sendMessage({ ipc.platform.nativeMessaging.sendMessage({
@@ -81,7 +81,7 @@ export class NativeMessagingService {
}); });
const fingerprint = await this.cryptoService.getFingerprint( const fingerprint = await this.cryptoService.getFingerprint(
await this.stateService.getUserId(), rawMessage.userId,
remotePublicKey, remotePublicKey,
); );
@@ -98,9 +98,7 @@ export class NativeMessagingService {
} }
} }
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.secureCommunication(remotePublicKey, appId);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.secureCommunication(remotePublicKey, appId);
return; return;
} }
@@ -144,9 +142,7 @@ export class NativeMessagingService {
? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$) ? firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)
: this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId); : this.biometricStateService.getBiometricUnlockEnabled(message.userId as UserId);
if (!(await biometricUnlockPromise)) { if (!(await biometricUnlockPromise)) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.send({ command: "biometricUnlock", response: "not enabled" }, appId);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.send({ command: "biometricUnlock", response: "not enabled" }, appId);
return this.ngZone.run(() => return this.ngZone.run(() =>
this.dialogService.openSimpleDialog({ this.dialogService.openSimpleDialog({
@@ -172,9 +168,7 @@ export class NativeMessagingService {
// we send the master key still for backwards compatibility // we send the master key still for backwards compatibility
// with older browser extensions // with older browser extensions
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472) // TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3472)
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.send(
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.send(
{ {
command: "biometricUnlock", command: "biometricUnlock",
response: "unlocked", response: "unlocked",
@@ -184,14 +178,10 @@ export class NativeMessagingService {
appId, appId,
); );
} else { } else {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.send({ command: "biometricUnlock", response: "canceled" }, appId);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.send({ command: "biometricUnlock", response: "canceled" }, appId);
} }
} catch (e) { } catch (e) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.send({ command: "biometricUnlock", response: "canceled" }, appId);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.send({ command: "biometricUnlock", response: "canceled" }, appId);
} }
break; break;

View File

@@ -1,5 +1,3 @@
import { Observable } from "rxjs";
import { BiometricKey } from "../../auth/types/biometric-key"; import { BiometricKey } from "../../auth/types/biometric-key";
import { GeneratorOptions } from "../../tools/generator/generator-options"; import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password"; import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
@@ -24,8 +22,6 @@ export type InitOptions = {
}; };
export abstract class StateService<T extends Account = Account> { export abstract class StateService<T extends Account = Account> {
accounts$: Observable<{ [userId: string]: T }>;
addAccount: (account: T) => Promise<void>; addAccount: (account: T) => Promise<void>;
clearDecryptedData: (userId: UserId) => Promise<void>; clearDecryptedData: (userId: UserId) => Promise<void>;
clean: (options?: StorageOptions) => Promise<void>; clean: (options?: StorageOptions) => Promise<void>;

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject, firstValueFrom, map } from "rxjs"; import { firstValueFrom, map } from "rxjs";
import { Jsonify, JsonValue } from "type-fest"; import { Jsonify, JsonValue } from "type-fest";
import { AccountService } from "../../auth/abstractions/account.service"; import { AccountService } from "../../auth/abstractions/account.service";
@@ -52,9 +52,6 @@ export class StateService<
TAccount extends Account = Account, TAccount extends Account = Account,
> implements StateServiceAbstraction<TAccount> > implements StateServiceAbstraction<TAccount>
{ {
protected accountsSubject = new BehaviorSubject<{ [userId: string]: TAccount }>({});
accounts$ = this.accountsSubject.asObservable();
private hasBeenInited = false; private hasBeenInited = false;
protected isRecoveredSession = false; protected isRecoveredSession = false;
@@ -115,8 +112,6 @@ export class StateService<
state = await this.syncAccountFromDisk(authenticatedAccounts[i]); state = await this.syncAccountFromDisk(authenticatedAccounts[i]);
} }
await this.pushAccounts();
return state; return state;
}); });
} }
@@ -153,7 +148,6 @@ export class StateService<
await this.removeAccountFromDisk(options?.userId); await this.removeAccountFromDisk(options?.userId);
await this.removeAccountFromMemory(options?.userId); await this.removeAccountFromMemory(options?.userId);
await this.pushAccounts();
} }
/** /**
@@ -856,7 +850,6 @@ export class StateService<
}); });
}); });
} }
await this.pushAccounts();
} }
protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> { protected async scaffoldNewAccountStorage(account: TAccount): Promise<void> {
@@ -934,17 +927,6 @@ export class StateService<
); );
} }
protected async pushAccounts(): Promise<void> {
await this.state().then((state) => {
if (state.accounts == null || Object.keys(state.accounts).length < 1) {
this.accountsSubject.next({});
return;
}
this.accountsSubject.next(state.accounts);
});
}
protected reconcileOptions( protected reconcileOptions(
requestedOptions: StorageOptions, requestedOptions: StorageOptions,
defaultOptions: StorageOptions, defaultOptions: StorageOptions,
@@ -1096,8 +1078,6 @@ export class StateService<
return state; return state;
}); });
await this.pushAccounts();
} }
protected createAccount(init: Partial<TAccount> = null): TAccount { protected createAccount(init: Partial<TAccount> = null): TAccount {