import React, { FormEventHandler, useCallback } from 'react'; import { Box, Text, Button, Spinner, color } from 'folds'; import { decodeRecoveryKey, deriveRecoveryKeyFromPassphrase } from 'matrix-js-sdk/lib/crypto-api'; import { PasswordInput } from './password-input'; import { SecretStorageKeyContent, SecretStoragePassphraseContent, } from '../../types/matrix/accountData'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { useAlive } from '../hooks/useAlive'; type SecretStorageRecoveryPassphraseProps = { processing?: boolean; keyContent: SecretStorageKeyContent; passphraseContent: SecretStoragePassphraseContent; onDecodedRecoveryKey: (recoveryKey: Uint8Array) => void; }; export function SecretStorageRecoveryPassphrase({ processing, keyContent, passphraseContent, onDecodedRecoveryKey, }: SecretStorageRecoveryPassphraseProps) { const mx = useMatrixClient(); const alive = useAlive(); const [driveKeyState, submitPassphrase] = useAsyncCallback< Uint8Array, Error, Parameters >( useCallback( async (passphrase, salt, iterations, bits) => { const decodedRecoveryKey = await deriveRecoveryKeyFromPassphrase( passphrase, salt, iterations, bits ); const match = await mx.secretStorage.checkKey(decodedRecoveryKey, keyContent as any); if (!match) { throw new Error('Invalid recovery passphrase.'); } return decodedRecoveryKey; }, [mx, keyContent] ) ); const drivingKey = driveKeyState.status === AsyncStatus.Loading; const loading = drivingKey || processing; const handleSubmit: FormEventHandler = (evt) => { if (loading) return; evt.preventDefault(); const target = evt.target as HTMLFormElement | undefined; const recoveryPassphraseInput = target?.recoveryPassphraseInput as HTMLInputElement | undefined; if (!recoveryPassphraseInput) return; const recoveryPassphrase = recoveryPassphraseInput.value.trim(); if (!recoveryPassphrase) return; const { salt, iterations, bits } = passphraseContent; submitPassphrase(recoveryPassphrase, salt, iterations, bits).then((decodedRecoveryKey) => { if (alive()) { recoveryPassphraseInput.value = ''; onDecodedRecoveryKey(decodedRecoveryKey); } }); }; return ( Recovery Passphrase {driveKeyState.status === AsyncStatus.Error && ( {driveKeyState.error.message} )} ); } type SecretStorageRecoveryKeyProps = { processing?: boolean; keyContent: SecretStorageKeyContent; onDecodedRecoveryKey: (recoveryKey: Uint8Array) => void; }; export function SecretStorageRecoveryKey({ processing, keyContent, onDecodedRecoveryKey, }: SecretStorageRecoveryKeyProps) { const mx = useMatrixClient(); const alive = useAlive(); const [driveKeyState, submitRecoveryKey] = useAsyncCallback( useCallback( async (recoveryKey) => { const decodedRecoveryKey = decodeRecoveryKey(recoveryKey); const match = await mx.secretStorage.checkKey(decodedRecoveryKey, keyContent as any); if (!match) { throw new Error('Invalid recovery key.'); } return decodedRecoveryKey; }, [mx, keyContent] ) ); const drivingKey = driveKeyState.status === AsyncStatus.Loading; const loading = drivingKey || processing; const handleSubmit: FormEventHandler = (evt) => { evt.preventDefault(); const target = evt.target as HTMLFormElement | undefined; const recoveryKeyInput = target?.recoveryKeyInput as HTMLInputElement | undefined; if (!recoveryKeyInput) return; const recoveryKey = recoveryKeyInput.value.trim(); if (!recoveryKey) return; submitRecoveryKey(recoveryKey).then((decodedRecoveryKey) => { if (alive()) { recoveryKeyInput.value = ''; onDecodedRecoveryKey(decodedRecoveryKey); } }); }; return ( Recovery Key {driveKeyState.status === AsyncStatus.Error && ( {driveKeyState.error.message} )} ); }