Files
standardnotes-app-web/packages/snjs/lib/Migrations/Versions/2_0_0.ts
Karol Sójko bb4f1ff099 fix(snjs): refreshing sessions (#2106)
* fix(snjs): refreshing sessions

* fix(snjs): bring back all tests

* fix(snjs): passing session tokens values

* fix(api): remove redundant specs

* fix(snjs): add projecting sessions to storage values

* fix(snjs): deps tree

* fix(snjs): bring back subscription tests

* fix(snjs): remove only tag for migration tests

* fix(snjs): session specs
2022-12-19 08:28:10 +01:00

731 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AnyKeyParamsContent, ContentType, ProtocolVersion } from '@standardnotes/common'
import { Migration } from '@Lib/Migrations/Migration'
import { MigrationServices } from '../MigrationServices'
import { PreviousSnjsVersion2_0_0 } from '../../Version'
import { SNRootKey, CreateNewRootKey } from '@standardnotes/encryption'
import { DiskStorageService } from '../../Services/Storage/DiskStorageService'
import { StorageReader1_0_0 } from '../StorageReaders/Versions/Reader1_0_0'
import * as Models from '@standardnotes/models'
import * as Services from '@standardnotes/services'
import * as Utils from '@standardnotes/utils'
import { isEnvironmentMobile, isEnvironmentWebOrDesktop } from '@Lib/Application/Platforms'
import {
getIncrementedDirtyIndex,
LegacyMobileKeychainStructure,
PayloadTimestampDefaults,
} from '@standardnotes/models'
import { isMobileDevice } from '@standardnotes/services'
import { LegacySession } from '@standardnotes/domain-core'
interface LegacyStorageContent extends Models.ItemContent {
storage: unknown
}
interface LegacyAccountKeysValue {
ak: string
mk: string
version: string
jwt: string
}
interface LegacyRootKeyContent extends Models.RootKeyContent {
accountKeys?: LegacyAccountKeysValue
}
const LEGACY_SESSION_TOKEN_KEY = 'jwt'
export class Migration2_0_0 extends Migration {
private legacyReader!: StorageReader1_0_0
constructor(services: MigrationServices) {
super(services)
this.legacyReader = new StorageReader1_0_0(
this.services.deviceInterface,
this.services.identifier,
this.services.environment,
)
}
static override version() {
return PreviousSnjsVersion2_0_0
}
protected registerStageHandlers() {
this.registerStageHandler(Services.ApplicationStage.PreparingForLaunch_0, async () => {
if (isEnvironmentWebOrDesktop(this.services.environment)) {
await this.migrateStorageStructureForWebDesktop()
} else if (isEnvironmentMobile(this.services.environment)) {
await this.migrateStorageStructureForMobile()
}
})
this.registerStageHandler(Services.ApplicationStage.StorageDecrypted_09, async () => {
await this.migrateArbitraryRawStorageToManagedStorageAllPlatforms()
if (isEnvironmentMobile(this.services.environment)) {
await this.migrateMobilePreferences()
}
await this.migrateSessionStorage()
await this.deleteLegacyStorageValues()
})
this.registerStageHandler(Services.ApplicationStage.LoadingDatabase_11, async () => {
await this.createDefaultItemsKeyForAllPlatforms()
this.markDone()
})
}
/**
* Web
* Migrates legacy storage structure into new managed format.
* If encrypted storage exists, we need to first decrypt it with the passcode.
* Then extract the account key from it. Then, encrypt storage with the
* account key. Then encrypt the account key with the passcode and store it
* within the new storage format.
*
* Generate note: We do not use the keychain if passcode is available.
*/
private async migrateStorageStructureForWebDesktop() {
const deviceInterface = this.services.deviceInterface
const newStorageRawStructure: Services.StorageValuesObject = {
[Services.ValueModesKeys.Wrapped]: {} as Models.LocalStorageEncryptedContextualPayload,
[Services.ValueModesKeys.Unwrapped]: {},
[Services.ValueModesKeys.Nonwrapped]: {},
}
const rawAccountKeyParams = (await this.legacyReader.getAccountKeyParams()) as AnyKeyParamsContent
/** Could be null if no account, or if account and storage is encrypted */
if (rawAccountKeyParams) {
newStorageRawStructure.nonwrapped[Services.StorageKey.RootKeyParams] = rawAccountKeyParams
}
const encryptedStorage = (await deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.WebEncryptedStorageKey,
)) as Models.EncryptedTransferPayload
if (encryptedStorage) {
const encryptedStoragePayload = new Models.EncryptedPayload(encryptedStorage)
const passcodeResult = await this.webDesktopHelperGetPasscodeKeyAndDecryptEncryptedStorage(
encryptedStoragePayload,
)
const passcodeKey = passcodeResult.key
const decryptedStoragePayload = passcodeResult.decryptedStoragePayload
const passcodeParams = passcodeResult.keyParams
newStorageRawStructure.nonwrapped[Services.StorageKey.RootKeyWrapperKeyParams] = passcodeParams.getPortableValue()
const rawStorageValueStore = Utils.Copy(decryptedStoragePayload.content.storage)
const storageValueStore: Record<string, unknown> = Utils.jsonParseEmbeddedKeys(rawStorageValueStore)
/** Store previously encrypted auth_params into new nonwrapped value key */
const accountKeyParams = storageValueStore[Services.LegacyKeys1_0_0.AllAccountKeyParamsKey] as AnyKeyParamsContent
newStorageRawStructure.nonwrapped[Services.StorageKey.RootKeyParams] = accountKeyParams
let keyToEncryptStorageWith = passcodeKey
/** Extract account key (mk, pw, ak) if it exists */
const hasAccountKeys = !Utils.isNullOrUndefined(storageValueStore.mk)
if (hasAccountKeys) {
const { accountKey, wrappedKey } = await this.webDesktopHelperExtractAndWrapAccountKeysFromValueStore(
passcodeKey,
accountKeyParams,
storageValueStore,
)
keyToEncryptStorageWith = accountKey
newStorageRawStructure.nonwrapped[Services.StorageKey.WrappedRootKey] = wrappedKey
}
/** Encrypt storage with proper key */
newStorageRawStructure.wrapped = await this.webDesktopHelperEncryptStorage(
keyToEncryptStorageWith,
decryptedStoragePayload,
storageValueStore,
)
} else {
/**
* No encrypted storage, take account keys (if they exist) out of raw storage
* and place them in the keychain. */
const ak = await this.services.deviceInterface.getRawStorageValue('ak')
const mk = await this.services.deviceInterface.getRawStorageValue('mk')
if (ak || mk) {
const version = rawAccountKeyParams.version || (await this.getFallbackRootKeyVersion())
const accountKey = CreateNewRootKey({
masterKey: mk as string,
dataAuthenticationKey: ak as string,
version: version,
keyParams: rawAccountKeyParams,
})
await this.services.deviceInterface.setNamespacedKeychainValue(
accountKey.getKeychainValue(),
this.services.identifier,
)
}
}
/** Persist storage under new key and structure */
await this.allPlatformHelperSetStorageStructure(newStorageRawStructure)
}
/**
* Helper
* All platforms
*/
private async allPlatformHelperSetStorageStructure(rawStructure: Services.StorageValuesObject) {
const newStructure = DiskStorageService.DefaultValuesObject(
rawStructure.wrapped,
rawStructure.unwrapped,
rawStructure.nonwrapped,
) as Partial<Services.StorageValuesObject>
newStructure[Services.ValueModesKeys.Unwrapped] = undefined
await this.services.deviceInterface.setRawStorageValue(
Services.namespacedKey(this.services.identifier, Services.RawStorageKey.StorageObject),
JSON.stringify(newStructure),
)
}
/**
* Helper
* Web/desktop only
*/
private async webDesktopHelperGetPasscodeKeyAndDecryptEncryptedStorage(
encryptedPayload: Models.EncryptedPayloadInterface,
) {
const rawPasscodeParams = (await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.WebPasscodeParamsKey,
)) as AnyKeyParamsContent
const passcodeParams = this.services.protocolService.createKeyParams(rawPasscodeParams)
/** Decrypt it with the passcode */
let decryptedStoragePayload:
| Models.DecryptedPayloadInterface<LegacyStorageContent>
| Models.EncryptedPayloadInterface = encryptedPayload
let passcodeKey: SNRootKey | undefined
await this.promptForPasscodeUntilCorrect(async (candidate: string) => {
passcodeKey = await this.services.protocolService.computeRootKey(candidate, passcodeParams)
decryptedStoragePayload = await this.services.protocolService.decryptSplitSingle({
usesRootKey: {
items: [encryptedPayload],
key: passcodeKey,
},
})
return !Models.isErrorDecryptingPayload(decryptedStoragePayload)
})
return {
decryptedStoragePayload:
decryptedStoragePayload as unknown as Models.DecryptedPayloadInterface<LegacyStorageContent>,
key: passcodeKey as SNRootKey,
keyParams: passcodeParams,
}
}
/**
* Helper
* Web/desktop only
*/
private async webDesktopHelperExtractAndWrapAccountKeysFromValueStore(
passcodeKey: SNRootKey,
accountKeyParams: AnyKeyParamsContent,
storageValueStore: Record<string, unknown>,
) {
const version = accountKeyParams?.version || (await this.getFallbackRootKeyVersion())
const accountKey = CreateNewRootKey({
masterKey: storageValueStore.mk as string,
dataAuthenticationKey: storageValueStore.ak as string,
version: version,
keyParams: accountKeyParams,
})
delete storageValueStore.mk
delete storageValueStore.pw
delete storageValueStore.ak
const accountKeyPayload = accountKey.payload
/** Encrypt account key with passcode */
const encryptedAccountKey = await this.services.protocolService.encryptSplitSingle({
usesRootKey: {
items: [accountKeyPayload],
key: passcodeKey,
},
})
return {
accountKey: accountKey,
wrappedKey: Models.CreateEncryptedLocalStorageContextPayload(encryptedAccountKey),
}
}
/**
* Helper
* Web/desktop only
* Encrypt storage with account key
*/
async webDesktopHelperEncryptStorage(
key: SNRootKey,
decryptedStoragePayload: Models.DecryptedPayloadInterface,
storageValueStore: Record<string, unknown>,
) {
const wrapped = await this.services.protocolService.encryptSplitSingle({
usesRootKey: {
items: [
decryptedStoragePayload.copy({
content_type: ContentType.EncryptedStorage,
content: storageValueStore as unknown as Models.ItemContent,
}),
],
key: key,
},
})
return Models.CreateEncryptedLocalStorageContextPayload(wrapped)
}
/**
* Mobile
* On mobile legacy structure is mostly similar to new structure,
* in that the account key is encrypted with the passcode. But mobile did
* not have encrypted storage, so we simply need to transfer all existing
* storage values into new managed structure.
*
* In version <= 3.0.16 on mobile, encrypted account keys were stored in the keychain
* under `encryptedAccountKeys`. In 3.0.17 a migration was introduced that moved this value
* to storage under key `encrypted_account_keys`. We need to anticipate the keys being in
* either location.
*
* If no account but passcode only, the only thing we stored on mobile
* previously was keys.offline.pw and keys.offline.timing in the keychain
* that we compared against for valid decryption.
* In the new version, we know a passcode is correct if it can decrypt storage.
* As part of the migration, well need to request the raw passcode from user,
* compare it against the keychain offline.pw value, and if correct,
* migrate storage to new structure, and encrypt with passcode key.
*
* If account only, take the value in the keychain, and rename the values
* (i.e mk > masterKey).
* @access private
*/
async migrateStorageStructureForMobile() {
Utils.assert(isMobileDevice(this.services.deviceInterface))
const keychainValue =
(await this.services.deviceInterface.getRawKeychainValue()) as unknown as LegacyMobileKeychainStructure
const wrappedAccountKey = ((await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobileWrappedRootKeyKey,
)) || keychainValue?.encryptedAccountKeys) as Models.EncryptedTransferPayload
const rawAccountKeyParams = (await this.legacyReader.getAccountKeyParams()) as AnyKeyParamsContent
const rawPasscodeParams = (await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobilePasscodeParamsKey,
)) as AnyKeyParamsContent
const firstRunValue = await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.NonwrappedStorageKey.MobileFirstRun,
)
const rawStructure: Services.StorageValuesObject = {
[Services.ValueModesKeys.Nonwrapped]: {
[Services.StorageKey.WrappedRootKey]: wrappedAccountKey,
/** A 'hash' key may be present from legacy versions that should be deleted */
[Services.StorageKey.RootKeyWrapperKeyParams]: Utils.omitByCopy(rawPasscodeParams, ['hash' as never]),
[Services.StorageKey.RootKeyParams]: rawAccountKeyParams,
[Services.NonwrappedStorageKey.MobileFirstRun]: firstRunValue,
},
[Services.ValueModesKeys.Unwrapped]: {},
[Services.ValueModesKeys.Wrapped]: {} as Models.LocalStorageDecryptedContextualPayload,
}
const biometricPrefs = (await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobileBiometricsPrefs,
)) as { enabled: boolean; timing: unknown }
if (biometricPrefs) {
rawStructure.nonwrapped[Services.StorageKey.BiometricsState] = biometricPrefs.enabled
rawStructure.nonwrapped[Services.StorageKey.MobileBiometricsTiming] = biometricPrefs.timing
}
const passcodeKeyboardType = await this.services.deviceInterface.getRawStorageValue(
Services.LegacyKeys1_0_0.MobilePasscodeKeyboardType,
)
if (passcodeKeyboardType) {
rawStructure.nonwrapped[Services.StorageKey.MobilePasscodeKeyboardType] = passcodeKeyboardType
}
if (rawPasscodeParams) {
const passcodeParams = this.services.protocolService.createKeyParams(rawPasscodeParams)
const getPasscodeKey = async () => {
let passcodeKey: SNRootKey | undefined
await this.promptForPasscodeUntilCorrect(async (candidate: string) => {
passcodeKey = await this.services.protocolService.computeRootKey(candidate, passcodeParams)
const pwHash = keychainValue?.offline?.pw
if (pwHash) {
return passcodeKey.serverPassword === pwHash
} else {
/**
* Fallback decryption if keychain is missing for some reason. If account,
* validate by attempting to decrypt wrapped account key. Otherwise, validate
* by attempting to decrypt random item.
* */
if (wrappedAccountKey) {
const decryptedAcctKey = await this.services.protocolService.decryptSplitSingle({
usesRootKey: {
items: [new Models.EncryptedPayload(wrappedAccountKey)],
key: passcodeKey,
},
})
return !Models.isErrorDecryptingPayload(decryptedAcctKey)
} else {
const item = (
await this.services.deviceInterface.getAllRawDatabasePayloads(this.services.identifier)
)[0] as Models.EncryptedTransferPayload
if (!item) {
throw Error('Passcode only migration aborting due to missing keychain.offline.pw')
}
const decryptedPayload = await this.services.protocolService.decryptSplitSingle({
usesRootKey: {
items: [new Models.EncryptedPayload(item)],
key: passcodeKey,
},
})
return !Models.isErrorDecryptingPayload(decryptedPayload)
}
}
})
return passcodeKey as SNRootKey
}
rawStructure.nonwrapped[Services.StorageKey.MobilePasscodeTiming] = keychainValue?.offline?.timing
if (wrappedAccountKey) {
/**
* Account key is encrypted with passcode. Inside, the accountKey is located inside
* content.accountKeys. We want to unembed these values to main content, rename
* with proper property names, wrap again, and store in new rawStructure.
*/
const passcodeKey = await getPasscodeKey()
const payload = new Models.EncryptedPayload(wrappedAccountKey)
const unwrappedAccountKey = await this.services.protocolService.decryptSplitSingle<LegacyRootKeyContent>({
usesRootKey: {
items: [payload],
key: passcodeKey,
},
})
if (Models.isErrorDecryptingPayload(unwrappedAccountKey)) {
return
}
const accountKeyContent = unwrappedAccountKey.content.accountKeys as LegacyAccountKeysValue
const version =
accountKeyContent.version || rawAccountKeyParams?.version || (await this.getFallbackRootKeyVersion())
const newAccountKey = unwrappedAccountKey.copy({
content: Models.FillItemContent<LegacyRootKeyContent>({
masterKey: accountKeyContent.mk,
dataAuthenticationKey: accountKeyContent.ak,
version: version as ProtocolVersion,
keyParams: rawAccountKeyParams,
accountKeys: undefined,
}),
})
const newWrappedAccountKey = await this.services.protocolService.encryptSplitSingle({
usesRootKey: {
items: [newAccountKey],
key: passcodeKey,
},
})
rawStructure.nonwrapped[Services.StorageKey.WrappedRootKey] =
Models.CreateEncryptedLocalStorageContextPayload(newWrappedAccountKey)
if (accountKeyContent.jwt) {
/** Move the jwt to raw storage so that it can be migrated in `migrateSessionStorage` */
void this.services.deviceInterface.setRawStorageValue(LEGACY_SESSION_TOKEN_KEY, accountKeyContent.jwt)
}
await this.services.deviceInterface.clearRawKeychainValue()
} else if (!wrappedAccountKey) {
/** Passcode only, no account */
const passcodeKey = await getPasscodeKey()
const payload = new Models.DecryptedPayload({
uuid: Utils.UuidGenerator.GenerateUuid(),
content: Models.FillItemContent(rawStructure.unwrapped),
content_type: ContentType.EncryptedStorage,
...PayloadTimestampDefaults(),
})
/** Encrypt new storage.unwrapped structure with passcode */
const wrapped = await this.services.protocolService.encryptSplitSingle({
usesRootKey: {
items: [payload],
key: passcodeKey,
},
})
rawStructure.wrapped = Models.CreateEncryptedLocalStorageContextPayload(wrapped)
await this.services.deviceInterface.clearRawKeychainValue()
}
} else {
/** No passcode, potentially account. Migrate keychain property keys. */
const hasAccount = !Utils.isNullOrUndefined(keychainValue?.mk)
if (hasAccount) {
const accountVersion =
(keychainValue.version as ProtocolVersion) ||
rawAccountKeyParams?.version ||
(await this.getFallbackRootKeyVersion())
const accountKey = CreateNewRootKey({
masterKey: keychainValue.mk,
dataAuthenticationKey: keychainValue.ak,
version: accountVersion,
keyParams: rawAccountKeyParams,
})
await this.services.deviceInterface.setNamespacedKeychainValue(
accountKey.getKeychainValue(),
this.services.identifier,
)
if (keychainValue.jwt) {
/** Move the jwt to raw storage so that it can be migrated in `migrateSessionStorage` */
void this.services.deviceInterface.setRawStorageValue(LEGACY_SESSION_TOKEN_KEY, keychainValue.jwt)
}
}
}
/** Move encrypted account key into place where it is now expected */
await this.allPlatformHelperSetStorageStructure(rawStructure)
}
/**
* If we are unable to determine a root key's version, due to missing version
* parameter from key params due to 001 or 002, we need to fallback to checking
* any encrypted payload and retrieving its version.
*
* If we are unable to garner any meaningful information, we will default to 002.
*
* (Previously we attempted to discern version based on presence of keys.ak; if ak,
* then 003, otherwise 002. However, late versions of 002 also inluded an ak, so this
* method can't be used. This method also didn't account for 001 versions.)
*/
private async getFallbackRootKeyVersion() {
const anyItem = (
await this.services.deviceInterface.getAllRawDatabasePayloads(this.services.identifier)
)[0] as Models.EncryptedTransferPayload
if (!anyItem) {
return ProtocolVersion.V002
}
const payload = new Models.EncryptedPayload(anyItem)
return payload.version || ProtocolVersion.V002
}
/**
* All platforms
* Migrate all previously independently stored storage keys into new
* managed approach.
*/
private async migrateArbitraryRawStorageToManagedStorageAllPlatforms() {
const allKeyValues = await this.services.deviceInterface.getAllRawStorageKeyValues()
const legacyKeys = Utils.objectToValueArray(Services.LegacyKeys1_0_0)
const tryJsonParse = (value: string) => {
try {
return JSON.parse(value)
} catch (e) {
return value
}
}
const applicationIdentifier = this.services.identifier
for (const keyValuePair of allKeyValues) {
const key = keyValuePair.key
const value = keyValuePair.value
const isNameSpacedKey =
applicationIdentifier && applicationIdentifier.length > 0 && key.startsWith(applicationIdentifier)
if (legacyKeys.includes(key) || isNameSpacedKey) {
continue
}
if (!Utils.isNullOrUndefined(value)) {
/**
* Raw values should always have been json stringified.
* New values should always be objects/parsed.
*/
const newValue = tryJsonParse(value as string)
this.services.storageService.setValue(key, newValue)
}
}
}
/**
* All platforms
* Deletes all StorageKey and LegacyKeys1_0_0 from root raw storage.
* @access private
*/
async deleteLegacyStorageValues() {
const miscKeys = [
'mk',
'ak',
'pw',
/** v1 unused key */
'encryptionKey',
/** v1 unused key */
'authKey',
'jwt',
'ephemeral',
'cachedThemes',
]
const managedKeys = [
...Utils.objectToValueArray(Services.StorageKey),
...Utils.objectToValueArray(Services.LegacyKeys1_0_0),
...miscKeys,
]
for (const key of managedKeys) {
await this.services.deviceInterface.removeRawStorageValue(key)
}
}
/**
* Mobile
* Migrate mobile preferences
*/
private async migrateMobilePreferences() {
const lastExportDate = await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobileLastExportDate,
)
const doNotWarnUnsupportedEditors = await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobileDoNotWarnUnsupportedEditors,
)
const legacyOptionsState = (await this.services.deviceInterface.getJsonParsedRawStorageValue(
Services.LegacyKeys1_0_0.MobileOptionsState,
)) as Record<string, unknown>
let migratedOptionsState = {}
if (legacyOptionsState) {
const legacySortBy = legacyOptionsState.sortBy
migratedOptionsState = {
sortBy:
legacySortBy === 'updated_at' || legacySortBy === 'client_updated_at'
? Models.CollectionSort.UpdatedAt
: legacySortBy,
sortReverse: legacyOptionsState.sortReverse ?? false,
hideNotePreview: legacyOptionsState.hidePreviews ?? false,
hideDate: legacyOptionsState.hideDates ?? false,
hideTags: legacyOptionsState.hideTags ?? false,
}
}
const preferences = {
...migratedOptionsState,
lastExportDate: lastExportDate ?? undefined,
doNotShowAgainUnsupportedEditors: doNotWarnUnsupportedEditors ?? false,
}
await this.services.storageService.setValue(Services.StorageKey.MobilePreferences, preferences)
}
/**
* All platforms
* Migrate previously stored session string token into object
* On mobile, JWTs were previously stored in storage, inside of the user object,
* but then custom-migrated to be stored in the keychain. We must account for
* both scenarios here in case a user did not perform the custom platform migration.
* On desktop/web, JWT was stored in storage.
*/
private migrateSessionStorage() {
const USER_OBJECT_KEY = 'user'
let currentToken = this.services.storageService.getValue<string | undefined>(LEGACY_SESSION_TOKEN_KEY)
const user = this.services.storageService.getValue<{ jwt: string; server: string }>(USER_OBJECT_KEY)
if (!currentToken) {
/** Try the user object */
if (user) {
currentToken = user.jwt
}
}
if (!currentToken) {
/**
* If we detect that a user object is present, but the jwt is missing,
* we'll fill the jwt value with a junk value just so we create a session.
* When the client attempts to talk to the server, the server will reply
* with invalid token error, and the client will automatically prompt to reauthenticate.
*/
const hasAccount = !Utils.isNullOrUndefined(user)
if (hasAccount) {
currentToken = 'junk-value'
} else {
return
}
}
const sessionOrError = LegacySession.create(currentToken)
if (!sessionOrError.isFailed()) {
this.services.storageService.setValue(
Services.StorageKey.Session,
this.services.legacySessionStorageMapper.toProjection(sessionOrError.getValue()),
)
}
/** Server has to be migrated separately on mobile */
if (isEnvironmentMobile(this.services.environment)) {
if (user && user.server) {
this.services.storageService.setValue(Services.StorageKey.ServerHost, user.server)
}
}
}
/**
* All platforms
* Create new default items key from root key.
* Otherwise, when data is loaded, we won't be able to decrypt it
* without existence of an item key. This will mean that if this migration
* is run on two different platforms for the same user, they will create
* two new items keys. Which one they use to decrypt past items and encrypt
* future items doesn't really matter.
* @access private
*/
async createDefaultItemsKeyForAllPlatforms() {
const rootKey = this.services.protocolService.getRootKey()
if (rootKey) {
const rootKeyParams = await this.services.protocolService.getRootKeyParams()
/** If params are missing a version, it must be 001 */
const fallbackVersion = ProtocolVersion.V001
const payload = new Models.DecryptedPayload({
uuid: Utils.UuidGenerator.GenerateUuid(),
content_type: ContentType.ItemsKey,
content: Models.FillItemContentSpecialized<Models.ItemsKeyContentSpecialized, Models.ItemsKeyContent>({
itemsKey: rootKey.masterKey,
dataAuthenticationKey: rootKey.dataAuthenticationKey,
version: rootKeyParams?.version || fallbackVersion,
}),
dirty: true,
dirtyIndex: getIncrementedDirtyIndex(),
...PayloadTimestampDefaults(),
})
const itemsKey = Models.CreateDecryptedItemFromPayload(payload)
await this.services.itemManager.emitItemFromPayload(
itemsKey.payloadRepresentation(),
Models.PayloadEmitSource.LocalChanged,
)
}
}
}