diff --git a/public/locales/en.json b/public/locales/en.json index 33e594d..13c2ff1 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -1076,5 +1076,22 @@ "syncWallets": { "errorMessage": "Error syncing wallets", "errorDescription": "There was an error while syncing your wallets. See console for details." + }, + + "legacyMigration": { + "modalTitle": "KristWeb v1 migration", + "description": "Welcome to KristWeb v2! It looks like you have used KristWeb v1 on this domain before. Please enter your master password to migrate your wallets to the new format. You will only have to do this once.", + + "walletCount": "Detected <1>{{count, number}} wallets", + "contactCount": "Detected <1>{{count, number}} contacts", + + "masterPasswordLabel": "Master password", + "masterPasswordPlaceholder": "Master password", + + "errorPasswordRequired": "Password is required.", + "errorPasswordLength": "Must be at least 1 character.", + "errorPasswordIncorrect": "Incorrect password.", + + "buttonSubmit": "Begin migration" } } diff --git a/src/global/AppServices.tsx b/src/global/AppServices.tsx index 7cc8254..fb5da45 100644 --- a/src/global/AppServices.tsx +++ b/src/global/AppServices.tsx @@ -1,24 +1,25 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt - +import { StorageBroadcast } from "./StorageBroadcast"; +import { LegacyMigration } from "./LegacyMigration"; import { SyncWallets } from "@comp/wallets/SyncWallets"; import { ForcedAuth } from "./ForcedAuth"; import { WebsocketService } from "./ws/WebsocketService"; import { SyncMOTD } from "./ws/SyncMOTD"; import { AppHotkeys } from "./AppHotkeys"; -import { StorageBroadcast } from "./StorageBroadcast"; import { PurchaseKristHandler } from "./PurchaseKrist"; import { AdvanceTip } from "@pages/dashboard/TipsCard"; export function AppServices(): JSX.Element { return <> + + - ; diff --git a/src/global/LegacyMigration.tsx b/src/global/LegacyMigration.tsx new file mode 100644 index 0000000..553d2aa --- /dev/null +++ b/src/global/LegacyMigration.tsx @@ -0,0 +1,59 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under AGPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useEffect } from "react"; + +import { BackupFormatType } from "@pages/backup/backupFormats"; + +import Debug from "debug"; +const debug = Debug("kristweb:legacy-migration"); + +export function LegacyMigration(): JSX.Element | null { + useEffect(() => { + debug("checking legacy migration status"); + + // Check if legacy migration has already been handled + const legacyMigrated = localStorage.getItem("migrated"); + if (legacyMigrated === "2") { + debug("migration already at 2, done"); + return; + } + + // Check if there is a v1 master password in local storage + const legacySalt = localStorage.getItem("salt") || undefined; + const legacyTester = localStorage.getItem("tester") || undefined; + const hasLegacyMasterPassword = !!legacySalt && !!legacyTester; + if (!hasLegacyMasterPassword) { + debug("no legacy master password, done"); + return; + } + + // Check if there are any v1 wallets or contacts in local storage + const walletIndex = localStorage.getItem("Wallet") || undefined; + const contactIndex = localStorage.getItem("Friend") || undefined; + if (!walletIndex && !contactIndex) { + debug("no wallets or contacts, done"); + return; + } + + // Fetch all the wallets and contacts, skipping over any that are missing + const wallets: Record = Object.fromEntries((walletIndex || "").split(",") + .map(id => [`Wallet-${id}`, localStorage.getItem(`Wallet-${id}`)]) + .filter(([_, v]) => !!v)); + const contacts: Record = Object.fromEntries((contactIndex || "").split(",") + .map(id => [`Friend-${id}`, localStorage.getItem(`Friend-${id}`)]) + .filter(([_, v]) => !!v)); + debug("found %d wallets and %d contacts", Object.keys(wallets).length, Object.keys(contacts).length); + + // Construct the backup object prior to showing the modal + const backup = { + type: BackupFormatType.KRISTWEB_V1, + wallets, + friends: contacts + }; + + debug(backup); + }, []); + + return null; +} diff --git a/src/krist/wallets/masterPassword.ts b/src/krist/wallets/masterPassword.ts index c6e165e..86b624c 100644 --- a/src/krist/wallets/masterPassword.ts +++ b/src/krist/wallets/masterPassword.ts @@ -57,8 +57,8 @@ const tester = await aesGcmEncrypt(saltHex, password); // Store them in local storage - localStorage.setItem("salt", saltHex); - localStorage.setItem("tester", tester); + localStorage.setItem("salt2", saltHex); + localStorage.setItem("tester2", tester); // Dispatch the auth state changes to the Redux store store.dispatch(actions.setMasterPassword(saltHex, tester, password)); diff --git a/src/store/reducers/MasterPasswordReducer.ts b/src/store/reducers/MasterPasswordReducer.ts index 3347926..89dba46 100644 --- a/src/store/reducers/MasterPasswordReducer.ts +++ b/src/store/reducers/MasterPasswordReducer.ts @@ -26,8 +26,8 @@ export function getInitialMasterPasswordState(): State { // Salt and tester from local storage (or undefined) - const salt = localStorage.getItem("salt") || undefined; - const tester = localStorage.getItem("tester") || undefined; + const salt = localStorage.getItem("salt2") || undefined; + const tester = localStorage.getItem("tester2") || undefined; // There is a master password configured if both `salt` and `tester` exist const hasMasterPassword = !!salt && !!tester;