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;