1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00
Files
browser/common/src/services/stateMigration.service.ts
Addison Beck 9e26336549 [feat(Account Switching)] Allow for extending application state (#584)
* [feat(Account Switching)] Allow for extending application state

* [bug(Account Switching)] Remove hardcoded dev urls

* [bug(Account Switching)] Init Account when signing in

* [bug(Account Switching)] Check for state migration version in local storage for web

* [bug(Account Switching)] Fix never lock configurations

* [chore] Prettier merge

* [bug] Move environmentUrls to global state

* [chore] Ran prettier

* [bug]change storage location for enityId and type

* [style] Ran prettier

Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com>
2021-12-20 08:48:47 -05:00

514 lines
21 KiB
TypeScript

import { StorageService } from "../abstractions/storage.service";
import { Account } from "../models/domain/account";
import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory";
import { State } from "../models/domain/state";
import { StorageOptions } from "../models/domain/storageOptions";
import { CipherData } from "../models/data/cipherData";
import { CollectionData } from "../models/data/collectionData";
import { EventData } from "../models/data/eventData";
import { FolderData } from "../models/data/folderData";
import { OrganizationData } from "../models/data/organizationData";
import { PolicyData } from "../models/data/policyData";
import { ProviderData } from "../models/data/providerData";
import { SendData } from "../models/data/sendData";
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
import { KdfType } from "../enums/kdfType";
// 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 = {
accessToken: "accessToken",
alwaysShowDock: "alwaysShowDock",
autoConfirmFingerprints: "autoConfirmFingerprints",
autoFillOnPageLoadDefault: "autoFillOnPageLoadDefault",
biometricAwaitingAcceptance: "biometricAwaitingAcceptance",
biometricFingerprintValidated: "biometricFingerprintValidated",
biometricText: "biometricText",
biometricUnlock: "biometric",
clearClipboard: "clearClipboardKey",
clientId: "clientId",
clientSecret: "clientSecret",
collapsedGroupings: "collapsedGroupings",
convertAccountToKeyConnector: "convertAccountToKeyConnector",
defaultUriMatch: "defaultUriMatch",
disableAddLoginNotification: "disableAddLoginNotification",
disableAutoBiometricsPrompt: "noAutoPromptBiometrics",
disableAutoTotpCopy: "disableAutoTotpCopy",
disableBadgeCounter: "disableBadgeCounter",
disableChangedPasswordNotification: "disableChangedPasswordNotification",
disableContextMenuItem: "disableContextMenuItem",
disableFavicon: "disableFavicon",
disableGa: "disableGa",
dontShowCardsCurrentTab: "dontShowCardsCurrentTab",
dontShowIdentitiesCurrentTab: "dontShowIdentitiesCurrentTab",
emailVerified: "emailVerified",
enableAlwaysOnTop: "enableAlwaysOnTopKey",
enableAutoFillOnPageLoad: "enableAutoFillOnPageLoad",
enableBiometric: "enabledBiometric",
enableBrowserIntegration: "enableBrowserIntegration",
enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint",
enableCloseToTray: "enableCloseToTray",
enableFullWidth: "enableFullWidth",
enableGravatars: "enableGravatars",
enableMinimizeToTray: "enableMinimizeToTray",
enableStartToTray: "enableStartToTrayKey",
enableTray: "enableTray",
encKey: "encKey", // Generated Symmetric Key
encOrgKeys: "encOrgKeys",
encPrivate: "encPrivateKey",
encProviderKeys: "encProviderKeys",
entityId: "entityId",
entityType: "entityType",
environmentUrls: "environmentUrls",
equivalentDomains: "equivalentDomains",
eventCollection: "eventCollection",
forcePasswordReset: "forcePasswordReset",
history: "generatedPasswordHistory",
installedVersion: "installedVersion",
kdf: "kdf",
kdfIterations: "kdfIterations",
key: "key", // Master Key
keyHash: "keyHash",
lastActive: "lastActive",
localData: "sitesLocalData",
locale: "locale",
mainWindowSize: "mainWindowSize",
minimizeOnCopyToClipboard: "minimizeOnCopyToClipboardKey",
neverDomains: "neverDomains",
noAutoPromptBiometricsText: "noAutoPromptBiometricsText",
openAtLogin: "openAtLogin",
passwordGenerationOptions: "passwordGenerationOptions",
pinProtected: "pinProtectedKey",
protectedPin: "protectedPin",
refreshToken: "refreshToken",
ssoCodeVerifier: "ssoCodeVerifier",
ssoIdentifier: "ssoOrgIdentifier",
ssoState: "ssoState",
stamp: "securityStamp",
theme: "theme",
userEmail: "userEmail",
userId: "userId",
usesConnector: "usesKeyConnector",
vaultTimeoutAction: "vaultTimeoutAction",
vaultTimeout: "lockOption",
rememberedEmail: "rememberedEmail",
};
const v1KeyPrefixes = {
ciphers: "ciphers_",
collections: "collections_",
folders: "folders_",
lastSync: "lastSync_",
policies: "policies_",
twoFactorToken: "twoFactorToken_",
organizations: "organizations_",
providers: "providers_",
sends: "sends_",
settings: "settings_",
};
export class StateMigrationService {
readonly latestVersion: number = 2;
constructor(
protected storageService: StorageService,
protected secureStorageService: StorageService
) {}
async needsMigration(): Promise<boolean> {
const currentStateVersion = (
await this.storageService.get<State<Account>>("state", {
htmlStorageLocation: HtmlStorageLocation.Local,
})
)?.globals?.stateVersion;
return currentStateVersion == null || currentStateVersion < this.latestVersion;
}
async migrate(): Promise<void> {
let currentStateVersion =
(await this.storageService.get<State<Account>>("state"))?.globals?.stateVersion ?? 1;
while (currentStateVersion < this.latestVersion) {
switch (currentStateVersion) {
case 1:
await this.migrateStateFrom1To2();
break;
}
currentStateVersion += 1;
}
}
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: {
stateVersion: 2,
},
accounts: {},
activeUserId: null,
}
: {
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: await this.storageService.get<any>(v1Keys.environmentUrls, options),
installedVersion: await this.storageService.get<string>(
v1Keys.installedVersion,
options
),
lastActive: await this.storageService.get<number>(v1Keys.lastActive, 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: 2,
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),
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.storageService.save("state", initialState, options);
if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "biometric" })) {
await this.secureStorageService.save(
`${userId}_masterkey_biometric`,
await this.secureStorageService.get(v1Keys.key, { keySuffix: "biometric" }),
{ keySuffix: "biometric" }
);
await this.secureStorageService.remove(v1Keys.key, { keySuffix: "biometric" });
}
if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "auto" })) {
await this.secureStorageService.save(
`${userId}_masterkey_auto`,
await this.secureStorageService.get(v1Keys.key, { keySuffix: "auto" }),
{ keySuffix: "auto" }
);
await this.secureStorageService.remove(v1Keys.key, { keySuffix: "auto" });
}
if (await this.secureStorageService.has(v1Keys.key)) {
await this.secureStorageService.save(
`${userId}_masterkey`,
await this.secureStorageService.get(v1Keys.key)
);
await this.secureStorageService.remove(v1Keys.key);
}
}
}