diff --git a/src/app/organisms/settings/KeyBackup.jsx b/src/app/organisms/settings/KeyBackup.jsx index 083589e1..50080a6e 100644 --- a/src/app/organisms/settings/KeyBackup.jsx +++ b/src/app/organisms/settings/KeyBackup.jsx @@ -6,8 +6,7 @@ import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import { openReusableDialog } from '../../../client/action/navigation'; -import { getDefaultSSKey } from '../../../util/matrixUtil'; -import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys'; +import { deletePrivateKey } from '../../../client/state/secretStorageKeys'; import Text from '../../atoms/text/Text'; import Button from '../../atoms/button/Button'; @@ -16,7 +15,7 @@ import Spinner from '../../atoms/spinner/Spinner'; import InfoCard from '../../atoms/card/InfoCard'; import SettingTile from '../../molecules/setting-tile/SettingTile'; -import SecretStorageAccess from './SecretStorageAccess'; +import { accessSecretStorage } from './SecretStorageAccess'; import InfoIC from '../../../../public/res/ic/outlined/info.svg'; import BinIC from '../../../../public/res/ic/outlined/bin.svg'; @@ -44,6 +43,7 @@ function CreateKeyBackupDialog({ keyData }) { if (!mountStore.getItem()) return; setDone(true); } catch (e) { + deletePrivateKey(keyData.keyId); await mx.deleteKeyBackupVersion(info.version); if (!mountStore.getItem()) return; setDone(null); @@ -83,24 +83,42 @@ CreateKeyBackupDialog.propTypes = { }; function RestoreKeyBackupDialog({ keyData, backupInfo }) { - const [done, setDone] = useState(false); + const [status, setStatus] = useState(false); const mx = initMatrix.matrixClient; const mountStore = useStore(); const restoreBackup = async () => { - setDone(false); + setStatus(false); + + let meBreath = true; + const progressCallback = (progress) => { + if (!progress.successes) return; + if (meBreath === false) return; + meBreath = false; + setTimeout(() => { + meBreath = true; + }, 200); + + setStatus({ message: `Restoring backup keys... (${progress.successes}/${progress.total})` }); + }; try { - await mx.restoreKeyBackupWithSecretStorage( + const info = await mx.restoreKeyBackupWithSecretStorage( backupInfo, undefined, undefined, + { progressCallback }, ); if (!mountStore.getItem()) return; - setDone(true); + setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` }); } catch (e) { if (!mountStore.getItem()) return; - setDone(null); + if (e.errcode === 'RESTORE_BACKUP_ERROR_BAD_KEY') { + deletePrivateKey(keyData.keyId); + setStatus({ error: 'Failed to restore backup. Key is invalid', errorCode: 'BAD_KEY' }); + } else { + setStatus({ error: 'Failed to restore backup.', errCode: 'UNKNOWN' }); + } } }; @@ -111,21 +129,21 @@ function RestoreKeyBackupDialog({ keyData, backupInfo }) { return (
- {done === false && ( + {(status === false || status.message) && (
- Restoring backup... + {status.message ?? 'Restoring backup keys...'}
)} - {done === true && ( + {status.done && ( <> {twemojify('✅')} - Successfully restored backup + {status.done} )} - {done === null && ( + {status.error && ( <> - Failed to restore backup + {status.error} )} @@ -201,42 +219,25 @@ function KeyBackup() { }; }, []); - const accessSecretStorage = (title, onComplete) => { - const defaultSSKey = getDefaultSSKey(); - if (hasPrivateKey(defaultSSKey)) { - onComplete({ decodedKey: getPrivateKey(defaultSSKey) }); - return; - } - const handleComplete = (keyData) => { - storePrivateKey(keyData.keyId, keyData.decodedKey); - onComplete(keyData); - }; + const openCreateKeyBackup = async () => { + const keyData = await accessSecretStorage('Create Key Backup'); + if (keyData === null) return; openReusableDialog( - {title}, - () => , + Create Key Backup, + () => , + () => fetchKeyBackupVersion(), ); }; - const openCreateKeyBackup = () => { - const createKeyBackup = (keyData) => { - openReusableDialog( - Create Key Backup, - () => , - () => fetchKeyBackupVersion(), - ); - }; - accessSecretStorage('Create Key Backup', createKeyBackup); - }; + const openRestoreKeyBackup = async () => { + const keyData = await accessSecretStorage('Restore Key Backup'); + if (keyData === null) return; - const openRestoreKeyBackup = () => { - const restoreKeyBackup = (keyData) => { - openReusableDialog( - Restore Key Backup, - () => , - ); - }; - accessSecretStorage('Restore Key Backup', restoreKeyBackup); + openReusableDialog( + Restore Key Backup, + () => , + ); }; const openDeleteKeyBackup = () => openReusableDialog( diff --git a/src/app/organisms/settings/SecretStorageAccess.jsx b/src/app/organisms/settings/SecretStorageAccess.jsx index 6756c4a8..a381cc74 100644 --- a/src/app/organisms/settings/SecretStorageAccess.jsx +++ b/src/app/organisms/settings/SecretStorageAccess.jsx @@ -4,7 +4,9 @@ import './SecretStorageAccess.scss'; import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase'; import initMatrix from '../../../client/initMatrix'; +import { openReusableDialog } from '../../../client/action/navigation'; import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil'; +import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys'; import Text from '../../atoms/text/Text'; import Button from '../../atoms/button/Button'; @@ -30,10 +32,10 @@ function SecretStorageAccess({ onComplete }) { setProcess(true); try { const { salt, iterations } = sSKeyInfo.passphrase; - const decodedKey = key + const privateKey = key ? mx.keyBackupKeyFromRecoveryKey(key) : await deriveKey(phrase, salt, iterations); - const isCorrect = await mx.checkSecretStorageKey(decodedKey, sSKeyInfo); + const isCorrect = await mx.checkSecretStorageKey(privateKey, sSKeyInfo); if (!mountStore.getItem()) return; if (!isCorrect) { @@ -45,7 +47,7 @@ function SecretStorageAccess({ onComplete }) { keyId: sSKeyId, key, phrase, - decodedKey, + privateKey, }); } catch (e) { if (!mountStore.getItem()) return; @@ -95,4 +97,30 @@ SecretStorageAccess.propTypes = { onComplete: PropTypes.func.isRequired, }; +/** + * @param {string} title Title of secret storage access dialog + * @returns {Promise} resolve to keyData or null + */ +export const accessSecretStorage = (title) => new Promise((resolve) => { + let isCompleted = false; + const defaultSSKey = getDefaultSSKey(); + if (hasPrivateKey(defaultSSKey)) { + resolve({ keyId: defaultSSKey, privateKey: getPrivateKey(defaultSSKey) }); + return; + } + const handleComplete = (keyData) => { + isCompleted = true; + storePrivateKey(keyData.keyId, keyData.privateKey); + resolve(keyData); + }; + + openReusableDialog( + {title}, + () => , + () => { + if (!isCompleted) resolve(null); + }, + ); +}); + export default SecretStorageAccess;