import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; import { Migrator } from "../migrator"; type ExpectedAccountState = { settings?: { pinKeyEncryptedUserKey?: string; // EncryptedString protectedPin?: string; // EncryptedString pinProtected?: { encrypted?: string; }; }; }; export const PIN_STATE: StateDefinitionLike = { name: "pinUnlock" }; export const PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT: KeyDefinitionLike = { stateDefinition: PIN_STATE, key: "pinKeyEncryptedUserKeyPersistent", }; export const USER_KEY_ENCRYPTED_PIN: KeyDefinitionLike = { stateDefinition: PIN_STATE, key: "userKeyEncryptedPin", }; export const OLD_PIN_KEY_ENCRYPTED_MASTER_KEY: KeyDefinitionLike = { stateDefinition: PIN_STATE, key: "oldPinKeyEncryptedMasterKey", }; export class PinStateMigrator extends Migrator<60, 61> { async migrate(helper: MigrationHelper): Promise { const legacyAccounts = await helper.getAccounts(); let updatedAccount = false; async function migrateAccount(userId: string, account: ExpectedAccountState) { // Migrate pinKeyEncryptedUserKey (to `pinKeyEncryptedUserKeyPersistent`) if (account?.settings?.pinKeyEncryptedUserKey != null) { await helper.setToUser( userId, PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, account.settings.pinKeyEncryptedUserKey, ); delete account.settings.pinKeyEncryptedUserKey; updatedAccount = true; } // Migrate protectedPin (to `userKeyEncryptedPin`) if (account?.settings?.protectedPin != null) { await helper.setToUser(userId, USER_KEY_ENCRYPTED_PIN, account.settings.protectedPin); delete account.settings.protectedPin; updatedAccount = true; } // Migrate pinProtected (to `oldPinKeyEncryptedMasterKey`) if (account?.settings?.pinProtected?.encrypted != null) { await helper.setToUser( userId, OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, account.settings.pinProtected.encrypted, ); delete account.settings.pinProtected; updatedAccount = true; } if (updatedAccount) { await helper.set(userId, account); } } await Promise.all([ ...legacyAccounts.map(({ userId, account }) => migrateAccount(userId, account)), ]); } async rollback(helper: MigrationHelper): Promise { const accounts = await helper.getAccounts(); async function rollbackAccount(userId: string, account: ExpectedAccountState) { let updatedAccount = false; const accountPinKeyEncryptedUserKeyPersistent = await helper.getFromUser( userId, PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, ); const accountUserKeyEncryptedPin = await helper.getFromUser( userId, USER_KEY_ENCRYPTED_PIN, ); const accountOldPinKeyEncryptedMasterKey = await helper.getFromUser( userId, OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, ); if (!account) { account = {}; } if (accountPinKeyEncryptedUserKeyPersistent != null) { account.settings.pinKeyEncryptedUserKey = accountPinKeyEncryptedUserKeyPersistent; await helper.setToUser(userId, PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null); updatedAccount = true; } if (accountUserKeyEncryptedPin != null) { account.settings.protectedPin = accountUserKeyEncryptedPin; await helper.setToUser(userId, USER_KEY_ENCRYPTED_PIN, null); updatedAccount = true; } if (accountOldPinKeyEncryptedMasterKey != null) { account.settings = Object.assign(account.settings ?? {}, { pinProtected: { encrypted: accountOldPinKeyEncryptedMasterKey, }, }); await helper.setToUser(userId, OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, null); updatedAccount = true; } if (updatedAccount) { await helper.set(userId, account); } } await Promise.all(accounts.map(({ userId, account }) => rollbackAccount(userId, account))); } }