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/password/PasswordCheck"; import Aes256Gcm from "@app/components/utilities/cryptography/aes-256-gcm"; import { useResetPassword, useVerifyPasswordResetCode } from "@app/hooks/api"; import { getBackupEncryptedPrivateKey } from "@app/hooks/api/auth/queries"; import { deriveArgonKey } from "../components/utilities/cryptography/crypto"; // 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 [passwordErrorTooShort, setPasswordErrorTooShort] = useState(false); const [passwordErrorTooLong, setPasswordErrorTooLong] = useState(false); const [passwordErrorNoLetterChar, setPasswordErrorNoLetterChar] = useState(false); const [passwordErrorNoNumOrSpecialChar, setPasswordErrorNoNumOrSpecialChar] = useState(false); const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false); const [passwordErrorEscapeChar, setPasswordErrorEscapeChar] = useState(false); const [passwordErrorLowEntropy, setPasswordErrorLowEntropy] = useState(false); const [passwordErrorBreached, setPasswordErrorBreached] = useState(false); const router = useRouter(); const { mutateAsync: verifyPasswordResetCodeMutateAsync } = useVerifyPasswordResetCode(); const { mutateAsync: resetPasswordMutateAsync } = useResetPassword(); const parsedUrl = queryString.parse(router.asPath.split("?")[1]); const token = parsedUrl.token as string; const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); // Decrypt 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 (err) { console.error(err); setBackupKeyError(true); } }; // If everything is correct, reset the password const resetPasswordHandler = async (e: FormEvent) => { e.preventDefault(); const errorCheck = await passwordCheck({ password: newPassword, setPasswordErrorTooShort, setPasswordErrorTooLong, setPasswordErrorNoLetterChar, setPasswordErrorNoNumOrSpecialChar, setPasswordErrorRepeatedChar, setPasswordErrorEscapeChar, setPasswordErrorLowEntropy, setPasswordErrorBreached }); 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) }); await resetPasswordMutateAsync({ protectedKey, protectedKeyIV, protectedKeyTag, encryptedPrivateKey, encryptedPrivateKeyIV, encryptedPrivateKeyTag, salt: result.salt, verifier: result.verifier, verificationToken }); 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 safe.

{ setNewPassword(password); passwordCheck({ password, setPasswordErrorTooShort, setPasswordErrorTooLong, setPasswordErrorNoLetterChar, setPasswordErrorNoNumOrSpecialChar, setPasswordErrorRepeatedChar, setPasswordErrorEscapeChar, setPasswordErrorLowEntropy, setPasswordErrorBreached }); }} type="password" value={newPassword} isRequired error={ passwordErrorTooShort && passwordErrorTooLong && passwordErrorNoLetterChar && passwordErrorNoNumOrSpecialChar && passwordErrorRepeatedChar && passwordErrorEscapeChar && passwordErrorLowEntropy && passwordErrorBreached } autoComplete="new-password" id="new-password" />
{passwordErrorTooShort || passwordErrorTooLong || passwordErrorNoLetterChar || passwordErrorNoNumOrSpecialChar || passwordErrorRepeatedChar || passwordErrorEscapeChar || passwordErrorLowEntropy || passwordErrorBreached ? (
Password should contain:
{passwordErrorTooShort ? ( ) : ( )}
at least 14 characters
{passwordErrorTooLong ? ( ) : ( )}
at most 100 characters
{passwordErrorNoLetterChar ? ( ) : ( )}
at least 1 letter character
{passwordErrorNoNumOrSpecialChar ? ( ) : ( )}
at least 1 number or special character
{passwordErrorRepeatedChar ? ( ) : ( )}
at most 3 repeated, consecutive characters
{passwordErrorEscapeChar ? ( ) : ( )}
No escape characters allowed.
{passwordErrorLowEntropy ? ( ) : ( )}
Password contains personal info.
{passwordErrorBreached ? ( ) : ( )}
Password was found in a data breach.
) : (
)}
); return (
{step === 1 && stepConfirmEmail} {step === 2 && stepInputBackupKey} {step === 3 && stepEnterNewPassword}
); }