diff --git a/angular/src/services/jslib-services.module.ts b/angular/src/services/jslib-services.module.ts index ec6261cf97..2e2245a2b8 100644 --- a/angular/src/services/jslib-services.module.ts +++ b/angular/src/services/jslib-services.module.ts @@ -360,7 +360,7 @@ import { StateFactory } from "jslib-common/factories/stateFactory"; new StateMigrationService( storageService, secureStorageService, - new GlobalStateFactory(GlobalState) + new StateFactory(GlobalState, Account) ), deps: [StorageServiceAbstraction, "SECURE_STORAGE"], }, diff --git a/common/src/factories/stateFactory.ts b/common/src/factories/stateFactory.ts index 5df194af67..4672fa59e8 100644 --- a/common/src/factories/stateFactory.ts +++ b/common/src/factories/stateFactory.ts @@ -3,7 +3,10 @@ import { GlobalState } from "../models/domain/globalState"; import { AccountFactory } from "./accountFactory"; import { GlobalStateFactory } from "./globalStateFactory"; -export class StateFactory { +export class StateFactory< + TGlobal extends GlobalState = GlobalState, + TAccount extends Account = Account +> { private globalStateFactory: GlobalStateFactory; private accountFactory: AccountFactory; diff --git a/common/src/models/domain/state.ts b/common/src/models/domain/state.ts index c87ff6e8fb..00a5347a52 100644 --- a/common/src/models/domain/state.ts +++ b/common/src/models/domain/state.ts @@ -2,8 +2,8 @@ import { Account } from "./account"; import { GlobalState } from "./globalState"; export class State< - TAccount extends Account = Account, - TGlobalState extends GlobalState = GlobalState + TGlobalState extends GlobalState = GlobalState, + TAccount extends Account = Account > { accounts: { [userId: string]: TAccount } = {}; globals: TGlobalState; diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 8ce399e992..c718b29b29 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -55,14 +55,14 @@ const partialKeys = { }; export class StateService< - TAccount extends Account = Account, - TGlobalState extends GlobalState = GlobalState + TGlobalState extends GlobalState = GlobalState, + TAccount extends Account = Account > implements StateServiceAbstraction { accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); activeAccount = new BehaviorSubject(null); - protected state: State = new State( + protected state: State = new State( this.createGlobals() ); @@ -73,7 +73,7 @@ export class StateService< protected secureStorageService: StorageService, protected logService: LogService, protected stateMigrationService: StateMigrationService, - protected stateFactory: StateFactory + protected stateFactory: StateFactory ) {} async init(): Promise { @@ -2233,7 +2233,6 @@ export class StateService< account.settings = await this.storageService.get(keys.tempAccountSettings); await this.storageService.remove(keys.tempAccountSettings); } - Object.assign(account.settings, this.createAccount().settings); account.settings.environmentUrls = environmentUrls; await this.storageService.save( account.profile.userId, diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index c1bf03a7d4..7844544f5d 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -21,6 +21,8 @@ import { ThemeType } from "../enums/themeType"; import { EnvironmentUrls } from "../models/domain/environmentUrls"; import { GlobalStateFactory } from "../factories/globalStateFactory"; +import { StateFactory } from "../factories/stateFactory"; +import { Account, AccountSettings } from "../models/domain/account"; // Originally (before January 2022) storage was handled as a flat key/value pair store. // With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. @@ -128,11 +130,14 @@ const partialKeys = { masterKey: "_masterkey", }; -export class StateMigrationService { +export class StateMigrationService< + TGlobalState extends GlobalState = GlobalState, + TAccount extends Account = Account +> { constructor( protected storageService: StorageService, protected secureStorageService: StorageService, - protected globalStateFactory: GlobalStateFactory + protected stateFactory: StateFactory ) {} async needsMigration(): Promise { @@ -177,7 +182,8 @@ export class StateMigrationService(keys.global)) ?? this.globalStateFactory.create(); + const globals = + (await this.get(keys.global)) ?? this.stateFactory.createGlobal(null); globals.stateVersion = StateVersion.Two; globals.environmentUrls = (await this.get(v1Keys.environmentUrls)) ?? globals.environmentUrls; @@ -220,46 +226,91 @@ export class StateMigrationService(v1Keys.userId)) ?? (await this.get(v1Keys.entityId)); + const defaultAccount = this.stateFactory.createAccount(null); + const accountSettings: AccountSettings = { + autoConfirmFingerPrints: + (await this.get(v1Keys.autoConfirmFingerprints)) ?? + defaultAccount.settings.autoConfirmFingerPrints, + autoFillOnPageLoadDefault: + (await this.get(v1Keys.autoFillOnPageLoadDefault)) ?? + defaultAccount.settings.autoFillOnPageLoadDefault, + biometricLocked: null, + biometricUnlock: + (await this.get(v1Keys.biometricUnlock)) ?? + defaultAccount.settings.biometricUnlock, + clearClipboard: + (await this.get(v1Keys.clearClipboard)) ?? defaultAccount.settings.clearClipboard, + defaultUriMatch: + (await this.get(v1Keys.defaultUriMatch)) ?? defaultAccount.settings.defaultUriMatch, + disableAddLoginNotification: + (await this.get(v1Keys.disableAddLoginNotification)) ?? + defaultAccount.settings.disableAddLoginNotification, + disableAutoBiometricsPrompt: + (await this.get(v1Keys.disableAutoBiometricsPrompt)) ?? + defaultAccount.settings.disableAutoBiometricsPrompt, + disableAutoTotpCopy: + (await this.get(v1Keys.disableAutoTotpCopy)) ?? + defaultAccount.settings.disableAutoTotpCopy, + disableBadgeCounter: + (await this.get(v1Keys.disableBadgeCounter)) ?? + defaultAccount.settings.disableBadgeCounter, + disableChangedPasswordNotification: + (await this.get(v1Keys.disableChangedPasswordNotification)) ?? + defaultAccount.settings.disableChangedPasswordNotification, + disableContextMenuItem: + (await this.get(v1Keys.disableContextMenuItem)) ?? + defaultAccount.settings.disableContextMenuItem, + disableGa: (await this.get(v1Keys.disableGa)) ?? defaultAccount.settings.disableGa, + dontShowCardsCurrentTab: + (await this.get(v1Keys.dontShowCardsCurrentTab)) ?? + defaultAccount.settings.dontShowCardsCurrentTab, + dontShowIdentitiesCurrentTab: + (await this.get(v1Keys.dontShowIdentitiesCurrentTab)) ?? + defaultAccount.settings.dontShowIdentitiesCurrentTab, + enableAlwaysOnTop: + (await this.get(v1Keys.enableAlwaysOnTop)) ?? + defaultAccount.settings.enableAlwaysOnTop, + enableAutoFillOnPageLoad: + (await this.get(v1Keys.enableAutoFillOnPageLoad)) ?? + defaultAccount.settings.enableAutoFillOnPageLoad, + enableBiometric: + (await this.get(v1Keys.enableBiometric)) ?? + defaultAccount.settings.enableBiometric, + enableFullWidth: + (await this.get(v1Keys.enableFullWidth)) ?? + defaultAccount.settings.enableFullWidth, + enableGravitars: + (await this.get(v1Keys.enableGravatars)) ?? + defaultAccount.settings.enableGravitars, + environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls, + equivalentDomains: + (await this.get(v1Keys.equivalentDomains)) ?? + defaultAccount.settings.equivalentDomains, + minimizeOnCopyToClipboard: + (await this.get(v1Keys.minimizeOnCopyToClipboard)) ?? + defaultAccount.settings.minimizeOnCopyToClipboard, + neverDomains: + (await this.get(v1Keys.neverDomains)) ?? defaultAccount.settings.neverDomains, + passwordGenerationOptions: + (await this.get(v1Keys.passwordGenerationOptions)) ?? + defaultAccount.settings.passwordGenerationOptions, + pinProtected: { + decrypted: null, + encrypted: await this.get(v1Keys.pinProtected), + }, + protectedPin: await this.get(v1Keys.protectedPin), + settings: userId == null ? null : await this.get(v1KeyPrefixes.settings + userId), + vaultTimeout: + (await this.get(v1Keys.vaultTimeout)) ?? defaultAccount.settings.vaultTimeout, + vaultTimeoutAction: + (await this.get(v1Keys.vaultTimeoutAction)) ?? + defaultAccount.settings.vaultTimeoutAction, + }; + // (userId == null) = no logged in user (so no known userId) and we need to temporarily store account specific settings in state to migrate on first auth // (userId != null) = we have a currently authed user (so known userId) with encrypted data and other key settings we can move, no need to temporarily store account settings if (userId == null) { - await this.set(keys.tempAccountSettings, { - autoConfirmFingerPrints: await this.get(v1Keys.autoConfirmFingerprints), - autoFillOnPageLoadDefault: await this.get(v1Keys.autoFillOnPageLoadDefault), - biometricLocked: null, - biometricUnlock: await this.get(v1Keys.biometricUnlock), - clearClipboard: await this.get(v1Keys.clearClipboard), - defaultUriMatch: await this.get(v1Keys.defaultUriMatch), - disableAddLoginNotification: await this.get(v1Keys.disableAddLoginNotification), - disableAutoBiometricsPrompt: await this.get(v1Keys.disableAutoBiometricsPrompt), - disableAutoTotpCopy: await this.get(v1Keys.disableAutoTotpCopy), - disableBadgeCounter: await this.get(v1Keys.disableBadgeCounter), - disableChangedPasswordNotification: await this.get( - v1Keys.disableChangedPasswordNotification - ), - disableContextMenuItem: await this.get(v1Keys.disableContextMenuItem), - disableGa: await this.get(v1Keys.disableGa), - dontShowCardsCurrentTab: await this.get(v1Keys.dontShowCardsCurrentTab), - dontShowIdentitiesCurrentTab: await this.get(v1Keys.dontShowIdentitiesCurrentTab), - enableAlwaysOnTop: await this.get(v1Keys.enableAlwaysOnTop), - enableAutoFillOnPageLoad: await this.get(v1Keys.enableAutoFillOnPageLoad), - enableBiometric: await this.get(v1Keys.enableBiometric), - enableFullWidth: await this.get(v1Keys.enableFullWidth), - enableGravitars: await this.get(v1Keys.enableGravatars), - environmentUrls: globals.environmentUrls, - equivalentDomains: await this.get(v1Keys.equivalentDomains), - minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), - neverDomains: await this.get(v1Keys.neverDomains), - passwordGenerationOptions: await this.get(v1Keys.passwordGenerationOptions), - pinProtected: { - decrypted: null, - encrypted: await this.get(v1Keys.pinProtected), - }, - protectedPin: await this.get(v1Keys.protectedPin), - settings: null, - vaultTimeout: await this.get(v1Keys.vaultTimeout), - vaultTimeoutAction: await this.get(v1Keys.vaultTimeoutAction), - }); + await this.set(keys.tempAccountSettings, accountSettings); await this.set(keys.global, globals); await this.set(keys.authenticatedAccounts, []); await this.set(keys.activeUserId, null); @@ -350,43 +401,7 @@ export class StateMigrationService(v1Keys.autoConfirmFingerprints), - autoFillOnPageLoadDefault: await this.get(v1Keys.autoFillOnPageLoadDefault), - biometricLocked: null, - biometricUnlock: await this.get(v1Keys.biometricUnlock), - clearClipboard: await this.get(v1Keys.clearClipboard), - defaultUriMatch: await this.get(v1Keys.defaultUriMatch), - disableAddLoginNotification: await this.get(v1Keys.disableAddLoginNotification), - disableAutoBiometricsPrompt: await this.get(v1Keys.disableAutoBiometricsPrompt), - disableAutoTotpCopy: await this.get(v1Keys.disableAutoTotpCopy), - disableBadgeCounter: await this.get(v1Keys.disableBadgeCounter), - disableChangedPasswordNotification: await this.get( - v1Keys.disableChangedPasswordNotification - ), - disableContextMenuItem: await this.get(v1Keys.disableContextMenuItem), - disableGa: await this.get(v1Keys.disableGa), - dontShowCardsCurrentTab: await this.get(v1Keys.dontShowCardsCurrentTab), - dontShowIdentitiesCurrentTab: await this.get(v1Keys.dontShowIdentitiesCurrentTab), - enableAlwaysOnTop: await this.get(v1Keys.enableAlwaysOnTop), - enableAutoFillOnPageLoad: await this.get(v1Keys.enableAutoFillOnPageLoad), - enableBiometric: await this.get(v1Keys.enableBiometric), - enableFullWidth: await this.get(v1Keys.enableFullWidth), - enableGravitars: await this.get(v1Keys.enableGravatars), - environmentUrls: globals.environmentUrls, - equivalentDomains: await this.get(v1Keys.equivalentDomains), - minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), - neverDomains: await this.get(v1Keys.neverDomains), - passwordGenerationOptions: await this.get(v1Keys.passwordGenerationOptions), - pinProtected: { - decrypted: null, - encrypted: await this.get(v1Keys.pinProtected), - }, - protectedPin: await this.get(v1Keys.protectedPin), - settings: await this.get(v1KeyPrefixes.settings + userId), - vaultTimeout: await this.get(v1Keys.vaultTimeout), - vaultTimeoutAction: await this.get(v1Keys.vaultTimeoutAction), - }, + settings: accountSettings, tokens: { accessToken: await this.get(v1Keys.accessToken), decodedToken: null,