[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.
This commit is contained in:
Addison Beck 2022-01-20 08:30:00 -05:00 committed by GitHub
parent 11e7133aef
commit 57351d29a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 269 additions and 365 deletions

View File

@ -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();
}

View File

@ -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<TAccount extends Account = Account>
// 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<any>(keys.tempAccountSettings);
await this.storageService.remove(keys.tempAccountSettings);
}
await this.storageService.save(
account.profile.userId,

View File

@ -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<boolean> {
const currentStateVersion = (
await this.storageService.get<GlobalState>(keys.global, {
htmlStorageLocation: HtmlStorageLocation.Local,
})
)?.stateVersion;
const currentStateVersion = await this.getCurrentStateVersion();
return currentStateVersion == null || currentStateVersion < StateVersion.Latest;
}
async migrate(): Promise<void> {
let currentStateVersion =
(await this.storageService.get<GlobalState>(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<void> {
const options: StorageOptions = { htmlStorageLocation: HtmlStorageLocation.Local };
const userId = await this.storageService.get<string>("userId");
const initialState: State<Account> =
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<boolean>(
v1Keys.biometricAwaitingAcceptance,
options
),
biometricFingerprintValidated: await this.storageService.get<boolean>(
v1Keys.biometricFingerprintValidated,
options
),
biometricText: await this.storageService.get<string>(v1Keys.biometricText, options),
disableFavicon: await this.storageService.get<boolean>(
v1Keys.disableFavicon,
options
),
enableAlwaysOnTop: await this.storageService.get<boolean>(
v1Keys.enableAlwaysOnTop,
options
),
enableBiometrics: await this.storageService.get<boolean>(
v1Keys.enableBiometric,
options
),
environmentUrls: null,
installedVersion: await this.storageService.get<string>(
v1Keys.installedVersion,
options
),
locale: await this.storageService.get<string>(v1Keys.locale, options),
loginRedirect: null,
mainWindowSize: null,
noAutoPromptBiometrics: await this.storageService.get<boolean>(
v1Keys.disableAutoBiometricsPrompt,
options
),
noAutoPromptBiometricsText: await this.storageService.get<string>(
v1Keys.noAutoPromptBiometricsText,
options
),
openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options),
organizationInvitation: await this.storageService.get<string>("", options),
ssoCodeVerifier: await this.storageService.get<string>(
v1Keys.ssoCodeVerifier,
options
),
ssoOrganizationIdentifier: await this.storageService.get<string>(
v1Keys.ssoIdentifier,
options
),
ssoState: null,
rememberedEmail: await this.storageService.get<string>(
v1Keys.rememberedEmail,
options
),
stateVersion: StateVersion.Two,
theme: await this.storageService.get<string>(v1Keys.theme, options),
twoFactorToken: await this.storageService.get<string>(
v1KeyPrefixes.twoFactorToken + userId,
options
),
vaultTimeout: await this.storageService.get<number>(v1Keys.vaultTimeout, options),
vaultTimeoutAction: await this.storageService.get<string>(
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<EventData[]>(
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<GeneratedPasswordHistory[]>(
"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<string>(
v1Keys.clientSecret,
options
),
cryptoMasterKey: null,
cryptoMasterKeyAuto: null,
cryptoMasterKeyB64: null,
cryptoMasterKeyBiometric: null,
cryptoSymmetricKey: {
encrypted: await this.storageService.get<string>(v1Keys.encKey, options),
decrypted: null,
},
legacyEtmKey: null,
organizationKeys: {
decrypted: null,
encrypted: await this.storageService.get<any>(
v1Keys.encOrgKeys + userId,
options
),
},
privateKey: {
decrypted: null,
encrypted: await this.storageService.get<string>(v1Keys.encPrivate, options),
},
providerKeys: {
decrypted: null,
encrypted: await this.storageService.get<any>(
v1Keys.encProviderKeys + userId,
options
),
},
publicKey: null,
},
profile: {
apiKeyClientId: await this.storageService.get<string>(v1Keys.clientId, options),
authenticationStatus: null,
convertAccountToKeyConnector: await this.storageService.get<boolean>(
v1Keys.convertAccountToKeyConnector,
options
),
email: await this.storageService.get<string>(v1Keys.userEmail, options),
emailVerified: await this.storageService.get<boolean>(
v1Keys.emailVerified,
options
),
entityId: null,
entityType: null,
everBeenUnlocked: null,
forcePasswordReset: null,
hasPremiumPersonally: null,
kdfIterations: await this.storageService.get<number>(
v1Keys.kdfIterations,
options
),
kdfType: await this.storageService.get<KdfType>(v1Keys.kdf, options),
keyHash: await this.storageService.get<string>(v1Keys.keyHash, options),
lastActive: await this.storageService.get<number>(v1Keys.lastActive, options),
lastSync: null,
userId: userId,
usesKeyConnector: null,
},
settings: {
alwaysShowDock: await this.storageService.get<boolean>(
v1Keys.alwaysShowDock,
options
),
autoConfirmFingerPrints: await this.storageService.get<boolean>(
v1Keys.autoConfirmFingerprints,
options
),
autoFillOnPageLoadDefault: await this.storageService.get<boolean>(
v1Keys.autoFillOnPageLoadDefault,
options
),
biometricLocked: null,
biometricUnlock: await this.storageService.get<boolean>(
v1Keys.biometricUnlock,
options
),
clearClipboard: await this.storageService.get<number>(
v1Keys.clearClipboard,
options
),
defaultUriMatch: await this.storageService.get<any>(
v1Keys.defaultUriMatch,
options
),
disableAddLoginNotification: await this.storageService.get<boolean>(
v1Keys.disableAddLoginNotification,
options
),
disableAutoBiometricsPrompt: await this.storageService.get<boolean>(
v1Keys.disableAutoBiometricsPrompt,
options
),
disableAutoTotpCopy: await this.storageService.get<boolean>(
v1Keys.disableAutoTotpCopy,
options
),
disableBadgeCounter: await this.storageService.get<boolean>(
v1Keys.disableBadgeCounter,
options
),
disableChangedPasswordNotification: await this.storageService.get<boolean>(
v1Keys.disableChangedPasswordNotification,
options
),
disableContextMenuItem: await this.storageService.get<boolean>(
v1Keys.disableContextMenuItem,
options
),
disableGa: await this.storageService.get<boolean>(v1Keys.disableGa, options),
dontShowCardsCurrentTab: await this.storageService.get<boolean>(
v1Keys.dontShowCardsCurrentTab,
options
),
dontShowIdentitiesCurrentTab: await this.storageService.get<boolean>(
v1Keys.dontShowIdentitiesCurrentTab,
options
),
enableAlwaysOnTop: await this.storageService.get<boolean>(
v1Keys.enableAlwaysOnTop,
options
),
enableAutoFillOnPageLoad: await this.storageService.get<boolean>(
v1Keys.enableAutoFillOnPageLoad,
options
),
enableBiometric: await this.storageService.get<boolean>(
v1Keys.enableBiometric,
options
),
enableBrowserIntegration: await this.storageService.get<boolean>(
v1Keys.enableBrowserIntegration,
options
),
enableBrowserIntegrationFingerprint: await this.storageService.get<boolean>(
v1Keys.enableBrowserIntegrationFingerprint,
options
),
enableCloseToTray: await this.storageService.get<boolean>(
v1Keys.enableCloseToTray,
options
),
enableFullWidth: await this.storageService.get<boolean>(
v1Keys.enableFullWidth,
options
),
enableGravitars: await this.storageService.get<boolean>(
v1Keys.enableGravatars,
options
),
enableMinimizeToTray: await this.storageService.get<boolean>(
v1Keys.enableMinimizeToTray,
options
),
enableStartToTray: await this.storageService.get<boolean>(
v1Keys.enableStartToTray,
options
),
enableTray: await this.storageService.get<boolean>(v1Keys.enableTray, options),
environmentUrls:
(await this.storageService.get<EnvironmentUrls>(
v1Keys.environmentUrls,
options
)) ?? new EnvironmentUrls(),
equivalentDomains: await this.storageService.get<any>(
v1Keys.equivalentDomains,
options
),
minimizeOnCopyToClipboard: await this.storageService.get<boolean>(
v1Keys.minimizeOnCopyToClipboard,
options
),
neverDomains: await this.storageService.get<any>(v1Keys.neverDomains, options),
openAtLogin: await this.storageService.get<boolean>(v1Keys.openAtLogin, options),
passwordGenerationOptions: await this.storageService.get<any>(
v1Keys.passwordGenerationOptions,
options
),
pinProtected: {
decrypted: null,
encrypted: await this.storageService.get<string>(v1Keys.pinProtected, options),
},
protectedPin: await this.storageService.get<string>(v1Keys.protectedPin, options),
settings: await this.storageService.get<any>(
v1KeyPrefixes.settings + userId,
options
),
vaultTimeout: await this.storageService.get<number>(v1Keys.vaultTimeout, options),
vaultTimeoutAction: await this.storageService.get<string>(
v1Keys.vaultTimeoutAction,
options
),
},
tokens: {
accessToken: await this.storageService.get<string>(v1Keys.accessToken, options),
decodedToken: null,
refreshToken: await this.storageService.get<string>(v1Keys.refreshToken, options),
securityStamp: null,
},
}),
},
};
await this.set(v1KeyPrefixes[keyPrefix] + userId, null);
}
}
};
initialState.globals.environmentUrls =
(await this.storageService.get<EnvironmentUrls>(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<EnvironmentUrls>(v1Keys.environmentUrls)) ?? new EnvironmentUrls(),
locale: await this.get<string>(v1Keys.locale),
loginRedirect: null,
mainWindowSize: null,
noAutoPromptBiometrics: await this.get<boolean>(v1Keys.disableAutoBiometricsPrompt),
noAutoPromptBiometricsText: await this.get<string>(v1Keys.noAutoPromptBiometricsText),
openAtLogin: await this.get<boolean>(v1Keys.openAtLogin),
organizationInvitation: null,
ssoCodeVerifier: await this.get<string>(v1Keys.ssoCodeVerifier),
ssoOrganizationIdentifier: await this.get<string>(v1Keys.ssoIdentifier),
ssoState: null,
rememberedEmail: await this.get<string>(v1Keys.rememberedEmail),
theme: await this.get<string>(v1Keys.theme),
vaultTimeout: await this.get<number>(v1Keys.vaultTimeout),
vaultTimeoutAction: await this.get<string>(v1Keys.vaultTimeoutAction),
window: null,
};
const userId = await this.get<string>(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<boolean>(v1Keys.alwaysShowDock),
autoConfirmFingerPrints: await this.get<boolean>(v1Keys.autoConfirmFingerprints),
autoFillOnPageLoadDefault: await this.get<boolean>(v1Keys.autoFillOnPageLoadDefault),
biometricLocked: null,
biometricUnlock: await this.get<boolean>(v1Keys.biometricUnlock),
clearClipboard: await this.get<number>(v1Keys.clearClipboard),
defaultUriMatch: await this.get<any>(v1Keys.defaultUriMatch),
disableAddLoginNotification: await this.get<boolean>(v1Keys.disableAddLoginNotification),
disableAutoBiometricsPrompt: await this.get<boolean>(v1Keys.disableAutoBiometricsPrompt),
disableAutoTotpCopy: await this.get<boolean>(v1Keys.disableAutoTotpCopy),
disableBadgeCounter: await this.get<boolean>(v1Keys.disableBadgeCounter),
disableChangedPasswordNotification: await this.get<boolean>(
v1Keys.disableChangedPasswordNotification
),
disableContextMenuItem: await this.get<boolean>(v1Keys.disableContextMenuItem),
disableGa: await this.get<boolean>(v1Keys.disableGa),
dontShowCardsCurrentTab: await this.get<boolean>(v1Keys.dontShowCardsCurrentTab),
dontShowIdentitiesCurrentTab: await this.get<boolean>(v1Keys.dontShowIdentitiesCurrentTab),
enableAlwaysOnTop: await this.get<boolean>(v1Keys.enableAlwaysOnTop),
enableAutoFillOnPageLoad: await this.get<boolean>(v1Keys.enableAutoFillOnPageLoad),
enableBiometric: await this.get<boolean>(v1Keys.enableBiometric),
enableBrowserIntegration: await this.get<boolean>(v1Keys.enableBrowserIntegration),
enableBrowserIntegrationFingerprint: await this.get<boolean>(
v1Keys.enableBrowserIntegrationFingerprint
),
enableCloseToTray: await this.get<boolean>(v1Keys.enableCloseToTray),
enableFullWidth: await this.get<boolean>(v1Keys.enableFullWidth),
enableGravitars: await this.get<boolean>(v1Keys.enableGravatars),
enableMinimizeToTray: await this.get<boolean>(v1Keys.enableMinimizeToTray),
enableStartToTray: await this.get<boolean>(v1Keys.enableStartToTray),
enableTray: await this.get<boolean>(v1Keys.enableTray),
environmentUrls: globals.environmentUrls,
equivalentDomains: await this.get<any>(v1Keys.equivalentDomains),
minimizeOnCopyToClipboard: await this.get<boolean>(v1Keys.minimizeOnCopyToClipboard),
neverDomains: await this.get<any>(v1Keys.neverDomains),
openAtLogin: await this.get<boolean>(v1Keys.openAtLogin),
passwordGenerationOptions: await this.get<any>(v1Keys.passwordGenerationOptions),
pinProtected: {
decrypted: null,
encrypted: await this.get<string>(v1Keys.pinProtected),
},
protectedPin: await this.get<string>(v1Keys.protectedPin),
settings: null,
vaultTimeout: await this.get<number>(v1Keys.vaultTimeout),
vaultTimeoutAction: await this.get<string>(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<string>(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<EventData[]>(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<GeneratedPasswordHistory[]>(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<string>(v1Keys.clientSecret),
cryptoMasterKey: null,
cryptoMasterKeyAuto: null,
cryptoMasterKeyB64: null,
cryptoMasterKeyBiometric: null,
cryptoSymmetricKey: {
encrypted: await this.get<string>(v1Keys.encKey),
decrypted: null,
},
legacyEtmKey: null,
organizationKeys: {
decrypted: null,
encrypted: await this.get<any>(v1Keys.encOrgKeys + userId),
},
privateKey: {
decrypted: null,
encrypted: await this.get<string>(v1Keys.encPrivate),
},
providerKeys: {
decrypted: null,
encrypted: await this.get<any>(v1Keys.encProviderKeys + userId),
},
publicKey: null,
},
profile: {
apiKeyClientId: await this.get<string>(v1Keys.clientId),
authenticationStatus: null,
convertAccountToKeyConnector: await this.get<boolean>(v1Keys.convertAccountToKeyConnector),
email: await this.get<string>(v1Keys.userEmail),
emailVerified: await this.get<boolean>(v1Keys.emailVerified),
entityId: null,
entityType: null,
everBeenUnlocked: null,
forcePasswordReset: null,
hasPremiumPersonally: null,
kdfIterations: await this.get<number>(v1Keys.kdfIterations),
kdfType: await this.get<KdfType>(v1Keys.kdf),
keyHash: await this.get<string>(v1Keys.keyHash),
lastActive: await this.get<number>(v1Keys.lastActive),
lastSync: null,
userId: userId,
usesKeyConnector: null,
},
settings: {
alwaysShowDock: await this.get<boolean>(v1Keys.alwaysShowDock),
autoConfirmFingerPrints: await this.get<boolean>(v1Keys.autoConfirmFingerprints),
autoFillOnPageLoadDefault: await this.get<boolean>(v1Keys.autoFillOnPageLoadDefault),
biometricLocked: null,
biometricUnlock: await this.get<boolean>(v1Keys.biometricUnlock),
clearClipboard: await this.get<number>(v1Keys.clearClipboard),
defaultUriMatch: await this.get<any>(v1Keys.defaultUriMatch),
disableAddLoginNotification: await this.get<boolean>(v1Keys.disableAddLoginNotification),
disableAutoBiometricsPrompt: await this.get<boolean>(v1Keys.disableAutoBiometricsPrompt),
disableAutoTotpCopy: await this.get<boolean>(v1Keys.disableAutoTotpCopy),
disableBadgeCounter: await this.get<boolean>(v1Keys.disableBadgeCounter),
disableChangedPasswordNotification: await this.get<boolean>(
v1Keys.disableChangedPasswordNotification
),
disableContextMenuItem: await this.get<boolean>(v1Keys.disableContextMenuItem),
disableGa: await this.get<boolean>(v1Keys.disableGa),
dontShowCardsCurrentTab: await this.get<boolean>(v1Keys.dontShowCardsCurrentTab),
dontShowIdentitiesCurrentTab: await this.get<boolean>(v1Keys.dontShowIdentitiesCurrentTab),
enableAlwaysOnTop: await this.get<boolean>(v1Keys.enableAlwaysOnTop),
enableAutoFillOnPageLoad: await this.get<boolean>(v1Keys.enableAutoFillOnPageLoad),
enableBiometric: await this.get<boolean>(v1Keys.enableBiometric),
enableBrowserIntegration: await this.get<boolean>(v1Keys.enableBrowserIntegration),
enableBrowserIntegrationFingerprint: await this.get<boolean>(
v1Keys.enableBrowserIntegrationFingerprint
),
enableCloseToTray: await this.get<boolean>(v1Keys.enableCloseToTray),
enableFullWidth: await this.get<boolean>(v1Keys.enableFullWidth),
enableGravitars: await this.get<boolean>(v1Keys.enableGravatars),
enableMinimizeToTray: await this.get<boolean>(v1Keys.enableMinimizeToTray),
enableStartToTray: await this.get<boolean>(v1Keys.enableStartToTray),
enableTray: await this.get<boolean>(v1Keys.enableTray),
environmentUrls: globals.environmentUrls,
equivalentDomains: await this.get<any>(v1Keys.equivalentDomains),
minimizeOnCopyToClipboard: await this.get<boolean>(v1Keys.minimizeOnCopyToClipboard),
neverDomains: await this.get<any>(v1Keys.neverDomains),
openAtLogin: await this.get<boolean>(v1Keys.openAtLogin),
passwordGenerationOptions: await this.get<any>(v1Keys.passwordGenerationOptions),
pinProtected: {
decrypted: null,
encrypted: await this.get<string>(v1Keys.pinProtected),
},
protectedPin: await this.get<string>(v1Keys.protectedPin),
settings: await this.get<any>(v1KeyPrefixes.settings + userId),
vaultTimeout: await this.get<number>(v1Keys.vaultTimeout),
vaultTimeoutAction: await this.get<string>(v1Keys.vaultTimeoutAction),
},
tokens: {
accessToken: await this.get<string>(v1Keys.accessToken),
decodedToken: null,
refreshToken: await this.get<string>(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<T>(key: string): Promise<T> {
return this.storageService.get<T>(key, this.options);
}
private set(key: string, value: any): Promise<any> {
if (value == null) {
return this.storageService.remove(key, this.options);
}
return this.storageService.save(key, value, this.options);
}
private async getGlobals(): Promise<GlobalState> {
return await this.get<GlobalState>(keys.global);
}
private async getCurrentStateVersion(): Promise<StateVersion> {
return (await this.getGlobals())?.stateVersion;
}
}