diff --git a/common/src/abstractions/state.service.ts b/common/src/abstractions/state.service.ts index 02140f24..3cbdd594 100644 --- a/common/src/abstractions/state.service.ts +++ b/common/src/abstractions/state.service.ts @@ -27,7 +27,6 @@ import { SendView } from '../models/view/sendView'; export abstract class StateService { accounts: BehaviorSubject<{ [userId: string]: Account }>; - init: () => Promise; addAccount: (account: Account) => Promise; setActiveUser: (userId: string) => Promise; clean: (options?: StorageOptions) => Promise; diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index 77fa9f30..e2fb8766 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -126,7 +126,8 @@ export class Account { convertAccountToKeyConnector: boolean; usesKeyConnector: boolean; enableFullWidth: boolean; - hasPremiumPersonally: boolean; + + private hasPremiumPersonally: boolean; constructor(userId: string, userEmail: string, kdfType: KdfType, kdfIterations: number, @@ -144,8 +145,39 @@ export class Account { this.hasPremiumPersonally = hasPremium; } + get isAuthenticated(): boolean { + if (!this.accessToken) { + return false; + } + + return this.userId != null; + } + + get canAccessPremium(): boolean { + if (!this.isAuthenticated) { + return false; + } + + return this.hasPremiumPersonally || this.hasPremiumThroughOrganization; + } + get serverUrl(): string { return this.environmentUrls?.base ?? 'bitwarden.com'; } + + private get hasPremiumThroughOrganization(): boolean { + if (this.organizations == null) { + return false; + } + + for (const id of Object.keys(this.organizations)) { + const o = this.organizations[id]; + if (o.enabled && o.usersGetPremium && !o.isProviderUser) { + return true; + } + } + + return false; + } } diff --git a/common/src/models/domain/state.ts b/common/src/models/domain/state.ts index a0099e5d..9a0a32c6 100644 --- a/common/src/models/domain/state.ts +++ b/common/src/models/domain/state.ts @@ -3,7 +3,7 @@ import { GlobalState } from './globalState'; export class State { accounts: { [userId: string]: Account } = {}; - globals: GlobalState = new GlobalState(); + globals: GlobalState; activeUserId: string; } diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 389ebcba..5d3a804d 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -63,17 +63,6 @@ export class StateService implements StateServiceAbstraction { private logService: LogService) { } - async init(): Promise { - if (this.state.activeUserId == null) { - await this.loadStateFromDisk(); - } - } - - async loadStateFromDisk() { - const diskState = await this.storageService.get('state', this.defaultOnDiskLocalOptions); - this.state = diskState; - } - async addAccount(account: Account) { this.state.accounts[account.userId] = account; await this.scaffoldNewAccountStorage(account); @@ -81,10 +70,10 @@ export class StateService implements StateServiceAbstraction { } async setActiveUser(userId: string): Promise { + if (!this.state.accounts[userId]) { + return; + } this.state.activeUserId = userId; - const storedState = await this.storageService.get('state', this.defaultOnDiskLocalOptions); - storedState.activeUserId = userId; - await this.storageService.save('state', storedState, this.defaultOnDiskLocalOptions); await this.pushAccounts(); } @@ -96,13 +85,13 @@ export class StateService implements StateServiceAbstraction { } async getAccessToken(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultOnDiskLocalOptions)))?.accessToken; + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.accessToken; } async setAccessToken(value: string, options?: StorageOptions): Promise { - const account = await this.getAccount(this.reconcileOptions(options, this.defaultOnDiskLocalOptions)); + const account = await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)); account.accessToken = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultOnDiskLocalOptions)); + await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); } async getAddEditCipherInfo(options?: StorageOptions): Promise { @@ -216,28 +205,7 @@ export class StateService implements StateServiceAbstraction { } async getCanAccessPremium(options?: StorageOptions): Promise { - if (!await this.getIsAuthenticated(options)) { - return false; - } - - const account = await this.getAccount(this.reconcileOptions(options, this.defaultOnDiskLocalOptions)); - if (account.hasPremiumPersonally) { - return true; - } - - const organizations = await this.getOrganizations(options); - if (organizations == null) { - return false; - } - - for (const id of Object.keys(organizations)) { - const o = organizations[id]; - if (o.enabled && o.usersGetPremium && !o.isProviderUser) { - return true; - } - } - - return false; + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.canAccessPremium ?? false; } async getClearClipboard(options?: StorageOptions): Promise { @@ -832,8 +800,7 @@ export class StateService implements StateServiceAbstraction { } async getIsAuthenticated(options?: StorageOptions): Promise { - const account = (await this.getAccount(this.reconcileOptions(options, this.defaultOnDiskLocalOptions))); - return account != null && account?.accessToken != null && account?.userId != null; + return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.isAuthenticated ?? false; } async getKdfIterations(options?: StorageOptions): Promise { @@ -1197,7 +1164,7 @@ export class StateService implements StateServiceAbstraction { } private async getGlobalsFromDisk(options: StorageOptions): Promise { - return (await this.storageService.get('state', options))?.globals; + return await this.storageService.get('globals', options); } private saveGlobalsToMemory(globals: GlobalState): void { @@ -1206,17 +1173,16 @@ export class StateService implements StateServiceAbstraction { private async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { if (options.useSecureStorage) { - const state = await this.secureStorageService.get('state', options) ?? new State(); - state.globals = globals; - await this.secureStorageService.save('state', state, options); + await this.secureStorageService.save('globals', globals, options); } else { - const state = await this.storageService.get('state', options) ?? new State(); - state.globals = globals; - await this.storageService.save('state', state, options); + await this.storageService.save('globals', globals, options); } } - private async getAccount(options: StorageOptions): Promise { + private async getAccount(options: StorageOptions = { + storageLocation: StorageLocation.Both, + useSecureStorage: false, + }): Promise { try { let account: Account; if (this.useMemory(options.storageLocation)) { @@ -1252,11 +1218,10 @@ export class StateService implements StateServiceAbstraction { return null; } - const state = options?.useSecureStorage ? - await this.secureStorageService.get('state', options) : - await this.storageService.get('state', options); - - return state.accounts[options?.userId ?? this.state.activeUserId]; + const account = options?.useSecureStorage ? + await this.secureStorageService.get(options.userId ?? this.state.activeUserId, options) : + await this.storageService.get(options.userId ?? this.state.activeUserId, options); + return account; } private useMemory(storageLocation: StorageLocation) { @@ -1279,14 +1244,11 @@ export class StateService implements StateServiceAbstraction { } private async saveAccountToDisk(account: Account, options: StorageOptions): Promise { - const storageLocation = options.useSecureStorage ? - this.secureStorageService : - this.storageService; - - const state = await storageLocation.get('state', options); - state.accounts[account.userId] = account; - - await storageLocation.save('state', state, options); + if (options.useSecureStorage) { + await this.secureStorageService.save(account.userId, account, options); + } else { + await this.storageService.save(account.userId, account, options); + } await this.pushAccounts(); } @@ -1298,20 +1260,18 @@ export class StateService implements StateServiceAbstraction { } private async scaffoldNewAccountStorage(account: Account): Promise { - const storedState = await this.storageService.get('state', this.defaultOnDiskLocalOptions) ?? new State(); - const storedAccount = storedState.accounts[account.userId]; - if (storedAccount != null) { - storedAccount.accessToken = account.accessToken; - storedAccount.refreshToken = account.refreshToken; - account = storedAccount; + const storageAccount = await this.storageService.get(account.userId, this.defaultOnDiskLocalOptions); + if (storageAccount != null) { + storageAccount.accessToken = account.accessToken; + storageAccount.refreshToken = account.refreshToken; + account = storageAccount; } - storedState.accounts[account.userId] = account; - await this.storageService.save('state', storedState, this.defaultOnDiskLocalOptions); - await this.storageService.save('state', storedState, this.defaultOnDiskMemoryOptions); - await this.storageService.save('state', storedState); + await this.storageService.save(account.userId, account, this.defaultOnDiskLocalOptions); + await this.storageService.save(account.userId, account, this.defaultOnDiskMemoryOptions); + await this.storageService.save(account.userId, account); - if (await this.secureStorageService.get('state') == null) { - await this.secureStorageService.save('state', storedState); + if (await this.secureStorageService.get(account.userId) == null) { + await this.secureStorageService.save(account.userId, account); } }