import { useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import * as uint8arrays from 'uint8arrays' import * as RootKey from '@oddjs/odd/common/root-key' import * as UCAN from "@oddjs/odd/ucan/index" import { filesystemStore, sessionStore } from '../../../stores' import { RECOVERY_STATES, USERNAME_STORAGE_KEY, loadAccount, prepareUsername } from '../../../lib/auth/account' import Check from '../../icons/CheckIcon' import RecoveryKitButton from './RecoveryKitButton' const HasRecoveryKit = () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, setFilesystem] = useRecoilState(filesystemStore) const session = useRecoilValue(sessionStore) const [state, setState] = useState<RECOVERY_STATES>( session.session ? RECOVERY_STATES.Done : RECOVERY_STATES.Ready ); /** * Parse the user's `username` and `readKey` from the uploaded recovery kit and pass them into * odd to recover the user's account and populate the `session` and `filesystem` stores * @param files */ const handleFileInput: ( files: FileList ) => Promise<void> = async files => { const reader = new FileReader() reader.onload = async event => { setState(RECOVERY_STATES.Processing) try { const { authStrategy, program: { components: { crypto, reference, storage }, }, } = session; const parts = (event.target.result as string) .split("username: ")[1] .split("key: "); const readKey = uint8arrays.fromString( parts[1].replace(/(\r\n|\n|\r)/gm, ""), "base64pad" ); const oldUsername = parts[0].replace(/(\r\n|\n|\r)/gm, ""); const hashedOldUsername = await prepareUsername(oldUsername); const newRootDID = await session.program.agentDID(); // Construct a new username using the old `trimmed` name and `newRootDID` const newUsername = `${oldUsername.split("#")[0]}#${newRootDID}`; const hashedNewUsername = await prepareUsername(newUsername); storage.setItem(USERNAME_STORAGE_KEY, newUsername); // Register the user with the `hashedNewUsername` const { success } = await authStrategy.register({ username: hashedNewUsername, }); if (!success) { throw new Error("Failed to register new user"); } // Build an ephemeral UCAN to allow the const proof: string | null = await storage.getItem( storage.KEYS.ACCOUNT_UCAN ); const ucan = await UCAN.build({ dependencies: session.program.components, potency: "APPEND", resource: "*", proof: proof ? proof : undefined, lifetimeInSeconds: 60 * 3, // Three minutes audience: newRootDID, issuer: newRootDID, }); const oldRootCID = await reference.dataRoot.lookup(hashedOldUsername); // Update the dataRoot of the new user await reference.dataRoot.update(oldRootCID, ucan); // Store the accountDID and readKey in odd so they can be used internally load the file system await RootKey.store({ accountDID: newRootDID, readKey, crypto: crypto, }); // Load account data into sessionStore await loadAccount(hashedNewUsername, newUsername); setState(RECOVERY_STATES.Done); } catch (error) { console.error(error) setState(RECOVERY_STATES.Error) } } reader.onerror = error => { console.error(error) setState(RECOVERY_STATES.Error) } reader.readAsText(files[0]) } return ( <div className="min-h-[calc(100vh-96px)] flex flex-col items-start justify-center max-w-[590px] m-auto gap-6 pb-5 text-sm" > <h1 className="text-xl">Recover your account</h1> {state === RECOVERY_STATES.Done ? ( <> <h3 className="flex items-center gap-2 font-normal text-base text-green-600"> <Check /> Account recovered! </h3> <p> Welcome back <strong>{session.username.trimmed}.</strong> We were able to successfully recover all of your private data. </p> </> ) : ( <p> If you’ve lost access to all of your connected devices, you can use your recovery kit to restore access to your private data. </p> )} {state === RECOVERY_STATES.Error && ( <p className="text-red-600"> We were unable to recover your account. Please double check that you uploaded the correct file. </p> )} <div className="flex flex-col gap-2"> <RecoveryKitButton handleFileInput={handleFileInput} state={state} /> {state !== RECOVERY_STATES.Done && ( <p className="text-xxs"> {`It should be a file named ODD-RecoveryKit-{yourUsername}.txt`} </p> )} </div> </div> ) } export default HasRecoveryKit