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:
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: EncryptedMessageHandlerService,
|
provide: EncryptedMessageHandlerService,
|
||||||
deps: [
|
deps: [
|
||||||
StateServiceAbstraction,
|
AccountServiceAbstraction,
|
||||||
AuthServiceAbstraction,
|
AuthServiceAbstraction,
|
||||||
CipherServiceAbstraction,
|
CipherServiceAbstraction,
|
||||||
PolicyServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user