2022-10-16 18:37:09 +02:00

183 lines
4.5 KiB
TypeScript

import * as yup from "yup"
import {useFormik} from "formik"
import {MdCheckCircle, MdChevronRight, MdLock} from "react-icons/md"
import {generateKey} from "openpgp"
import React, {ReactElement, 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 {useUser} from "~/hooks"
import {MASTER_PASSWORD_LENGTH} from "~/constants/values"
interface Form {
password: string
passwordConfirmation: string
detail?: string
}
const schema = yup.object().shape({
password: yup.string().required(),
passwordConfirmation: yup
.string()
.required()
.oneOf([yup.ref("password"), null], "Passwords must match"),
})
export default function PasswordForm(): ReactElement {
const user = useUser()
const {} = useMutation<>()
const awaitGenerateKey = useMemo(
() =>
generateKey({
type: "rsa",
format: "armored",
curve: "curve25519",
userIDs: [{name: "John Smith", email: "john@example.com"}],
passphrase: "",
rsaBits: isDev ? 2048 : 4096,
}),
[],
)
const formik = useFormik<Form>({
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,
`${values.password}-${user.email.address}`,
)
const encryptedPrivateKey = encryptString(
keyPair.privateKey,
masterPassword,
)
} catch (error) {
setErrors({detail: "An error occurred"})
}
},
})
return (
<Box width="80vw">
<form onSubmit={formik.handleSubmit}>
<Grid
container
spacing={4}
paddingX={2}
paddingY={4}
alignItems="center"
justifyContent="center"
>
<Grid item>
<Grid container spacing={2} direction="column">
<Grid item>
<Typography
variant="h6"
component="h2"
align="center"
>
Set up your password
</Typography>
</Grid>
<Grid item>
<Typography variant="subtitle1" component="p">
Please enter a safe password so that we can
encrypt your data.
</Typography>
</Grid>
</Grid>
</Grid>
<Grid item>
<Grid container spacing={2} justifyContent="center">
<Grid item>
<PasswordField
fullWidth
id="password"
name="password"
label="Password"
autoComplete="new-password"
value={formik.values.password}
onChange={formik.handleChange}
disabled={formik.isSubmitting}
error={
formik.touched.password &&
Boolean(formik.errors.password)
}
helperText={
formik.touched.password &&
formik.errors.password
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MdLock />
</InputAdornment>
),
}}
/>
</Grid>
<Grid item>
<PasswordField
fullWidth
id="passwordConfirmation"
name="passwordConfirmation"
label="Confirm Password"
value={formik.values.passwordConfirmation}
onChange={formik.handleChange}
disabled={formik.isSubmitting}
error={
formik.touched.passwordConfirmation &&
Boolean(
formik.errors.passwordConfirmation,
)
}
helperText={
formik.touched.passwordConfirmation &&
formik.errors.passwordConfirmation
}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MdCheckCircle />
</InputAdornment>
),
}}
/>
</Grid>
</Grid>
</Grid>
<Grid item>
<LoadingButton
type="submit"
variant="contained"
loading={formik.isSubmitting}
startIcon={<MdChevronRight />}
>
Continue
</LoadingButton>
</Grid>
</Grid>
</form>
</Box>
)
}