Skip to content

Commit 7e72632

Browse files
fix: add stronghold migration for seed backup (#8674)
* fix: add stornghold migration for seed backup * fix: error message handling * fix:add info when profile needs migration * fix: open chrome link in external browser * fix: unlocking strongholds * fix: close popup on error --------- Co-authored-by: Begoña Álvarez de la Cruz <[email protected]>
1 parent aa17bcc commit 7e72632

File tree

9 files changed

+108
-24
lines changed

9 files changed

+108
-24
lines changed

packages/desktop/components/popups/AppDeprecationPopup.svelte

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import { TextHintVariant } from 'shared/components/enums'
44
import { getEnabledNetworkFromFeatureFlags } from '@core/network'
55
import { NetworkId } from '@core/network/enums'
6+
import { openUrlInBrowser } from '@core/app'
67
78
const NETWORK_ID = getEnabledNetworkFromFeatureFlags()
89
910
function onLinkClick(): void {
10-
window.open('https://chrome.google.com/webstore/detail/iota-wallet/iidjkmdceolghepehaaddojmnjnkkija', '_blank')
11+
openUrlInBrowser('chromewebstore.google.com/detail/iota-wallet/iidjkmdceolghepehaaddojmnjnkkija')
1112
}
1213
</script>
1314

packages/desktop/components/popups/GetSeedPopup.svelte

+52-10
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111
initialiseOnboardingProfile,
1212
initialiseProfileManagerFromOnboardingProfile,
1313
resetOnboardingProfile,
14+
onboardingProfile,
1415
} from '@contexts/onboarding'
15-
import { STRONGHOLD_VERSION } from '@core/stronghold'
1616
import { api, getProfileManager } from '@core/profile-manager'
1717
import { ProfileType } from '@core/profile'
18-
import { getDefaultPersistedNetwork, NetworkId } from '@core/network'
19-
18+
import { getDefaultClientOptions, getDefaultPersistedNetwork, NetworkId } from '@core/network'
19+
import { CLIENT_ERROR_REGEXES } from '@core/error/constants'
20+
import { ClientError } from '@core/error/enums'
21+
import { restoreBackup } from '@core/profile-manager/api'
22+
import { StrongholdVersion } from '@core/stronghold/enums'
23+
import { STRONGHOLD_VERSION } from '@core/stronghold'
24+
import { showAppNotification } from '@auxiliary/notification'
2025
interface FileWithPath extends File {
2126
path?: string
2227
}
@@ -42,13 +47,19 @@
4247
setClipboard(seed)
4348
}
4449
45-
function openUnlockStrongholdPopup(): void {
50+
function openUnlockStrongholdPopup(shouldMigrateStronghold: boolean): void {
4651
openPopup({
4752
id: PopupId.UnlockStronghold,
4853
props: {
4954
returnPassword: true,
5055
restoreBackupFromStronghold: true,
51-
onSuccess: () => {
56+
shouldMigrateStronghold,
57+
onSuccess: async (password) => {
58+
if (!shouldMigrateStronghold) {
59+
updateOnboardingProfile({ strongholdPassword: password })
60+
await initialiseProfileManagerFromOnboardingProfile()
61+
}
62+
5263
openPopup({
5364
id: PopupId.GetSeedPopup,
5465
props: {
@@ -110,23 +121,54 @@
110121
111122
async function extractSeedFromStrongholdFile(): Promise<void> {
112123
await initialiseOnboardingProfile(false, true)
124+
const network = getDefaultPersistedNetwork(NetworkId.Iota)
125+
const clientOptions = getDefaultClientOptions(NetworkId.Iota)
113126
validateBackupFile(importFileName)
114127
updateOnboardingProfile({
115-
network: getDefaultPersistedNetwork(NetworkId.Iota),
128+
network,
129+
clientOptions,
116130
importFile,
117131
importFilePath,
118132
// TODO: we don't have a way to know the stronghold version of the backup file yet
119133
strongholdVersion: STRONGHOLD_VERSION,
120134
type: ProfileType.Software,
121135
})
136+
122137
await initialiseProfileManagerFromOnboardingProfile()
123-
openUnlockStrongholdPopup()
138+
139+
const _shouldMigrate = await shouldMigrate()
140+
if (_shouldMigrate) {
141+
updateOnboardingProfile({ strongholdVersion: StrongholdVersion.V2 })
142+
}
143+
openUnlockStrongholdPopup(_shouldMigrate)
124144
}
125145
126146
async function getSeedFromSecretManager(): Promise<void> {
127-
const managerId = await getProfileManager().id
128-
const secretManager = await api.getSecretManager(managerId)
129-
seed = await secretManager?.getSeed()
147+
try {
148+
const managerId = await getProfileManager().id
149+
const secretManager = await api.getSecretManager(managerId)
150+
seed = await secretManager?.getSeed()
151+
} catch (err) {
152+
showAppNotification({
153+
type: 'error',
154+
message: 'Seed backup was unsuccessful.',
155+
subMessage: 'Please ensure your Stronghold file is valid and the password is correct.',
156+
})
157+
closePopup()
158+
if (shouldResetOnboardingProfile) {
159+
resetOnboardingProfile()
160+
}
161+
console.error(err)
162+
}
163+
}
164+
165+
async function shouldMigrate(): Promise<boolean> {
166+
try {
167+
await restoreBackup(importFilePath, '', $onboardingProfile.network.protocol.bech32Hrp)
168+
} catch (err) {
169+
const isMigrationRequired = CLIENT_ERROR_REGEXES[ClientError.MigrationRequired].test(err?.error)
170+
return isMigrationRequired
171+
}
130172
}
131173
132174
if (!readFromFile) {

packages/desktop/components/popups/UnlockStrongholdPopup.svelte

+23-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
import { closePopup } from '@auxiliary/popup'
44
import { localize } from '@core/i18n'
55
import { unlockStronghold } from '@core/profile'
6-
import { restoreBackupFromStrongholdFile } from '@contexts/onboarding'
6+
import {
7+
migrateStrongholdFromOnboardingProfile,
8+
restoreBackupFromStrongholdFile,
9+
StrongholdMigrationRequiredError,
10+
} from '@contexts/onboarding'
711
import { CLIENT_ERROR_REGEXES, ClientError } from '@core/error'
812
913
export let subtitle: string = ''
1014
export let returnPassword = false
1115
export let restoreBackupFromStronghold = false
16+
export let shouldMigrateStronghold: boolean = false
1217
1318
export let onSuccess: (..._: any[]) => void = () => {}
1419
export let onCancelled: (..._: any[]) => void = () => {}
@@ -20,23 +25,39 @@
2025
async function onSubmit(): Promise<void> {
2126
try {
2227
isBusy = true
28+
if (shouldMigrateStronghold) {
29+
await migrateStrongholdFromOnboardingProfile(password)
30+
}
31+
2332
const response = restoreBackupFromStronghold
2433
? await restoreBackupFromStrongholdFile(password)
2534
: await unlockStronghold(password)
2635
closePopup()
2736
onSuccess(returnPassword ? password : response)
2837
} catch (err) {
2938
if (err.message) {
30-
error = localize(err.message)
39+
handleErrorMessage(err.message)
3140
} else if (CLIENT_ERROR_REGEXES[ClientError.InvalidStrongholdPassword].test(err?.error)) {
3241
error = localize('error.password.incorrect')
42+
} else if (CLIENT_ERROR_REGEXES[ClientError.MigrationRequired].test(err?.error)) {
43+
throw new StrongholdMigrationRequiredError()
3344
} else {
3445
error = localize(err)
3546
}
3647
} finally {
3748
isBusy = false
3849
}
3950
}
51+
function handleErrorMessage(message): void {
52+
try {
53+
const migrationError = JSON.parse(message).payload?.error
54+
if (migrationError && CLIENT_ERROR_REGEXES[ClientError.StrongholdMigration].test(migrationError)) {
55+
error = localize('error.password.incorrect')
56+
} else error = localize(migrationError ?? message)
57+
} catch {
58+
error = localize(message)
59+
}
60+
}
4061
4162
function onCancelClick(): void {
4263
closePopup()

packages/desktop/views/dashboard/Dashboard.svelte

+9-7
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
reflectLockedStronghold,
1111
} from '@core/profile'
1212
import { appRouter, dashboardRoute } from '@core/router'
13-
import { Idle } from 'shared/components'
13+
import { Idle, Link } from 'shared/components'
1414
import { stopPollingLedgerNanoStatus } from '@core/ledger'
1515
import { removeDisplayNotification, showAppNotification } from '@auxiliary/notification'
16-
import { Platform } from '@core/app'
16+
import { Platform, openUrlInBrowser } from '@core/app'
1717
import { Developer, Settings, Vesting, Collectibles, Governance, Wallet } from './'
1818
import { onDestroy, onMount } from 'svelte'
1919
import Sidebar from './Sidebar.svelte'
@@ -148,12 +148,14 @@
148148
<span class=" text-gray-800 dark:text-white max-w-lg"
149149
>Please switch to the new IOTA Wallet for continued support and updates.
150150
</span>
151-
<a
152-
href="https://chromewebstore.google.com/detail/iota-wallet/iidjkmdceolghepehaaddojmnjnkkija"
153-
target="_blank"
154-
rel="noopener noreferrer"
155-
class="text-blue-500 underline">Download here</a
151+
<Link
152+
on:click={() =>
153+
openUrlInBrowser(
154+
'https://chromewebstore.google.com/detail/iota-wallet/iidjkmdceolghepehaaddojmnjnkkija'
155+
)}
156156
>
157+
Download here
158+
</Link>
157159
</div>
158160
{/if}
159161
<TopNavigation />

packages/shared/components/modals/InitProfileActionsModal.svelte

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import { MenuItem, Modal } from 'shared/components'
1111
import { PopupId, openPopup } from '@auxiliary/popup'
1212
import { profileManager } from '@core/profile-manager/stores'
13+
import { isLatestStrongholdVersion } from '@core/app'
1314
1415
export let modal: Modal | undefined
1516
@@ -40,6 +41,14 @@
4041
}
4142
4243
async function backupSeed(): Promise<void> {
44+
if (!isLatestStrongholdVersion($activeProfile?.strongholdVersion)) {
45+
showAppNotification({
46+
type: 'error',
47+
message:
48+
'The selected profile needs migration. Please locate your stronghold file and use the drag-and-drop flow located in the Electron menu > Backup Seed.',
49+
})
50+
return
51+
}
4352
if (!$profileManager) {
4453
const profileManagerOptions = await buildProfileManagerOptionsFromProfileData($activeProfile)
4554
const { storagePath, coinType, clientOptions, secretManager: secretManagerType } = profileManagerOptions

packages/shared/lib/contexts/onboarding/actions/initialiseProfileManagerFromOnboardingProfile.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { get } from 'svelte/store'
22

33
import {
44
buildProfileManagerOptionsFromProfileData,
5+
getSecretManagerFromProfileType,
56
initialiseProfileManager,
67
profileManager,
78
} from '@core/profile-manager'
@@ -21,8 +22,9 @@ export async function initialiseProfileManagerFromOnboardingProfile(checkForExis
2122
}
2223

2324
const profileManagerOptions = await buildProfileManagerOptionsFromProfileData(get(onboardingProfile))
24-
const { storagePath, coinType, clientOptions, secretManager } = profileManagerOptions
25-
const { id } = get(onboardingProfile)
25+
const { storagePath, coinType, clientOptions } = profileManagerOptions
26+
const { id, strongholdPassword, type } = get(onboardingProfile)
27+
const secretManager = getSecretManagerFromProfileType(type, storagePath, strongholdPassword)
2628
const manager = await initialiseProfileManager(storagePath, coinType, clientOptions, secretManager, id)
2729
profileManager.set(manager)
2830
updateOnboardingProfile({ hasInitialisedProfileManager: true })

packages/shared/lib/core/error/constants/error-regex.constant.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { ClientError, IotaClientError } from '../enums'
22

33
export const CLIENT_ERROR_REGEXES = {
44
[ClientError.InvalidStrongholdPassword]: /`invalid stronghold password`/,
5+
[ClientError.StrongholdMigration]:
6+
/stronghold migration error: failed to decrypt snapshot: incorrect password or corrupt data/,
57
[ClientError.MigrationRequired]: /`unsupported snapshot version, expected \d*, found \d*, migration required`/,
68
[ClientError.NoSyncedNode]: /`No synced node available`/,
79
[ClientError.InsufficientAmount]: /`insufficient amount: found \d*, required \d*`/,

packages/shared/lib/core/error/enums/client-error.enum.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum ClientError {
22
InvalidStrongholdPassword = 'invalidStrongholdPassword',
33
MigrationRequired = 'migrationRequired',
4+
StrongholdMigration = 'strongHoldMigration',
45
NoSyncedNode = 'noSyncedNode',
56
NotEnoughFundsToClaim = 'notEnoughFundsToClaim',
67
InsufficientAmount = 'insufficientAmount',

packages/shared/lib/core/profile-manager/utils/getSecretManagerFromProfileType.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import { SecretManagerType } from '@iota/sdk/out/types'
33
import { USE_LEDGER_SIMULATOR } from '@core/ledger'
44
import { ProfileType } from '@core/profile'
55

6-
export function getSecretManagerFromProfileType(type: ProfileType, storagePath?: string): SecretManagerType {
6+
export function getSecretManagerFromProfileType(
7+
type: ProfileType,
8+
storagePath?: string,
9+
password?: string
10+
): SecretManagerType {
711
const strongholdSecretManager = {
8-
stronghold: { snapshotPath: `${storagePath}/wallet.stronghold` },
12+
stronghold: { snapshotPath: `${storagePath}/wallet.stronghold`, password },
913
}
1014
const ledgerSecretManager = {
1115
ledgerNano: USE_LEDGER_SIMULATOR,

0 commit comments

Comments
 (0)