diff --git a/common/src/abstractions/state.service.ts b/common/src/abstractions/state.service.ts index 55707b7f1e3..5dd3da2e872 100644 --- a/common/src/abstractions/state.service.ts +++ b/common/src/abstractions/state.service.ts @@ -24,11 +24,11 @@ import { CollectionView } from "../models/view/collectionView"; import { FolderView } from "../models/view/folderView"; import { SendView } from "../models/view/sendView"; -export abstract class StateService { - accounts: BehaviorSubject<{ [userId: string]: Account }>; +export abstract class StateService { + accounts: BehaviorSubject<{ [userId: string]: T }>; activeAccount: BehaviorSubject; - addAccount: (account: Account) => Promise; + addAccount: (account: T) => Promise; setActiveUser: (userId: string) => Promise; clean: (options?: StorageOptions) => Promise; init: () => Promise; diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index 98ea62df438..cab08d1a9fb 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -130,9 +130,6 @@ export class AccountSettings { enableMinimizeToTray?: boolean; enableStartToTray?: boolean; enableTray?: boolean; - environmentUrls?: any = { - server: "bitwarden.com", - }; equivalentDomains?: any; minimizeOnCopyToClipboard?: boolean; neverDomains?: { [id: string]: any }; diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts index 66ad5130d4d..0fdce6486da 100644 --- a/common/src/models/domain/globalState.ts +++ b/common/src/models/domain/globalState.ts @@ -24,4 +24,7 @@ export class GlobalState { noAutoPromptBiometrics?: boolean; noAutoPromptBiometricsText?: string; stateVersion: number; + environmentUrls?: any = { + server: "bitwarden.com", + }; } diff --git a/common/src/models/domain/state.ts b/common/src/models/domain/state.ts index 17889bf45b3..d9087264e36 100644 --- a/common/src/models/domain/state.ts +++ b/common/src/models/domain/state.ts @@ -1,8 +1,8 @@ import { Account } from "./account"; import { GlobalState } from "./globalState"; -export class State { - accounts: { [userId: string]: Account } = {}; +export class State { + accounts: { [userId: string]: TAccount } = {}; globals: GlobalState = new GlobalState(); activeUserId: string; } diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index 5e44c90deca..3d7506d38a2 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -2,7 +2,13 @@ import { HashPurpose } from "../enums/hashPurpose"; import { KdfType } from "../enums/kdfType"; import { TwoFactorProviderType } from "../enums/twoFactorProviderType"; -import { Account, AccountData, AccountProfile, AccountTokens } from "../models/domain/account"; +import { + Account, + AccountData, + AccountKeys, + AccountProfile, + AccountTokens, +} from "../models/domain/account"; import { AuthResult } from "../models/domain/authResult"; import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; @@ -538,27 +544,34 @@ export class AuthService implements AuthServiceAbstraction { result.forcePasswordReset = tokenResponse.forcePasswordReset; const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken); - await this.stateService.addAccount({ - profile: { - ...new AccountProfile(), - ...{ - userId: accountInformation.sub, - email: accountInformation.email, - apiKeyClientId: clientId, - apiKeyClientSecret: clientSecret, - hasPremiumPersonally: accountInformation.premium, - kdfIterations: tokenResponse.kdfIterations, - kdfType: tokenResponse.kdf, + await this.stateService.addAccount( + new Account({ + profile: { + ...new AccountProfile(), + ...{ + userId: accountInformation.sub, + email: accountInformation.email, + apiKeyClientId: clientId, + hasPremiumPersonally: accountInformation.premium, + kdfIterations: tokenResponse.kdfIterations, + kdfType: tokenResponse.kdf, + }, }, - }, - tokens: { - ...new AccountTokens(), - ...{ - accessToken: tokenResponse.accessToken, - refreshToken: tokenResponse.refreshToken, + keys: { + ...new AccountKeys(), + ...{ + apiKeyClientSecret: clientSecret, + }, }, - }, - }); + tokens: { + ...new AccountTokens(), + ...{ + accessToken: tokenResponse.accessToken, + refreshToken: tokenResponse.refreshToken, + }, + }, + }) + ); if (tokenResponse.twoFactorToken != null) { await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index 05c14d93a92..fd9d83c41f1 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -761,13 +761,13 @@ export class CryptoService implements CryptoServiceAbstraction { // Helpers protected async storeKey(key: SymmetricCryptoKey, userId?: string) { - if ( - (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) || - (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) - ) { - await this.stateService.setCryptoMasterKeyB64(key.keyB64, { userId: userId }); + if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { + await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); + } else if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { + await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); } else { - await this.stateService.setCryptoMasterKeyB64(null, { userId: userId }); + await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); + await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); } } diff --git a/common/src/services/environment.service.ts b/common/src/services/environment.service.ts index d95718e02cb..cf435d878d6 100644 --- a/common/src/services/environment.service.ts +++ b/common/src/services/environment.service.ts @@ -109,18 +109,7 @@ export class EnvironmentService implements EnvironmentServiceAbstraction { } async setUrlsFromStorage(): Promise { - const urlsObj: any = await this.stateService.getEnvironmentUrls(); - const urls = urlsObj || { - base: null, - api: null, - identity: null, - icons: null, - notifications: null, - events: null, - webVault: null, - keyConnector: null, - }; - + const urls: any = await this.stateService.getEnvironmentUrls(); const envUrls = new EnvironmentUrls(); if (urls.base) { diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 59c5a73a946..5de1ccb18e0 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -1,6 +1,12 @@ import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; -import { Account } from "../models/domain/account"; +import { + Account, + AccountData, + AccountKeys, + AccountProfile, + AccountTokens, +} from "../models/domain/account"; import { LogService } from "../abstractions/log.service"; import { StorageService } from "../abstractions/storage.service"; @@ -34,19 +40,21 @@ import { SendData } from "../models/data/sendData"; import { BehaviorSubject } from "rxjs"; -import { StateMigrationService } from "./stateMigration.service"; +import { StateMigrationService } from "../abstractions/stateMigration.service"; -export class StateService implements StateServiceAbstraction { - accounts = new BehaviorSubject<{ [userId: string]: Account }>({}); +export class StateService + implements StateServiceAbstraction +{ + accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); activeAccount = new BehaviorSubject(null); - private state: State = new State(); + protected state: State = new State(); constructor( - private storageService: StorageService, - private secureStorageService: StorageService, - private logService: LogService, - private stateMigrationService: StateMigrationService + protected storageService: StorageService, + protected secureStorageService: StorageService, + protected logService: LogService, + protected stateMigrationService: StateMigrationService ) {} async init(): Promise { @@ -60,7 +68,7 @@ export class StateService implements StateServiceAbstraction { async loadStateFromDisk() { if ((await this.getActiveUserIdFromStorage()) != null) { - const diskState = await this.storageService.get( + const diskState = await this.storageService.get>( "state", await this.defaultOnDiskOptions() ); @@ -71,10 +79,7 @@ export class StateService implements StateServiceAbstraction { } } - async addAccount(account: Account) { - if (account?.profile?.userId == null) { - return; - } + async addAccount(account: TAccount) { this.state.accounts[account.profile.userId] = account; await this.scaffoldNewAccountStorage(account); await this.setActiveUser(account.profile.userId); @@ -83,7 +88,7 @@ export class StateService implements StateServiceAbstraction { async setActiveUser(userId: string): Promise { this.state.activeUserId = userId; - const storedState = await this.storageService.get( + const storedState = await this.storageService.get>( "state", await this.defaultOnDiskOptions() ); @@ -1321,47 +1326,64 @@ export class StateService implements StateServiceAbstraction { } async getEntityId(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.entityId; + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.profile?.entityId; } async setEntityId(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityId = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); } async getEntityType(options?: StorageOptions): Promise { - return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) - ?.profile?.entityType; + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) + )?.profile?.entityType; } async setEntityType(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityType = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) + ); } async getEnvironmentUrls(options?: StorageOptions): Promise { return ( - (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) - ?.settings?.environmentUrls ?? { + (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) + ?.environmentUrls ?? { + base: null, + api: null, + identity: null, + icons: null, + notifications: null, + events: null, + webVault: null, + keyConnector: null, + // TODO: this is a bug and we should use base instead for the server detail in the account switcher, otherwise self hosted urls will not show correctly server: "bitwarden.com", } ); } async setEnvironmentUrls(value: any, options?: StorageOptions): Promise { - const account = await this.getAccount( + const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.settings.environmentUrls = value; - await this.saveAccount( - account, + globals.environmentUrls = value; + await this.saveGlobals( + globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @@ -1402,17 +1424,20 @@ export class StateService implements StateServiceAbstraction { async getEverBeenUnlocked(options?: StorageOptions): Promise { return ( - (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile - ?.everBeenUnlocked ?? false + (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) + ?.profile?.everBeenUnlocked ?? false ); } async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( - this.reconcileOptions(options, this.defaultInMemoryOptions) + this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.everBeenUnlocked = value; - await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); } async getForcePasswordReset(options?: StorageOptions): Promise { @@ -1981,10 +2006,7 @@ export class StateService implements StateServiceAbstraction { const accountVaultTimeout = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.vaultTimeout; - const globalVaultTimeout = ( - await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.vaultTimeout; - return accountVaultTimeout ?? globalVaultTimeout ?? 15; + return accountVaultTimeout; } async setVaultTimeout(value: number, options?: StorageOptions): Promise { @@ -2047,7 +2069,7 @@ export class StateService implements StateServiceAbstraction { ); } - private async getGlobals(options: StorageOptions): Promise { + protected async getGlobals(options: StorageOptions): Promise { let globals: GlobalState; if (this.useMemory(options.storageLocation)) { globals = this.getGlobalsFromMemory(); @@ -2060,39 +2082,41 @@ export class StateService implements StateServiceAbstraction { return globals ?? new GlobalState(); } - private async saveGlobals(globals: GlobalState, options: StorageOptions) { + protected async saveGlobals(globals: GlobalState, options: StorageOptions) { return this.useMemory(options.storageLocation) ? this.saveGlobalsToMemory(globals) : await this.saveGlobalsToDisk(globals, options); } - private getGlobalsFromMemory(): GlobalState { + protected getGlobalsFromMemory(): GlobalState { return this.state.globals; } - private async getGlobalsFromDisk(options: StorageOptions): Promise { - return (await this.storageService.get("state", options))?.globals; + protected async getGlobalsFromDisk(options: StorageOptions): Promise { + return (await this.storageService.get>("state", options))?.globals; } - private saveGlobalsToMemory(globals: GlobalState): void { + protected saveGlobalsToMemory(globals: GlobalState): void { this.state.globals = globals; } - private async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { + protected async saveGlobalsToDisk(globals: GlobalState, options: StorageOptions): Promise { if (options.useSecureStorage) { - const state = (await this.secureStorageService.get("state", options)) ?? new State(); + const state = + (await this.secureStorageService.get>("state", options)) ?? new State(); state.globals = globals; await this.secureStorageService.save("state", state, options); } else { - const state = (await this.storageService.get("state", options)) ?? new State(); + const state = + (await this.storageService.get>("state", options)) ?? new State(); state.globals = globals; await this.saveStateToStorage(state, options); } } - private async getAccount(options: StorageOptions): Promise { + protected async getAccount(options: StorageOptions): Promise { try { - let account: Account; + let account: TAccount; if (this.useMemory(options.storageLocation)) { account = this.getAccountFromMemory(options); } @@ -2101,51 +2125,51 @@ export class StateService implements StateServiceAbstraction { account = await this.getAccountFromDisk(options); } - return account != null ? new Account(account) : null; + return account; } catch (e) { this.logService.error(e); } } - private getAccountFromMemory(options: StorageOptions): Account { + protected getAccountFromMemory(options: StorageOptions): TAccount { if (this.state.accounts == null) { return null; } return this.state.accounts[this.getUserIdFromMemory(options)]; } - private getUserIdFromMemory(options: StorageOptions): string { + protected getUserIdFromMemory(options: StorageOptions): string { return options?.userId != null ? this.state.accounts[options.userId]?.profile?.userId : this.state.activeUserId; } - private async getAccountFromDisk(options: StorageOptions): Promise { + protected async getAccountFromDisk(options: StorageOptions): Promise { if (options?.userId == null && this.state.activeUserId == null) { return null; } const state = options?.useSecureStorage - ? (await this.secureStorageService.get("state", options)) ?? - (await this.storageService.get( + ? (await this.secureStorageService.get>("state", options)) ?? + (await this.storageService.get>( "state", this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }) )) - : await this.storageService.get("state", options); + : await this.storageService.get>("state", options); return state?.accounts[options?.userId ?? this.state.activeUserId]; } - private useMemory(storageLocation: StorageLocation) { + protected useMemory(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both; } - private useDisk(storageLocation: StorageLocation) { + protected useDisk(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both; } - private async saveAccount( - account: Account, + protected async saveAccount( + account: TAccount, options: StorageOptions = { storageLocation: StorageLocation.Both, useSecureStorage: false, @@ -2156,86 +2180,75 @@ export class StateService implements StateServiceAbstraction { : await this.saveAccountToDisk(account, options); } - private async saveAccountToDisk(account: Account, options: StorageOptions): Promise { + protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise { const storageLocation = options.useSecureStorage ? this.secureStorageService : this.storageService; - const state = (await storageLocation.get("state", options)) ?? new State(); + const state = + (await storageLocation.get>("state", options)) ?? new State(); state.accounts[account.profile.userId] = account; await storageLocation.save("state", state, options); await this.pushAccounts(); } - private async saveAccountToMemory(account: Account): Promise { + protected async saveAccountToMemory(account: TAccount): Promise { if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) { this.state.accounts[account.profile.userId] = account; } await this.pushAccounts(); } - private async scaffoldNewAccountStorage(account: Account): Promise { + protected async scaffoldNewAccountStorage(account: TAccount): Promise { await this.scaffoldNewAccountLocalStorage(account); await this.scaffoldNewAccountSessionStorage(account); await this.scaffoldNewAccountMemoryStorage(account); } - private async scaffoldNewAccountLocalStorage(account: Account): Promise { + protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskLocalOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskLocalOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskLocalOptions()); } - private async scaffoldNewAccountMemoryStorage(account: Account): Promise { + protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskMemoryOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskMemoryOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskMemoryOptions()); } - private async scaffoldNewAccountSessionStorage(account: Account): Promise { + protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise { const storedState = - (await this.storageService.get("state", await this.defaultOnDiskOptions())) ?? - new State(); + (await this.storageService.get>( + "state", + await this.defaultOnDiskOptions() + )) ?? new State(); const storedAccount = storedState.accounts[account.profile.userId]; if (storedAccount != null) { - account = { - settings: storedAccount.settings, - profile: account.profile, - tokens: account.tokens, - keys: account.keys, - data: account.data, - }; + account.settings = storedAccount.settings; } storedState.accounts[account.profile.userId] = account; await this.saveStateToStorage(storedState, await this.defaultOnDiskOptions()); } - private async pushAccounts(): Promise { + protected async pushAccounts(): Promise { await this.pruneInMemoryAccounts(); if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) { this.accounts.next(null); @@ -2245,7 +2258,7 @@ export class StateService implements StateServiceAbstraction { this.accounts.next(this.state.accounts); } - private reconcileOptions( + protected reconcileOptions( requestedOptions: StorageOptions, defaultOptions: StorageOptions ): StorageOptions { @@ -2263,11 +2276,11 @@ export class StateService implements StateServiceAbstraction { return requestedOptions; } - private get defaultInMemoryOptions(): StorageOptions { + protected get defaultInMemoryOptions(): StorageOptions { return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId }; } - private async defaultOnDiskOptions(): Promise { + protected async defaultOnDiskOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Session, @@ -2276,7 +2289,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultOnDiskLocalOptions(): Promise { + protected async defaultOnDiskLocalOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Local, @@ -2285,7 +2298,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultOnDiskMemoryOptions(): Promise { + protected async defaultOnDiskMemoryOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Memory, @@ -2294,7 +2307,7 @@ export class StateService implements StateServiceAbstraction { }; } - private async defaultSecureStorageOptions(): Promise { + protected async defaultSecureStorageOptions(): Promise { return { storageLocation: StorageLocation.Disk, useSecureStorage: true, @@ -2302,46 +2315,38 @@ export class StateService implements StateServiceAbstraction { }; } - private async getActiveUserIdFromStorage(): Promise { - const state = await this.storageService.get("state"); + protected async getActiveUserIdFromStorage(): Promise { + const state = await this.storageService.get>("state"); return state?.activeUserId; } - private async removeAccountFromLocalStorage( + protected async removeAccountFromLocalStorage( userId: string = this.state.activeUserId ): Promise { - const state = await this.storageService.get("state", { + const state = await this.storageService.get>("state", { htmlStorageLocation: HtmlStorageLocation.Local, }); if (state?.accounts[userId] == null) { return; } - - state.accounts[userId] = new Account({ - settings: state.accounts[userId].settings, - }); - + state.accounts[userId] = this.resetAccount(state.accounts[userId]); await this.saveStateToStorage(state, await this.defaultOnDiskLocalOptions()); } - private async removeAccountFromSessionStorage( + protected async removeAccountFromSessionStorage( userId: string = this.state.activeUserId ): Promise { - const state = await this.storageService.get("state", { + const state = await this.storageService.get>("state", { htmlStorageLocation: HtmlStorageLocation.Session, }); if (state?.accounts[userId] == null) { return; } - - state.accounts[userId] = new Account({ - settings: state.accounts[userId].settings, - }); - + state.accounts[userId] = this.resetAccount(state.accounts[userId]); await this.saveStateToStorage(state, await this.defaultOnDiskOptions()); } - private async removeAccountFromSecureStorage( + protected async removeAccountFromSecureStorage( userId: string = this.state.activeUserId ): Promise { await this.setCryptoMasterKeyAuto(null, { userId: userId }); @@ -2349,15 +2354,18 @@ export class StateService implements StateServiceAbstraction { await this.setCryptoMasterKeyB64(null, { userId: userId }); } - private removeAccountFromMemory(userId: string = this.state.activeUserId): void { + protected removeAccountFromMemory(userId: string = this.state.activeUserId): void { delete this.state.accounts[userId]; } - private async saveStateToStorage(state: State, options: StorageOptions): Promise { + protected async saveStateToStorage( + state: State, + options: StorageOptions + ): Promise { await this.storageService.save("state", state, options); } - private async pruneInMemoryAccounts() { + protected async pruneInMemoryAccounts() { // We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state for (const userId in this.state.accounts) { if (!(await this.getIsAuthenticated({ userId: userId }))) { @@ -2365,4 +2373,13 @@ export class StateService implements StateServiceAbstraction { } } } + + // settings persist even on reset + protected resetAccount(account: TAccount) { + account.data = new AccountData(); + account.keys = new AccountKeys(); + account.profile = new AccountProfile(); + account.tokens = new AccountTokens(); + return account; + } } diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index 903cc093ff4..afe756e44ba 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -114,19 +114,22 @@ export class StateMigrationService { readonly latestVersion: number = 2; constructor( - private storageService: StorageService, - private secureStorageService: StorageService + protected storageService: StorageService, + protected secureStorageService: StorageService ) {} async needsMigration(): Promise { - const currentStateVersion = (await this.storageService.get("state"))?.globals - ?.stateVersion; + const currentStateVersion = ( + await this.storageService.get>("state", { + htmlStorageLocation: HtmlStorageLocation.Local, + }) + )?.globals?.stateVersion; return currentStateVersion == null || currentStateVersion < this.latestVersion; } async migrate(): Promise { let currentStateVersion = - (await this.storageService.get("state"))?.globals?.stateVersion ?? 1; + (await this.storageService.get>("state"))?.globals?.stateVersion ?? 1; while (currentStateVersion < this.latestVersion) { switch (currentStateVersion) { case 1: @@ -138,10 +141,10 @@ export class StateMigrationService { } } - private async migrateStateFrom1To2(): Promise { + protected async migrateStateFrom1To2(): Promise { const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local }; const userId = await this.storageService.get("userId"); - const initialState: State = + const initialState: State = userId == null ? { globals: { @@ -174,6 +177,7 @@ export class StateMigrationService { v1Keys.enableBiometric, options ), + environmentUrls: await this.storageService.get(v1Keys.environmentUrls, options), installedVersion: await this.storageService.get( v1Keys.installedVersion, options @@ -439,10 +443,6 @@ export class StateMigrationService { options ), enableTray: await this.storageService.get(v1Keys.enableTray, options), - environmentUrls: await this.storageService.get( - v1Keys.environmentUrls, - options - ), equivalentDomains: await this.storageService.get( v1Keys.equivalentDomains, options diff --git a/common/src/services/vaultTimeout.service.ts b/common/src/services/vaultTimeout.service.ts index 544e6bc2382..cd810e8c4eb 100644 --- a/common/src/services/vaultTimeout.service.ts +++ b/common/src/services/vaultTimeout.service.ts @@ -53,7 +53,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { async isLocked(userId?: string): Promise { const neverLock = (await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) && - !(await this.stateService.getEverBeenUnlocked({ userId: userId })); + (await this.stateService.getEverBeenUnlocked({ userId: userId })); if (neverLock) { // TODO: This also _sets_ the key so when we check memory in the next line it finds a key. // We should refactor here. diff --git a/node/src/cli/baseProgram.ts b/node/src/cli/baseProgram.ts index e8c15f9a18a..b2ddd22fd89 100644 --- a/node/src/cli/baseProgram.ts +++ b/node/src/cli/baseProgram.ts @@ -9,7 +9,7 @@ import { StringResponse } from "./models/response/stringResponse"; export abstract class BaseProgram { constructor( - private stateService: StateService, + protected stateService: StateService, private writeLn: (s: string, finalLine: boolean, error: boolean) => void ) {}