From 57351d29a2810e3f73f85bb46bb4762381dc512a Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Thu, 20 Jan 2022 08:30:00 -0500 Subject: [PATCH] [bug] Migrate state even if there is not a user logged in (#615) Currently the StateMigrationService depends on a userId key for running migrations, but if there is not an authenticated user saved to storage that userId is not present. These changes allow for migrating state data even without an active user. For account specific settings like clearClipboard we now temporarily store those values together in disk state until an account is authed that they can be added to. Temp account state is then cleared. Some notes: * In order for this to work we need GlobalState.stateVersion to have a default value of StateVersion.One instead of StateVersion.Latest. Defaulting it to latest was causing migrations to not run on some clients (like desktop) that try to access storage before migrations have been run but save a version as if migrations did run. * I also noticed we aren't clearing old state items from before migrating, and added a case for this to the migrator. * I extracted a few bits of reused code into private methods in the stateMigration service. Things like get/set from storage, default options, etc. --- common/src/models/domain/globalState.ts | 2 +- common/src/services/state.service.ts | 4 + common/src/services/stateMigration.service.ts | 628 ++++++++---------- 3 files changed, 269 insertions(+), 365 deletions(-) diff --git a/common/src/models/domain/globalState.ts b/common/src/models/domain/globalState.ts index f14d48d3e5..7a1895d1ff 100644 --- a/common/src/models/domain/globalState.ts +++ b/common/src/models/domain/globalState.ts @@ -25,6 +25,6 @@ export class GlobalState { biometricText?: string; noAutoPromptBiometrics?: boolean; noAutoPromptBiometricsText?: string; - stateVersion: StateVersion = StateVersion.Latest; + stateVersion: StateVersion = StateVersion.One; environmentUrls: EnvironmentUrls = new EnvironmentUrls(); } diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 68cbe7890e..60975d5721 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -41,6 +41,7 @@ const keys = { global: "global", authenticatedAccounts: "authenticatedAccounts", activeUserId: "activeUserId", + tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication }; const partialKeys = { @@ -2213,6 +2214,9 @@ export class StateService // EnvironmentUrls are set before authenticating and should override whatever is stored from last session storedAccount.settings.environmentUrls = account.settings.environmentUrls; account.settings = storedAccount.settings; + } else if (await this.storageService.has(keys.tempAccountSettings)) { + account.settings = await this.storageService.get(keys.tempAccountSettings); + await this.storageService.remove(keys.tempAccountSettings); } await this.storageService.save( account.profile.userId, diff --git a/common/src/services/stateMigration.service.ts b/common/src/services/stateMigration.service.ts index 59db60722e..36bf686486 100644 --- a/common/src/services/stateMigration.service.ts +++ b/common/src/services/stateMigration.service.ts @@ -1,9 +1,7 @@ import { StorageService } from "../abstractions/storage.service"; -import { Account } from "../models/domain/account"; import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; import { GlobalState } from "../models/domain/globalState"; -import { State } from "../models/domain/state"; import { StorageOptions } from "../models/domain/storageOptions"; import { CipherData } from "../models/data/cipherData"; @@ -18,11 +16,12 @@ import { SendData } from "../models/data/sendData"; import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; import { KdfType } from "../enums/kdfType"; import { StateVersion } from "../enums/stateVersion"; + import { EnvironmentUrls } from "../models/domain/environmentUrls"; // 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. -const v1Keys = { +const v1Keys: { [key: string]: string } = { accessToken: "accessToken", alwaysShowDock: "alwaysShowDock", autoConfirmFingerprints: "autoConfirmFingerprints", @@ -100,7 +99,7 @@ const v1Keys = { rememberedEmail: "rememberedEmail", }; -const v1KeyPrefixes = { +const v1KeyPrefixes: { [key: string]: string } = { ciphers: "ciphers_", collections: "collections_", folders: "folders_", @@ -117,6 +116,7 @@ const keys = { global: "global", authenticatedAccounts: "authenticatedAccounts", activeUserId: "activeUserId", + tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication }; const partialKeys = { @@ -132,17 +132,12 @@ export class StateMigrationService { ) {} async needsMigration(): Promise { - const currentStateVersion = ( - await this.storageService.get(keys.global, { - htmlStorageLocation: HtmlStorageLocation.Local, - }) - )?.stateVersion; + const currentStateVersion = await this.getCurrentStateVersion(); return currentStateVersion == null || currentStateVersion < StateVersion.Latest; } async migrate(): Promise { - let currentStateVersion = - (await this.storageService.get(keys.global))?.stateVersion ?? StateVersion.One; + let currentStateVersion = await this.getCurrentStateVersion(); while (currentStateVersion < StateVersion.Latest) { switch (currentStateVersion) { case StateVersion.One: @@ -155,362 +150,244 @@ export class StateMigrationService { } protected async migrateStateFrom1To2(): Promise { - const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local }; - const userId = await this.storageService.get("userId"); - const initialState: State = - userId == null - ? { - globals: new GlobalState(), - accounts: {}, - activeUserId: null, - authenticatedAccounts: [], + const clearV1Keys = async (clearingUserId?: string) => { + for (const key in v1Keys) { + if (key == null) { + continue; + } + await this.set(v1Keys[key], null); + } + if (clearingUserId != null) { + for (const keyPrefix in v1KeyPrefixes) { + if (keyPrefix == null) { + continue; } - : { - authenticatedAccounts: [userId], - activeUserId: userId, - globals: { - biometricAwaitingAcceptance: await this.storageService.get( - v1Keys.biometricAwaitingAcceptance, - options - ), - biometricFingerprintValidated: await this.storageService.get( - v1Keys.biometricFingerprintValidated, - options - ), - biometricText: await this.storageService.get(v1Keys.biometricText, options), - disableFavicon: await this.storageService.get( - v1Keys.disableFavicon, - options - ), - enableAlwaysOnTop: await this.storageService.get( - v1Keys.enableAlwaysOnTop, - options - ), - enableBiometrics: await this.storageService.get( - v1Keys.enableBiometric, - options - ), - environmentUrls: null, - installedVersion: await this.storageService.get( - v1Keys.installedVersion, - options - ), - locale: await this.storageService.get(v1Keys.locale, options), - loginRedirect: null, - mainWindowSize: null, - noAutoPromptBiometrics: await this.storageService.get( - v1Keys.disableAutoBiometricsPrompt, - options - ), - noAutoPromptBiometricsText: await this.storageService.get( - v1Keys.noAutoPromptBiometricsText, - options - ), - openAtLogin: await this.storageService.get(v1Keys.openAtLogin, options), - organizationInvitation: await this.storageService.get("", options), - ssoCodeVerifier: await this.storageService.get( - v1Keys.ssoCodeVerifier, - options - ), - ssoOrganizationIdentifier: await this.storageService.get( - v1Keys.ssoIdentifier, - options - ), - ssoState: null, - rememberedEmail: await this.storageService.get( - v1Keys.rememberedEmail, - options - ), - stateVersion: StateVersion.Two, - theme: await this.storageService.get(v1Keys.theme, options), - twoFactorToken: await this.storageService.get( - v1KeyPrefixes.twoFactorToken + userId, - options - ), - vaultTimeout: await this.storageService.get(v1Keys.vaultTimeout, options), - vaultTimeoutAction: await this.storageService.get( - v1Keys.vaultTimeoutAction, - options - ), - window: null, - }, - accounts: { - [userId]: new Account({ - data: { - addEditCipherInfo: null, - ciphers: { - decrypted: null, - encrypted: await this.storageService.get<{ [id: string]: CipherData }>( - v1KeyPrefixes.ciphers + userId, - options - ), - }, - collapsedGroupings: null, - collections: { - decrypted: null, - encrypted: await this.storageService.get<{ [id: string]: CollectionData }>( - v1KeyPrefixes.collections + userId, - options - ), - }, - eventCollection: await this.storageService.get( - v1Keys.eventCollection, - options - ), - folders: { - decrypted: null, - encrypted: await this.storageService.get<{ [id: string]: FolderData }>( - v1KeyPrefixes.folders + userId, - options - ), - }, - localData: null, - organizations: await this.storageService.get<{ [id: string]: OrganizationData }>( - v1KeyPrefixes.organizations + userId - ), - passwordGenerationHistory: { - decrypted: null, - encrypted: await this.storageService.get( - "TODO", - options - ), // TODO: Whats up here? - }, - policies: { - decrypted: null, - encrypted: await this.storageService.get<{ [id: string]: PolicyData }>( - v1KeyPrefixes.policies + userId, - options - ), - }, - providers: await this.storageService.get<{ [id: string]: ProviderData }>( - v1KeyPrefixes.providers + userId - ), - sends: { - decrypted: null, - encrypted: await this.storageService.get<{ [id: string]: SendData }>( - v1KeyPrefixes.sends, - options - ), - }, - }, - keys: { - apiKeyClientSecret: await this.storageService.get( - v1Keys.clientSecret, - options - ), - cryptoMasterKey: null, - cryptoMasterKeyAuto: null, - cryptoMasterKeyB64: null, - cryptoMasterKeyBiometric: null, - cryptoSymmetricKey: { - encrypted: await this.storageService.get(v1Keys.encKey, options), - decrypted: null, - }, - legacyEtmKey: null, - organizationKeys: { - decrypted: null, - encrypted: await this.storageService.get( - v1Keys.encOrgKeys + userId, - options - ), - }, - privateKey: { - decrypted: null, - encrypted: await this.storageService.get(v1Keys.encPrivate, options), - }, - providerKeys: { - decrypted: null, - encrypted: await this.storageService.get( - v1Keys.encProviderKeys + userId, - options - ), - }, - publicKey: null, - }, - profile: { - apiKeyClientId: await this.storageService.get(v1Keys.clientId, options), - authenticationStatus: null, - convertAccountToKeyConnector: await this.storageService.get( - v1Keys.convertAccountToKeyConnector, - options - ), - email: await this.storageService.get(v1Keys.userEmail, options), - emailVerified: await this.storageService.get( - v1Keys.emailVerified, - options - ), - entityId: null, - entityType: null, - everBeenUnlocked: null, - forcePasswordReset: null, - hasPremiumPersonally: null, - kdfIterations: await this.storageService.get( - v1Keys.kdfIterations, - options - ), - kdfType: await this.storageService.get(v1Keys.kdf, options), - keyHash: await this.storageService.get(v1Keys.keyHash, options), - lastActive: await this.storageService.get(v1Keys.lastActive, options), - lastSync: null, - userId: userId, - usesKeyConnector: null, - }, - settings: { - alwaysShowDock: await this.storageService.get( - v1Keys.alwaysShowDock, - options - ), - autoConfirmFingerPrints: await this.storageService.get( - v1Keys.autoConfirmFingerprints, - options - ), - autoFillOnPageLoadDefault: await this.storageService.get( - v1Keys.autoFillOnPageLoadDefault, - options - ), - biometricLocked: null, - biometricUnlock: await this.storageService.get( - v1Keys.biometricUnlock, - options - ), - clearClipboard: await this.storageService.get( - v1Keys.clearClipboard, - options - ), - defaultUriMatch: await this.storageService.get( - v1Keys.defaultUriMatch, - options - ), - disableAddLoginNotification: await this.storageService.get( - v1Keys.disableAddLoginNotification, - options - ), - disableAutoBiometricsPrompt: await this.storageService.get( - v1Keys.disableAutoBiometricsPrompt, - options - ), - disableAutoTotpCopy: await this.storageService.get( - v1Keys.disableAutoTotpCopy, - options - ), - disableBadgeCounter: await this.storageService.get( - v1Keys.disableBadgeCounter, - options - ), - disableChangedPasswordNotification: await this.storageService.get( - v1Keys.disableChangedPasswordNotification, - options - ), - disableContextMenuItem: await this.storageService.get( - v1Keys.disableContextMenuItem, - options - ), - disableGa: await this.storageService.get(v1Keys.disableGa, options), - dontShowCardsCurrentTab: await this.storageService.get( - v1Keys.dontShowCardsCurrentTab, - options - ), - dontShowIdentitiesCurrentTab: await this.storageService.get( - v1Keys.dontShowIdentitiesCurrentTab, - options - ), - enableAlwaysOnTop: await this.storageService.get( - v1Keys.enableAlwaysOnTop, - options - ), - enableAutoFillOnPageLoad: await this.storageService.get( - v1Keys.enableAutoFillOnPageLoad, - options - ), - enableBiometric: await this.storageService.get( - v1Keys.enableBiometric, - options - ), - enableBrowserIntegration: await this.storageService.get( - v1Keys.enableBrowserIntegration, - options - ), - enableBrowserIntegrationFingerprint: await this.storageService.get( - v1Keys.enableBrowserIntegrationFingerprint, - options - ), - enableCloseToTray: await this.storageService.get( - v1Keys.enableCloseToTray, - options - ), - enableFullWidth: await this.storageService.get( - v1Keys.enableFullWidth, - options - ), - enableGravitars: await this.storageService.get( - v1Keys.enableGravatars, - options - ), - enableMinimizeToTray: await this.storageService.get( - v1Keys.enableMinimizeToTray, - options - ), - enableStartToTray: await this.storageService.get( - v1Keys.enableStartToTray, - options - ), - enableTray: await this.storageService.get(v1Keys.enableTray, options), - environmentUrls: - (await this.storageService.get( - v1Keys.environmentUrls, - options - )) ?? new EnvironmentUrls(), - equivalentDomains: await this.storageService.get( - v1Keys.equivalentDomains, - options - ), - minimizeOnCopyToClipboard: await this.storageService.get( - v1Keys.minimizeOnCopyToClipboard, - options - ), - neverDomains: await this.storageService.get(v1Keys.neverDomains, options), - openAtLogin: await this.storageService.get(v1Keys.openAtLogin, options), - passwordGenerationOptions: await this.storageService.get( - v1Keys.passwordGenerationOptions, - options - ), - pinProtected: { - decrypted: null, - encrypted: await this.storageService.get(v1Keys.pinProtected, options), - }, - protectedPin: await this.storageService.get(v1Keys.protectedPin, options), - settings: await this.storageService.get( - v1KeyPrefixes.settings + userId, - options - ), - vaultTimeout: await this.storageService.get(v1Keys.vaultTimeout, options), - vaultTimeoutAction: await this.storageService.get( - v1Keys.vaultTimeoutAction, - options - ), - }, - tokens: { - accessToken: await this.storageService.get(v1Keys.accessToken, options), - decodedToken: null, - refreshToken: await this.storageService.get(v1Keys.refreshToken, options), - securityStamp: null, - }, - }), - }, - }; + await this.set(v1KeyPrefixes[keyPrefix] + userId, null); + } + } + }; - initialState.globals.environmentUrls = - (await this.storageService.get(v1Keys.environmentUrls, options)) ?? - new EnvironmentUrls(); - await this.storageService.save(keys.global, initialState.globals, options); - await this.storageService.save(keys.activeUserId, initialState.activeUserId, options); - if (initialState.activeUserId != null) { - await this.storageService.save( - initialState.activeUserId, - initialState.accounts[initialState.activeUserId] - ); + const globals: GlobalState = { + stateVersion: StateVersion.Two, + environmentUrls: + (await this.get(v1Keys.environmentUrls)) ?? new EnvironmentUrls(), + locale: await this.get(v1Keys.locale), + loginRedirect: null, + mainWindowSize: null, + noAutoPromptBiometrics: await this.get(v1Keys.disableAutoBiometricsPrompt), + noAutoPromptBiometricsText: await this.get(v1Keys.noAutoPromptBiometricsText), + openAtLogin: await this.get(v1Keys.openAtLogin), + organizationInvitation: null, + ssoCodeVerifier: await this.get(v1Keys.ssoCodeVerifier), + ssoOrganizationIdentifier: await this.get(v1Keys.ssoIdentifier), + ssoState: null, + rememberedEmail: await this.get(v1Keys.rememberedEmail), + theme: await this.get(v1Keys.theme), + vaultTimeout: await this.get(v1Keys.vaultTimeout), + vaultTimeoutAction: await this.get(v1Keys.vaultTimeoutAction), + window: null, + }; + + const userId = await this.get(v1Keys.userId); + + // (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, { + alwaysShowDock: await this.get(v1Keys.alwaysShowDock), + 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), + enableBrowserIntegration: await this.get(v1Keys.enableBrowserIntegration), + enableBrowserIntegrationFingerprint: await this.get( + v1Keys.enableBrowserIntegrationFingerprint + ), + enableCloseToTray: await this.get(v1Keys.enableCloseToTray), + enableFullWidth: await this.get(v1Keys.enableFullWidth), + enableGravitars: await this.get(v1Keys.enableGravatars), + enableMinimizeToTray: await this.get(v1Keys.enableMinimizeToTray), + enableStartToTray: await this.get(v1Keys.enableStartToTray), + enableTray: await this.get(v1Keys.enableTray), + environmentUrls: globals.environmentUrls, + equivalentDomains: await this.get(v1Keys.equivalentDomains), + minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), + neverDomains: await this.get(v1Keys.neverDomains), + openAtLogin: await this.get(v1Keys.openAtLogin), + 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.global, globals); + await this.set(keys.authenticatedAccounts, []); + await this.set(keys.activeUserId, null); + await clearV1Keys(); + return; } - await this.storageService.save(keys.authenticatedAccounts, initialState.authenticatedAccounts); + + globals.twoFactorToken = await this.get(v1KeyPrefixes.twoFactorToken + userId); + await this.set(keys.global, globals); + await this.set(userId, { + data: { + addEditCipherInfo: null, + ciphers: { + decrypted: null, + encrypted: await this.get<{ [id: string]: CipherData }>(v1KeyPrefixes.ciphers + userId), + }, + collapsedGroupings: null, + collections: { + decrypted: null, + encrypted: await this.get<{ [id: string]: CollectionData }>( + v1KeyPrefixes.collections + userId + ), + }, + eventCollection: await this.get(v1Keys.eventCollection), + folders: { + decrypted: null, + encrypted: await this.get<{ [id: string]: FolderData }>(v1KeyPrefixes.folders + userId), + }, + localData: null, + organizations: await this.get<{ [id: string]: OrganizationData }>( + v1KeyPrefixes.organizations + userId + ), + passwordGenerationHistory: { + decrypted: null, + encrypted: await this.get(v1Keys.history), + }, + policies: { + decrypted: null, + encrypted: await this.get<{ [id: string]: PolicyData }>(v1KeyPrefixes.policies + userId), + }, + providers: await this.get<{ [id: string]: ProviderData }>(v1KeyPrefixes.providers + userId), + sends: { + decrypted: null, + encrypted: await this.get<{ [id: string]: SendData }>(v1KeyPrefixes.sends + userId), + }, + }, + keys: { + apiKeyClientSecret: await this.get(v1Keys.clientSecret), + cryptoMasterKey: null, + cryptoMasterKeyAuto: null, + cryptoMasterKeyB64: null, + cryptoMasterKeyBiometric: null, + cryptoSymmetricKey: { + encrypted: await this.get(v1Keys.encKey), + decrypted: null, + }, + legacyEtmKey: null, + organizationKeys: { + decrypted: null, + encrypted: await this.get(v1Keys.encOrgKeys + userId), + }, + privateKey: { + decrypted: null, + encrypted: await this.get(v1Keys.encPrivate), + }, + providerKeys: { + decrypted: null, + encrypted: await this.get(v1Keys.encProviderKeys + userId), + }, + publicKey: null, + }, + profile: { + apiKeyClientId: await this.get(v1Keys.clientId), + authenticationStatus: null, + convertAccountToKeyConnector: await this.get(v1Keys.convertAccountToKeyConnector), + email: await this.get(v1Keys.userEmail), + emailVerified: await this.get(v1Keys.emailVerified), + entityId: null, + entityType: null, + everBeenUnlocked: null, + forcePasswordReset: null, + hasPremiumPersonally: null, + kdfIterations: await this.get(v1Keys.kdfIterations), + kdfType: await this.get(v1Keys.kdf), + keyHash: await this.get(v1Keys.keyHash), + lastActive: await this.get(v1Keys.lastActive), + lastSync: null, + userId: userId, + usesKeyConnector: null, + }, + settings: { + alwaysShowDock: await this.get(v1Keys.alwaysShowDock), + 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), + enableBrowserIntegration: await this.get(v1Keys.enableBrowserIntegration), + enableBrowserIntegrationFingerprint: await this.get( + v1Keys.enableBrowserIntegrationFingerprint + ), + enableCloseToTray: await this.get(v1Keys.enableCloseToTray), + enableFullWidth: await this.get(v1Keys.enableFullWidth), + enableGravitars: await this.get(v1Keys.enableGravatars), + enableMinimizeToTray: await this.get(v1Keys.enableMinimizeToTray), + enableStartToTray: await this.get(v1Keys.enableStartToTray), + enableTray: await this.get(v1Keys.enableTray), + environmentUrls: globals.environmentUrls, + equivalentDomains: await this.get(v1Keys.equivalentDomains), + minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), + neverDomains: await this.get(v1Keys.neverDomains), + openAtLogin: await this.get(v1Keys.openAtLogin), + 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), + }, + tokens: { + accessToken: await this.get(v1Keys.accessToken), + decodedToken: null, + refreshToken: await this.get(v1Keys.refreshToken), + securityStamp: null, + }, + }); + + await this.set(keys.authenticatedAccounts, [userId]); + await this.set(keys.activeUserId, userId); + await clearV1Keys(userId); if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "biometric" })) { await this.secureStorageService.save( @@ -538,4 +415,27 @@ export class StateMigrationService { await this.secureStorageService.remove(v1Keys.key); } } + + private get options(): StorageOptions { + return { htmlStorageLocation: HtmlStorageLocation.Local }; + } + + private get(key: string): Promise { + return this.storageService.get(key, this.options); + } + + private set(key: string, value: any): Promise { + if (value == null) { + return this.storageService.remove(key, this.options); + } + return this.storageService.save(key, value, this.options); + } + + private async getGlobals(): Promise { + return await this.get(keys.global); + } + + private async getCurrentStateVersion(): Promise { + return (await this.getGlobals())?.stateVersion; + } }