import * as yup from "yup" import {useFormik} from "formik" import {MdCheckCircle, MdChevronRight, MdLock} from "react-icons/md" import {generateKey, readKey} from "openpgp" import {AxiosError} from "axios" import {useTranslation} from "react-i18next" import React, {ReactElement, useContext, useMemo} from "react" import passwordGenerator from "secure-random-password" import {LoadingButton} from "@mui/lab" import {Box, Grid, InputAdornment, Typography} from "@mui/material" import {useMutation} from "@tanstack/react-query" import {PasswordField} from "~/components" import {buildEncryptionPassword, encryptString} from "~/utils" import {isDev} from "~/constants/development" import {useSystemPreferredTheme, useUser} from "~/hooks" import {MASTER_PASSWORD_LENGTH} from "~/constants/values" import {AuthenticationDetails, UserNote} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" import {encryptUserNote} from "~/utils/encrypt-user-note" import AuthContext from "~/AuthContext/AuthContext" export interface PasswordFormProps { onDone: () => void } interface Form { password: string passwordConfirmation: string detail?: string } export default function PasswordForm({onDone}: PasswordFormProps): ReactElement { const {t} = useTranslation() const user = useUser() const theme = useSystemPreferredTheme() const schema = yup.object().shape({ password: yup.string().required(), passwordConfirmation: yup .string() .required() .oneOf( [yup.ref("password"), null], t( "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.mustMatchHelperText", ) as string, ) .label(t("routes.CompleteAccountRoute.forms.password.form.passwordConfirm.label")), }) const {_setDecryptionPassword, login} = useContext(AuthContext) const awaitGenerateKey = useMemo( () => generateKey({ type: "rsa", format: "armored", userIDs: [{name: "John Smith", email: "john@example.com"}], passphrase: "", rsaBits: isDev ? 2048 : 4096, }), [], ) const {mutateAsync} = useMutation( updateAccount, { onSuccess: ({user}) => { login(user) onDone() }, }, ) const formik = useFormik
({ validationSchema: schema, initialValues: { password: "", passwordConfirmation: "", }, onSubmit: async (values, {setErrors}) => { try { const keyPair = await awaitGenerateKey const masterPassword = passwordGenerator.randomPassword({ length: MASTER_PASSWORD_LENGTH, }) const encryptionPassword = buildEncryptionPassword( values.password, user.email.address, ) const encryptedMasterPassword = encryptString(masterPassword, encryptionPassword) const note: UserNote = { theme, privateKey: keyPair.privateKey, } const encryptedNotes = encryptUserNote(note, masterPassword) _setDecryptionPassword(encryptionPassword) await mutateAsync({ encryptedPassword: encryptedMasterPassword, publicKey: ( await readKey({ armoredKey: keyPair.publicKey, }) ) .toPublic() .armor(), encryptedNotes, }) } catch (error) { setErrors({detail: t("general.defaultError")}) } }, }) return ( {t("routes.CompleteAccountRoute.forms.password.title")} {t("routes.CompleteAccountRoute.forms.password.description")} ), }} /> ), }} /> } > {t("routes.CompleteAccountRoute.forms.password.continueAction")}
) }