Show progress when restoring key backup
This commit is contained in:
parent
848cbb1066
commit
89f69ed5b4
2 changed files with 76 additions and 47 deletions
|
@ -6,8 +6,7 @@ import { twemojify } from '../../../util/twemojify';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
import { openReusableDialog } from '../../../client/action/navigation';
|
import { openReusableDialog } from '../../../client/action/navigation';
|
||||||
import { getDefaultSSKey } from '../../../util/matrixUtil';
|
import { deletePrivateKey } from '../../../client/state/secretStorageKeys';
|
||||||
import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys';
|
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import Button from '../../atoms/button/Button';
|
import Button from '../../atoms/button/Button';
|
||||||
|
@ -16,7 +15,7 @@ import Spinner from '../../atoms/spinner/Spinner';
|
||||||
import InfoCard from '../../atoms/card/InfoCard';
|
import InfoCard from '../../atoms/card/InfoCard';
|
||||||
import SettingTile from '../../molecules/setting-tile/SettingTile';
|
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 InfoIC from '../../../../public/res/ic/outlined/info.svg';
|
||||||
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||||
|
@ -44,6 +43,7 @@ function CreateKeyBackupDialog({ keyData }) {
|
||||||
if (!mountStore.getItem()) return;
|
if (!mountStore.getItem()) return;
|
||||||
setDone(true);
|
setDone(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
deletePrivateKey(keyData.keyId);
|
||||||
await mx.deleteKeyBackupVersion(info.version);
|
await mx.deleteKeyBackupVersion(info.version);
|
||||||
if (!mountStore.getItem()) return;
|
if (!mountStore.getItem()) return;
|
||||||
setDone(null);
|
setDone(null);
|
||||||
|
@ -83,24 +83,42 @@ CreateKeyBackupDialog.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function RestoreKeyBackupDialog({ keyData, backupInfo }) {
|
function RestoreKeyBackupDialog({ keyData, backupInfo }) {
|
||||||
const [done, setDone] = useState(false);
|
const [status, setStatus] = useState(false);
|
||||||
const mx = initMatrix.matrixClient;
|
const mx = initMatrix.matrixClient;
|
||||||
const mountStore = useStore();
|
const mountStore = useStore();
|
||||||
|
|
||||||
const restoreBackup = async () => {
|
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 {
|
try {
|
||||||
await mx.restoreKeyBackupWithSecretStorage(
|
const info = await mx.restoreKeyBackupWithSecretStorage(
|
||||||
backupInfo,
|
backupInfo,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
{ progressCallback },
|
||||||
);
|
);
|
||||||
if (!mountStore.getItem()) return;
|
if (!mountStore.getItem()) return;
|
||||||
setDone(true);
|
setStatus({ done: `Successfully restored backup keys (${info.imported}/${info.total}).` });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mountStore.getItem()) return;
|
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 (
|
return (
|
||||||
<div className="key-backup__restore">
|
<div className="key-backup__restore">
|
||||||
{done === false && (
|
{(status === false || status.message) && (
|
||||||
<div>
|
<div>
|
||||||
<Spinner size="small" />
|
<Spinner size="small" />
|
||||||
<Text>Restoring backup...</Text>
|
<Text>{status.message ?? 'Restoring backup keys...'}</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{done === true && (
|
{status.done && (
|
||||||
<>
|
<>
|
||||||
<Text variant="h1">{twemojify('✅')}</Text>
|
<Text variant="h1">{twemojify('✅')}</Text>
|
||||||
<Text>Successfully restored backup</Text>
|
<Text>{status.done}</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{done === null && (
|
{status.error && (
|
||||||
<>
|
<>
|
||||||
<Text>Failed to restore backup</Text>
|
<Text>{status.error}</Text>
|
||||||
<Button onClick={restoreBackup}>Retry</Button>
|
<Button onClick={restoreBackup}>Retry</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -201,42 +219,25 @@ function KeyBackup() {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const accessSecretStorage = (title, onComplete) => {
|
const openCreateKeyBackup = async () => {
|
||||||
const defaultSSKey = getDefaultSSKey();
|
const keyData = await accessSecretStorage('Create Key Backup');
|
||||||
if (hasPrivateKey(defaultSSKey)) {
|
if (keyData === null) return;
|
||||||
onComplete({ decodedKey: getPrivateKey(defaultSSKey) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const handleComplete = (keyData) => {
|
|
||||||
storePrivateKey(keyData.keyId, keyData.decodedKey);
|
|
||||||
onComplete(keyData);
|
|
||||||
};
|
|
||||||
|
|
||||||
openReusableDialog(
|
openReusableDialog(
|
||||||
<Text variant="s1" weight="medium">{title}</Text>,
|
<Text variant="s1" weight="medium">Create Key Backup</Text>,
|
||||||
() => <SecretStorageAccess onComplete={handleComplete} />,
|
() => <CreateKeyBackupDialog keyData={keyData} />,
|
||||||
|
() => fetchKeyBackupVersion(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openCreateKeyBackup = () => {
|
const openRestoreKeyBackup = async () => {
|
||||||
const createKeyBackup = (keyData) => {
|
const keyData = await accessSecretStorage('Restore Key Backup');
|
||||||
openReusableDialog(
|
if (keyData === null) return;
|
||||||
<Text variant="s1" weight="medium">Create Key Backup</Text>,
|
|
||||||
() => <CreateKeyBackupDialog keyData={keyData} />,
|
|
||||||
() => fetchKeyBackupVersion(),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
accessSecretStorage('Create Key Backup', createKeyBackup);
|
|
||||||
};
|
|
||||||
|
|
||||||
const openRestoreKeyBackup = () => {
|
openReusableDialog(
|
||||||
const restoreKeyBackup = (keyData) => {
|
<Text variant="s1" weight="medium">Restore Key Backup</Text>,
|
||||||
openReusableDialog(
|
() => <RestoreKeyBackupDialog keyData={keyData} backupInfo={keyBackup} />,
|
||||||
<Text variant="s1" weight="medium">Restore Key Backup</Text>,
|
);
|
||||||
() => <RestoreKeyBackupDialog keyData={keyData} backupInfo={keyBackup} />,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
accessSecretStorage('Restore Key Backup', restoreKeyBackup);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDeleteKeyBackup = () => openReusableDialog(
|
const openDeleteKeyBackup = () => openReusableDialog(
|
||||||
|
|
|
@ -4,7 +4,9 @@ import './SecretStorageAccess.scss';
|
||||||
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
|
import { deriveKey } from 'matrix-js-sdk/lib/crypto/key_passphrase';
|
||||||
|
|
||||||
import initMatrix from '../../../client/initMatrix';
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
import { openReusableDialog } from '../../../client/action/navigation';
|
||||||
import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil';
|
import { getDefaultSSKey, getSSKeyInfo } from '../../../util/matrixUtil';
|
||||||
|
import { storePrivateKey, hasPrivateKey, getPrivateKey } from '../../../client/state/secretStorageKeys';
|
||||||
|
|
||||||
import Text from '../../atoms/text/Text';
|
import Text from '../../atoms/text/Text';
|
||||||
import Button from '../../atoms/button/Button';
|
import Button from '../../atoms/button/Button';
|
||||||
|
@ -30,10 +32,10 @@ function SecretStorageAccess({ onComplete }) {
|
||||||
setProcess(true);
|
setProcess(true);
|
||||||
try {
|
try {
|
||||||
const { salt, iterations } = sSKeyInfo.passphrase;
|
const { salt, iterations } = sSKeyInfo.passphrase;
|
||||||
const decodedKey = key
|
const privateKey = key
|
||||||
? mx.keyBackupKeyFromRecoveryKey(key)
|
? mx.keyBackupKeyFromRecoveryKey(key)
|
||||||
: await deriveKey(phrase, salt, iterations);
|
: await deriveKey(phrase, salt, iterations);
|
||||||
const isCorrect = await mx.checkSecretStorageKey(decodedKey, sSKeyInfo);
|
const isCorrect = await mx.checkSecretStorageKey(privateKey, sSKeyInfo);
|
||||||
|
|
||||||
if (!mountStore.getItem()) return;
|
if (!mountStore.getItem()) return;
|
||||||
if (!isCorrect) {
|
if (!isCorrect) {
|
||||||
|
@ -45,7 +47,7 @@ function SecretStorageAccess({ onComplete }) {
|
||||||
keyId: sSKeyId,
|
keyId: sSKeyId,
|
||||||
key,
|
key,
|
||||||
phrase,
|
phrase,
|
||||||
decodedKey,
|
privateKey,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mountStore.getItem()) return;
|
if (!mountStore.getItem()) return;
|
||||||
|
@ -95,4 +97,30 @@ SecretStorageAccess.propTypes = {
|
||||||
onComplete: PropTypes.func.isRequired,
|
onComplete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} title Title of secret storage access dialog
|
||||||
|
* @returns {Promise<keyData | null>} 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(
|
||||||
|
<Text variant="s1" weight="medium">{title}</Text>,
|
||||||
|
() => <SecretStorageAccess onComplete={handleComplete} />,
|
||||||
|
() => {
|
||||||
|
if (!isCompleted) resolve(null);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default SecretStorageAccess;
|
export default SecretStorageAccess;
|
||||||
|
|
Loading…
Reference in a new issue