import crypto from "crypto"; import { FormEvent, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; import { faCheck, faX } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import jsrp from "jsrp"; import queryString from "query-string"; import Button from "@app/components/basic/buttons/Button"; import InputField from "@app/components/basic/InputField"; import passwordCheck from "@app/components/utilities/checks/PasswordCheck"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; import { deriveArgonKey } from "../components/utilities/cryptography/crypto"; import EmailVerifyOnPasswordReset from "./api/auth/EmailVerifyOnPasswordReset"; import getBackupEncryptedPrivateKey from "./api/auth/getBackupEncryptedPrivateKey"; import resetPasswordOnAccountRecovery from "./api/auth/resetPasswordOnAccountRecovery"; // eslint-disable-next-line new-cap const client = new jsrp.client(); export default function PasswordReset() { const [verificationToken, setVerificationToken] = useState(""); const [step, setStep] = useState(1); const [loading, setLoading] = useState(false); const [backupKey, setBackupKey] = useState(""); const [privateKey, setPrivateKey] = useState(""); const [newPassword, setNewPassword] = useState(""); const [backupKeyError, setBackupKeyError] = useState(false); const [passwordErrorLength, setPasswordErrorLength] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const router = useRouter(); const parsedUrl = queryString.parse(router.asPath.split("?")[1]); const token = parsedUrl.token as string; const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); // Unencrypt the private key with a backup key const getEncryptedKeyHandler = async (e: FormEvent) => { e.preventDefault(); try { const result = await getBackupEncryptedPrivateKey({ verificationToken }); setPrivateKey( Aes256Gcm.decrypt({ ciphertext: result.encryptedPrivateKey, iv: result.iv, tag: result.tag, secret: backupKey }) ); setStep(3); } catch { setBackupKeyError(true); } }; // If everything is correct, reset the password const resetPasswordHandler = async (e: FormEvent) => { e.preventDefault(); const errorCheck = passwordCheck({ password: newPassword, setPasswordErrorLength, setPasswordErrorNumber, setPasswordErrorLowerCase, errorCheck: false }); if (!errorCheck) { client.init( { username: email, password: newPassword }, async () => { client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => { const derivedKey = await deriveArgonKey({ password: newPassword, salt: result.salt, mem: 65536, time: 3, parallelism: 1, hashLen: 32 }); if (!derivedKey) throw new Error("Failed to derive key from password"); const key = crypto.randomBytes(32); // create encrypted private key by encrypting the private // key with the symmetric key [key] const { ciphertext: encryptedPrivateKey, iv: encryptedPrivateKeyIV, tag: encryptedPrivateKeyTag } = Aes256Gcm.encrypt({ text: privateKey, secret: key }); // create the protected key by encrypting the symmetric key // [key] with the derived key const { ciphertext: protectedKey, iv: protectedKeyIV, tag: protectedKeyTag } = Aes256Gcm.encrypt({ text: key.toString("hex"), secret: Buffer.from(derivedKey.hash) }); const response = await resetPasswordOnAccountRecovery({ protectedKey, protectedKeyIV, protectedKeyTag, encryptedPrivateKey, encryptedPrivateKeyIV, encryptedPrivateKeyTag, salt: result.salt, verifier: result.verifier, verificationToken }); // if everything works, go the main dashboard page. if (response?.status === 200) { router.push("/login"); } setLoading(false) }); } ); } }; // Click a button to confirm email const stepConfirmEmail = (

Confirm your email

verify email
); // Input backup key const stepInputBackupKey = (

Enter your backup key

You can find it in your emergency kit. You had to download the emergency kit during signup.

); // Enter new password const stepEnterNewPassword = (

Enter new password

Make sure you save it somewhere save.

{ setNewPassword(password); passwordCheck({ password, setPasswordErrorLength, setPasswordErrorNumber, setPasswordErrorLowerCase, errorCheck: false }); }} type="password" value={newPassword} isRequired error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber} autoComplete="new-password" id="new-password" />
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
Password should contain at least:
{passwordErrorLength ? ( ) : ( )}
14 characters
{passwordErrorLowerCase ? ( ) : ( )}
1 lowercase character
{passwordErrorNumber ? ( ) : ( )}
1 number
) : (
)}
); return (
{step === 1 && stepConfirmEmail} {step === 2 && stepInputBackupKey} {step === 3 && stepEnterNewPassword}
); }