1043 lines
48 KiB
JavaScript
1043 lines
48 KiB
JavaScript
/* eslint-disable no-undef */
|
|
import * as Factory from '../lib/factory.js'
|
|
import * as Utils from '../lib/Utils.js'
|
|
import FakeWebCrypto from '../lib/fake_web_crypto.js'
|
|
chai.use(chaiAsPromised)
|
|
const expect = chai.expect
|
|
|
|
describe('2020-01-15 mobile migration', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear()
|
|
})
|
|
|
|
afterEach(() => {
|
|
localStorage.clear()
|
|
})
|
|
|
|
it(
|
|
'2020-01-15 migration with passcode and account',
|
|
async function () {
|
|
let application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const passcode = 'bar'
|
|
/** Create old version passcode parameters */
|
|
const passcodeKey = await operator003.createRootKey(identifier, passcode)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'pc_params',
|
|
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
|
|
)
|
|
const passcodeTiming = 'immediately'
|
|
|
|
/** Create old version account parameters */
|
|
const password = 'tar'
|
|
const accountKey = await operator003.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
const customServer = 'http://server-dev.standardnotes.org'
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'user',
|
|
JSON.stringify({ email: identifier, server: customServer }),
|
|
)
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
offline: {
|
|
pw: passcodeKey.serverPassword,
|
|
timing: passcodeTiming,
|
|
},
|
|
})
|
|
/** Wrap account key with passcode key and store in storage */
|
|
const keyPayload = new DecryptedPayload({
|
|
uuid: Utils.generateUuid(),
|
|
content_type: 'SN|Mobile|EncryptedKeys',
|
|
content: {
|
|
accountKeys: {
|
|
jwt: 'foo',
|
|
mk: accountKey.masterKey,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
pw: accountKey.serverPassword,
|
|
},
|
|
},
|
|
})
|
|
const encryptedKeyParams = await operator003.generateEncryptedParametersAsync(keyPayload, passcodeKey)
|
|
const wrappedKey = new EncryptedPayload({ ...keyPayload.ejected(), ...encryptedKeyParams })
|
|
await application.deviceInterface.setRawStorageValue('encrypted_account_keys', JSON.stringify(wrappedKey))
|
|
const biometricPrefs = { enabled: true, timing: 'immediately' }
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
/** setup options */
|
|
const lastExportDate = '2020:02'
|
|
await application.deviceInterface.setRawStorageValue('LastExportDateKey', lastExportDate)
|
|
const options = JSON.stringify({
|
|
sortBy: 'userModifiedAt',
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: true,
|
|
hideDates: false,
|
|
hideTags: false,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (
|
|
prompt.validation === ChallengeValidation.None ||
|
|
prompt.validation === ChallengeValidation.LocalPasscode
|
|
) {
|
|
values.push(CreateChallengeValue(prompt, passcode))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
const values = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, values)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
|
|
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
|
|
const keyParams = await application.diskStorageService.getValue(
|
|
StorageKey.RootKeyParams,
|
|
StorageValueModes.Nonwrapped,
|
|
)
|
|
expect(typeof keyParams).to.equal('object')
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
|
|
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
|
|
expect(rootKey.serverPassword).to.not.be.ok
|
|
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
|
|
|
|
const keychainValue = await application.deviceInterface.getNamespacedKeychainValue(application.identifier)
|
|
expect(keychainValue).to.not.be.ok
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
|
|
expect(
|
|
await application.diskStorageService.getValue(NonwrappedStorageKey.MobileFirstRun, StorageValueModes.Nonwrapped),
|
|
).to.equal(false)
|
|
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.BiometricsState, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.enabled)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.timing)
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
|
|
const appId = application.identifier
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
|
|
/** Full sync completed event will not trigger due to mocked credentials,
|
|
* thus we manually need to mark any sync dependent migrations as complete. */
|
|
await application.migrationService.markMigrationsAsDone()
|
|
await Factory.safeDeinit(application)
|
|
|
|
/** Recreate application and ensure storage values are consistent */
|
|
application = Factory.createApplicationWithFakeCrypto(appId)
|
|
await application.prepareForLaunch({
|
|
receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
expect(await application.getHost()).to.equal(customServer)
|
|
const preferences = await application.diskStorageService.getValue('preferences')
|
|
expect(preferences.sortBy).to.equal('userModifiedAt')
|
|
expect(preferences.sortReverse).to.be.false
|
|
expect(preferences.hideDate).to.be.false
|
|
expect(preferences.hideTags).to.be.false
|
|
expect(preferences.hideNotePreview).to.be.true
|
|
expect(preferences.lastExportDate).to.equal(lastExportDate)
|
|
expect(preferences.doNotShowAgainUnsupportedEditors).to.be.false
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
await Factory.safeDeinit(application)
|
|
},
|
|
Factory.TwentySecondTimeout,
|
|
)
|
|
|
|
it('2020-01-15 migration with passcode only', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const passcode = 'bar'
|
|
/** Create old version passcode parameters */
|
|
const passcodeKey = await operator003.createRootKey(identifier, passcode)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'pc_params',
|
|
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
|
|
)
|
|
const passcodeTiming = 'immediately'
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
offline: {
|
|
pw: passcodeKey.serverPassword,
|
|
timing: passcodeTiming,
|
|
},
|
|
})
|
|
|
|
const biometricPrefs = { enabled: true, timing: 'immediately' }
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
const passcodeKeyboardType = 'numeric'
|
|
await application.deviceInterface.setRawStorageValue('passcodeKeyboardType', passcodeKeyboardType)
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, passcodeKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
/** setup options */
|
|
await application.deviceInterface.setRawStorageValue('DoNotShowAgainUnsupportedEditorsKey', true)
|
|
const options = JSON.stringify({
|
|
sortBy: undefined,
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: false,
|
|
hideDates: undefined,
|
|
hideTags: true,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None || prompt.validation === ChallengeValidation.LocalPasscode) {
|
|
values.push(CreateChallengeValue(prompt, passcode))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
await Factory.sleep(0)
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)
|
|
await application.launch(true)
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey.masterKey).to.equal(passcodeKey.masterKey)
|
|
expect(rootKey.dataAuthenticationKey).to.equal(passcodeKey.dataAuthenticationKey)
|
|
/** Root key is in memory with passcode only, so server password can be defined */
|
|
expect(rootKey.serverPassword).to.be.ok
|
|
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)
|
|
|
|
const keychainValue = await application.deviceInterface.getNamespacedKeychainValue(application.identifier)
|
|
expect(keychainValue).to.not.be.ok
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
expect(
|
|
await application.diskStorageService.getValue(NonwrappedStorageKey.MobileFirstRun, StorageValueModes.Nonwrapped),
|
|
).to.equal(false)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.BiometricsState, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.enabled)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.timing)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped),
|
|
).to.eql(passcodeTiming)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobilePasscodeKeyboardType, StorageValueModes.Nonwrapped),
|
|
).to.eql(passcodeKeyboardType)
|
|
const preferences = await application.diskStorageService.getValue('preferences')
|
|
expect(preferences.sortBy).to.equal(undefined)
|
|
expect(preferences.sortReverse).to.be.false
|
|
expect(preferences.hideNotePreview).to.be.false
|
|
expect(preferences.hideDate).to.be.false
|
|
expect(preferences.hideTags).to.be.true
|
|
expect(preferences.lastExportDate).to.equal(undefined)
|
|
expect(preferences.doNotShowAgainUnsupportedEditors).to.be.true
|
|
await Factory.safeDeinit(application)
|
|
})
|
|
|
|
it('2020-01-15 migration with passcode-only missing keychain', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const passcode = 'bar'
|
|
/** Create old version passcode parameters */
|
|
const passcodeKey = await operator003.createRootKey(identifier, passcode)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'pc_params',
|
|
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
|
|
)
|
|
const biometricPrefs = { enabled: true, timing: 'immediately' }
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
const passcodeKeyboardType = 'numeric'
|
|
await application.deviceInterface.setRawStorageValue('passcodeKeyboardType', passcodeKeyboardType)
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, passcodeKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
/** setup options */
|
|
await application.deviceInterface.setRawStorageValue('DoNotShowAgainUnsupportedEditorsKey', true)
|
|
const options = JSON.stringify({
|
|
sortBy: undefined,
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: false,
|
|
hideDates: undefined,
|
|
hideTags: true,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None || prompt.validation === ChallengeValidation.LocalPasscode) {
|
|
values.push(CreateChallengeValue(prompt, passcode))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
await Factory.sleep(0)
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.WrapperOnly)
|
|
await application.launch(true)
|
|
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.errorDecrypting).to.not.be.ok
|
|
|
|
/** application should not crash */
|
|
await Factory.safeDeinit(application)
|
|
})
|
|
|
|
it('2020-01-15 migration with account only', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
/** Create old version account parameters */
|
|
const password = 'tar'
|
|
const accountKey = await operator003.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: identifier }))
|
|
expect(accountKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
mk: accountKey.masterKey,
|
|
pw: accountKey.serverPassword,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
jwt: 'foo',
|
|
version: ProtocolVersion.V003,
|
|
})
|
|
const biometricPrefs = {
|
|
enabled: true,
|
|
timing: 'immediately',
|
|
}
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
/** setup options */
|
|
const lastExportDate = '2020:02'
|
|
await application.deviceInterface.setRawStorageValue('LastExportDateKey', lastExportDate)
|
|
await application.deviceInterface.setRawStorageValue('DoNotShowAgainUnsupportedEditorsKey', false)
|
|
const options = JSON.stringify({
|
|
sortBy: 'created_at',
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: true,
|
|
hideDates: false,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None) {
|
|
values.push(CreateChallengeValue(prompt, password))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
/** Runs migration */
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
|
|
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
|
|
expect(rootKey.serverPassword).to.not.be.ok
|
|
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
|
|
|
|
const keyParams = await application.diskStorageService.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped)
|
|
expect(typeof keyParams).to.equal('object')
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
expect(
|
|
await application.diskStorageService.getValue(NonwrappedStorageKey.MobileFirstRun, StorageValueModes.Nonwrapped),
|
|
).to.equal(false)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.BiometricsState, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.enabled)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.timing)
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
const preferences = await application.diskStorageService.getValue('preferences')
|
|
expect(preferences.sortBy).to.equal('created_at')
|
|
expect(preferences.sortReverse).to.be.false
|
|
expect(preferences.hideDate).to.be.false
|
|
expect(preferences.hideNotePreview).to.be.true
|
|
expect(preferences.lastExportDate).to.equal(lastExportDate)
|
|
expect(preferences.doNotShowAgainUnsupportedEditors).to.be.false
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 launching with account but missing keychain', async function () {
|
|
/**
|
|
* We expect that the keychain will attempt to be recovered
|
|
* We expect two challenges, one to recover just the keychain
|
|
* and another to recover the user session via a sign in request
|
|
*/
|
|
|
|
/** Register a real user so we can attempt to sign back into this account later */
|
|
const tempApp = await Factory.createInitAppWithFakeCrypto(Environment.Mobile, Platform.Ios)
|
|
const email = UuidGenerator.GenerateUuid()
|
|
const password = UuidGenerator.GenerateUuid()
|
|
/** Register with 003 account */
|
|
await Factory.registerOldUser({
|
|
application: tempApp,
|
|
email: email,
|
|
password: password,
|
|
version: ProtocolVersion.V003,
|
|
})
|
|
const accountKey = tempApp.protocolService.getRootKey()
|
|
await Factory.safeDeinit(tempApp)
|
|
localStorage.clear()
|
|
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
/** Create old version account parameters */
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: email }))
|
|
expect(accountKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.placeholder === SessionStrings.EmailInputPlaceholder) {
|
|
values.push(CreateChallengeValue(prompt, email))
|
|
} else if (prompt.placeholder === SessionStrings.PasswordInputPlaceholder) {
|
|
values.push(CreateChallengeValue(prompt, password))
|
|
} else {
|
|
throw Error('Unhandled prompt')
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
let totalChallenges = 0
|
|
const expectedChallenges = 2
|
|
const receiveChallenge = async (challenge) => {
|
|
totalChallenges++
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
/** Recovery migration is non-blocking, so let's block for it */
|
|
await Factory.sleep(1.0)
|
|
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey).to.be.ok
|
|
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
|
|
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
|
|
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyOnly)
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
expect(await application.getUser().email).to.equal(email)
|
|
expect(await application.apiService.getSession()).to.be.ok
|
|
expect(totalChallenges).to.equal(expectedChallenges)
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 migration with 002 account should not create 003 data', async function () {
|
|
/** There was an issue where 002 account loading new app would create new default items key
|
|
* with 003 version. Should be 002. */
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator002 = new SNProtocolOperator002(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
/** Create old version account parameters */
|
|
const password = 'tar'
|
|
const accountKey = await operator002.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: identifier }))
|
|
expect(accountKey.keyVersion).to.equal(ProtocolVersion.V002)
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
mk: accountKey.masterKey,
|
|
pw: accountKey.serverPassword,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
jwt: 'foo',
|
|
})
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator002.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None) {
|
|
values.push(CreateChallengeValue(prompt, password))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
const itemsKey = application.itemManager.getDisplayableItemsKeys()[0]
|
|
expect(itemsKey.keyVersion).to.equal(ProtocolVersion.V002)
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 migration with 001 account detect 001 version even with missing info', async function () {
|
|
/** If 001 account, and for some reason we dont have version stored, the migrations
|
|
* should determine correct version based on saved payloads */
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator001 = new SNProtocolOperator001(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
/** Create old version account parameters */
|
|
const password = 'tar'
|
|
const accountKey = await operator001.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify({
|
|
...accountKey.keyParams.getPortableValue(),
|
|
version: undefined,
|
|
}),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: identifier }))
|
|
expect(accountKey.keyVersion).to.equal(ProtocolVersion.V001)
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
mk: accountKey.masterKey,
|
|
pw: accountKey.serverPassword,
|
|
jwt: 'foo',
|
|
})
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator001.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None) {
|
|
values.push(CreateChallengeValue(prompt, password))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
const itemsKey = application.itemManager.getDisplayableItemsKeys()[0]
|
|
expect(itemsKey.keyVersion).to.equal(ProtocolVersion.V001)
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 successfully creates session if jwt is stored in keychain', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const password = 'tar'
|
|
const accountKey = await operator003.createRootKey(identifier, password)
|
|
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: identifier }))
|
|
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
mk: accountKey.masterKey,
|
|
pw: accountKey.serverPassword,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
jwt: 'foo',
|
|
version: ProtocolVersion.V003,
|
|
})
|
|
|
|
await application.prepareForLaunch({ receiveChallenge: () => {} })
|
|
await application.launch(true)
|
|
|
|
expect(application.apiService.getSession()).to.be.ok
|
|
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 successfully creates session if jwt is stored in storage', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const password = 'tar'
|
|
const accountKey = await operator003.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
await application.deviceInterface.setRawStorageValue('user', JSON.stringify({ email: identifier, jwt: 'foo' }))
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
mk: accountKey.masterKey,
|
|
pw: accountKey.serverPassword,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
version: ProtocolVersion.V003,
|
|
})
|
|
|
|
await application.prepareForLaunch({ receiveChallenge: () => {} })
|
|
await application.launch(true)
|
|
|
|
expect(application.apiService.getSession()).to.be.ok
|
|
|
|
await Factory.safeDeinit(application)
|
|
}).timeout(10000)
|
|
|
|
it('2020-01-15 migration with no account and no passcode', async function () {
|
|
const application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const biometricPrefs = {
|
|
enabled: true,
|
|
timing: 'immediately',
|
|
}
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
await application.deviceInterface.saveRawDatabasePayload(notePayload.ejected(), application.identifier)
|
|
/** setup options */
|
|
await application.deviceInterface.setRawStorageValue('DoNotShowAgainUnsupportedEditorsKey', true)
|
|
const options = JSON.stringify({
|
|
sortBy: 'created_at',
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: true,
|
|
hideDates: false,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (prompt.validation === ChallengeValidation.None || prompt.validation === ChallengeValidation.LocalPasscode) {
|
|
values.push(CreateChallengeValue(prompt, passcode))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
application.addChallengeObserver(challenge, {
|
|
onInvalidValue: (value) => {
|
|
const values = promptValueReply([value.prompt])
|
|
application.submitValuesForChallenge(challenge, values)
|
|
},
|
|
})
|
|
const initialValues = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, initialValues)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge: receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey).to.not.be.ok
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyNone)
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
expect(
|
|
await application.diskStorageService.getValue(NonwrappedStorageKey.MobileFirstRun, StorageValueModes.Nonwrapped),
|
|
).to.equal(false)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.BiometricsState, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.enabled)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.timing)
|
|
const preferences = await application.diskStorageService.getValue('preferences')
|
|
expect(preferences.sortBy).to.equal('created_at')
|
|
expect(preferences.sortReverse).to.be.false
|
|
expect(preferences.hideDate).to.be.false
|
|
expect(preferences.hideNotePreview).to.be.true
|
|
expect(preferences.lastExportDate).to.equal(undefined)
|
|
expect(preferences.doNotShowAgainUnsupportedEditors).to.be.true
|
|
await Factory.safeDeinit(application)
|
|
})
|
|
|
|
it(
|
|
'2020-01-15 migration from mobile version 3.0.16',
|
|
async function () {
|
|
/**
|
|
* In version 3.0.16, encrypted account keys were stored in keychain, not storage.
|
|
* This was migrated in version 3.0.17, but we want to be sure we can go from 3.0.16
|
|
* to current state directly.
|
|
*/
|
|
let application = await Factory.createAppWithRandNamespace(Environment.Mobile, Platform.Ios)
|
|
/** Create legacy migrations value so that base migration detects old app */
|
|
await application.deviceInterface.setRawStorageValue('migrations', JSON.stringify(['anything']))
|
|
const operator003 = new SNProtocolOperator003(new FakeWebCrypto())
|
|
const identifier = 'foo'
|
|
const passcode = 'bar'
|
|
/** Create old version passcode parameters */
|
|
const passcodeKey = await operator003.createRootKey(identifier, passcode)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'pc_params',
|
|
JSON.stringify(passcodeKey.keyParams.getPortableValue()),
|
|
)
|
|
const passcodeTiming = 'immediately'
|
|
|
|
/** Create old version account parameters */
|
|
const password = 'tar'
|
|
const accountKey = await operator003.createRootKey(identifier, password)
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'auth_params',
|
|
JSON.stringify(accountKey.keyParams.getPortableValue()),
|
|
)
|
|
const customServer = 'http://server-dev.standardnotes.org'
|
|
await application.deviceInterface.setRawStorageValue(
|
|
'user',
|
|
JSON.stringify({ email: identifier, server: customServer }),
|
|
)
|
|
/** Wrap account key with passcode key and store in storage */
|
|
const keyPayload = new DecryptedPayload({
|
|
uuid: Utils.generateUuid(),
|
|
content_type: 'SN|Mobile|EncryptedKeys',
|
|
content: {
|
|
accountKeys: {
|
|
jwt: 'foo',
|
|
mk: accountKey.masterKey,
|
|
ak: accountKey.dataAuthenticationKey,
|
|
pw: accountKey.serverPassword,
|
|
},
|
|
},
|
|
})
|
|
const encryptedKeyParams = await operator003.generateEncryptedParametersAsync(keyPayload, passcodeKey)
|
|
const wrappedKey = new EncryptedPayload({ ...keyPayload, ...encryptedKeyParams })
|
|
await application.deviceInterface.setLegacyRawKeychainValue({
|
|
encryptedAccountKeys: wrappedKey,
|
|
offline: {
|
|
pw: passcodeKey.serverPassword,
|
|
timing: passcodeTiming,
|
|
},
|
|
})
|
|
const biometricPrefs = { enabled: true, timing: 'immediately' }
|
|
/** Create legacy storage. Storage in mobile was never wrapped. */
|
|
await application.deviceInterface.setRawStorageValue('biometrics_prefs', JSON.stringify(biometricPrefs))
|
|
await application.deviceInterface.setRawStorageValue(NonwrappedStorageKey.MobileFirstRun, false)
|
|
/** Create encrypted item and store it in db */
|
|
const notePayload = Factory.createNotePayload()
|
|
const noteEncryptionParams = await operator003.generateEncryptedParametersAsync(notePayload, accountKey)
|
|
const noteEncryptedPayload = new EncryptedPayload({ ...notePayload, ...noteEncryptionParams })
|
|
await application.deviceInterface.saveRawDatabasePayload(noteEncryptedPayload, application.identifier)
|
|
/** setup options */
|
|
const lastExportDate = '2020:02'
|
|
await application.deviceInterface.setRawStorageValue('LastExportDateKey', lastExportDate)
|
|
const options = JSON.stringify({
|
|
sortBy: 'userModifiedAt',
|
|
sortReverse: undefined,
|
|
selectedTagIds: [],
|
|
hidePreviews: true,
|
|
hideDates: false,
|
|
hideTags: false,
|
|
})
|
|
await application.deviceInterface.setRawStorageValue('options', options)
|
|
/** Run migration */
|
|
const promptValueReply = (prompts) => {
|
|
const values = []
|
|
for (const prompt of prompts) {
|
|
if (
|
|
prompt.validation === ChallengeValidation.None ||
|
|
prompt.validation === ChallengeValidation.LocalPasscode
|
|
) {
|
|
values.push(CreateChallengeValue(prompt, passcode))
|
|
}
|
|
if (prompt.validation === ChallengeValidation.Biometric) {
|
|
values.push(CreateChallengeValue(prompt, true))
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
const receiveChallenge = async (challenge) => {
|
|
const values = promptValueReply(challenge.prompts)
|
|
application.submitValuesForChallenge(challenge, values)
|
|
}
|
|
await application.prepareForLaunch({
|
|
receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
|
|
|
|
/** Should be decrypted */
|
|
const storageMode = application.diskStorageService.domainKeyForMode(StorageValueModes.Default)
|
|
const valueStore = application.diskStorageService.values[storageMode]
|
|
expect(valueStore.content_type).to.not.be.ok
|
|
|
|
const keyParams = await application.diskStorageService.getValue(
|
|
StorageKey.RootKeyParams,
|
|
StorageValueModes.Nonwrapped,
|
|
)
|
|
expect(typeof keyParams).to.equal('object')
|
|
const rootKey = await application.protocolService.getRootKey()
|
|
expect(rootKey.masterKey).to.equal(accountKey.masterKey)
|
|
expect(rootKey.dataAuthenticationKey).to.equal(accountKey.dataAuthenticationKey)
|
|
expect(rootKey.serverPassword).to.not.be.ok
|
|
expect(rootKey.keyVersion).to.equal(ProtocolVersion.V003)
|
|
expect(application.protocolService.rootKeyEncryption.keyMode).to.equal(KeyMode.RootKeyPlusWrapper)
|
|
|
|
const keychainValue = await application.deviceInterface.getNamespacedKeychainValue(application.identifier)
|
|
expect(keychainValue).to.not.be.ok
|
|
|
|
/** Expect note is decrypted */
|
|
expect(application.itemManager.getDisplayableNotes().length).to.equal(1)
|
|
const retrievedNote = application.itemManager.getDisplayableNotes()[0]
|
|
expect(retrievedNote.uuid).to.equal(notePayload.uuid)
|
|
expect(retrievedNote.content.text).to.equal(notePayload.content.text)
|
|
|
|
expect(
|
|
await application.diskStorageService.getValue(NonwrappedStorageKey.MobileFirstRun, StorageValueModes.Nonwrapped),
|
|
).to.equal(false)
|
|
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.BiometricsState, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.enabled)
|
|
expect(
|
|
await application.diskStorageService.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped),
|
|
).to.equal(biometricPrefs.timing)
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
|
|
const appId = application.identifier
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
/** Full sync completed event will not trigger due to mocked credentials,
|
|
* thus we manually need to mark any sync dependent migrations as complete. */
|
|
await application.migrationService.markMigrationsAsDone()
|
|
await Factory.safeDeinit(application)
|
|
|
|
/** Recreate application and ensure storage values are consistent */
|
|
application = Factory.createApplicationWithFakeCrypto(appId)
|
|
await application.prepareForLaunch({
|
|
receiveChallenge,
|
|
})
|
|
await application.launch(true)
|
|
expect(await application.getUser().email).to.equal(identifier)
|
|
expect(await application.getHost()).to.equal(customServer)
|
|
const preferences = await application.diskStorageService.getValue('preferences')
|
|
expect(preferences.sortBy).to.equal('userModifiedAt')
|
|
expect(preferences.sortReverse).to.be.false
|
|
expect(preferences.hideDate).to.be.false
|
|
expect(preferences.hideTags).to.be.false
|
|
expect(preferences.hideNotePreview).to.be.true
|
|
expect(preferences.lastExportDate).to.equal(lastExportDate)
|
|
expect(preferences.doNotShowAgainUnsupportedEditors).to.be.false
|
|
console.warn('Expecting exception due to deiniting application while trying to renew session')
|
|
await Factory.safeDeinit(application)
|
|
},
|
|
Factory.TwentySecondTimeout,
|
|
)
|
|
})
|