267 lines
11 KiB
TypeScript
267 lines
11 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Formik, FormikHelpers } from 'formik';
|
|
import * as Yup from 'yup';
|
|
import { sendOtt } from '@ente/accounts/api/user';
|
|
import { setData, LS_KEYS } from '@ente/shared/storage/localStorage';
|
|
import SubmitButton from '@ente/shared/components/SubmitButton';
|
|
import {
|
|
generateAndSaveIntermediateKeyAttributes,
|
|
saveKeyInSessionStore,
|
|
} from '@ente/shared/crypto/helpers';
|
|
import { isWeakPassword } from '@ente/accounts/utils';
|
|
import { generateKeyAndSRPAttributes } from '@ente/accounts/utils/srp';
|
|
|
|
import { setJustSignedUp } from '@ente/shared/storage/localStorage/helpers';
|
|
import { SESSION_KEYS } from '@ente/shared/storage/sessionStorage';
|
|
import { PAGES } from '@ente/accounts/constants/pages';
|
|
import {
|
|
Box,
|
|
Checkbox,
|
|
FormControlLabel,
|
|
FormGroup,
|
|
Link,
|
|
TextField,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import FormPaperTitle from '@ente/shared/components/Form/FormPaper/Title';
|
|
import LinkButton from '@ente/shared/components/LinkButton';
|
|
import FormPaperFooter from '@ente/shared/components/Form/FormPaper/Footer';
|
|
import { VerticallyCentered } from '@ente/shared/components//Container';
|
|
import { PasswordStrengthHint } from '@ente/accounts/components/PasswordStrength';
|
|
import { Trans } from 'react-i18next';
|
|
import { t } from 'i18next';
|
|
import ShowHidePassword from '@ente/shared/components/Form/ShowHidePassword';
|
|
import { APPS } from '@ente/shared/apps/constants';
|
|
import { NextRouter } from 'next/router';
|
|
import { logError } from '@ente/shared/sentry';
|
|
|
|
interface FormValues {
|
|
email: string;
|
|
passphrase: string;
|
|
confirm: string;
|
|
}
|
|
|
|
interface SignUpProps {
|
|
router: NextRouter;
|
|
login: () => void;
|
|
appName: APPS;
|
|
}
|
|
|
|
export default function SignUp({ router, appName, login }: SignUpProps) {
|
|
const [acceptTerms, setAcceptTerms] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
const handleClickShowPassword = () => {
|
|
setShowPassword(!showPassword);
|
|
};
|
|
|
|
const handleMouseDownPassword = (
|
|
event: React.MouseEvent<HTMLButtonElement>
|
|
) => {
|
|
event.preventDefault();
|
|
};
|
|
|
|
const registerUser = async (
|
|
{ email, passphrase, confirm }: FormValues,
|
|
{ setFieldError }: FormikHelpers<FormValues>
|
|
) => {
|
|
try {
|
|
if (passphrase !== confirm) {
|
|
setFieldError('confirm', t('PASSPHRASE_MATCH_ERROR'));
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
setData(LS_KEYS.USER, { email });
|
|
await sendOtt(appName, email);
|
|
} catch (e) {
|
|
setFieldError('confirm', `${t('UNKNOWN_ERROR')} ${e.message}`);
|
|
throw e;
|
|
}
|
|
try {
|
|
const { keyAttributes, masterKey, srpSetupAttributes } =
|
|
await generateKeyAndSRPAttributes(passphrase);
|
|
|
|
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
|
|
setData(LS_KEYS.SRP_SETUP_ATTRIBUTES, srpSetupAttributes);
|
|
await generateAndSaveIntermediateKeyAttributes(
|
|
passphrase,
|
|
keyAttributes,
|
|
masterKey
|
|
);
|
|
|
|
await saveKeyInSessionStore(
|
|
SESSION_KEYS.ENCRYPTION_KEY,
|
|
masterKey
|
|
);
|
|
setJustSignedUp(true);
|
|
router.push(PAGES.VERIFY);
|
|
} catch (e) {
|
|
setFieldError('confirm', t('PASSWORD_GENERATION_FAILED'));
|
|
throw e;
|
|
}
|
|
} catch (err) {
|
|
logError(err, 'signup failed');
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<FormPaperTitle> {t('SIGN_UP')}</FormPaperTitle>
|
|
<Formik<FormValues>
|
|
initialValues={{
|
|
email: '',
|
|
passphrase: '',
|
|
confirm: '',
|
|
}}
|
|
validationSchema={Yup.object().shape({
|
|
email: Yup.string()
|
|
.email(t('EMAIL_ERROR'))
|
|
.required(t('REQUIRED')),
|
|
passphrase: Yup.string().required(t('REQUIRED')),
|
|
confirm: Yup.string().required(t('REQUIRED')),
|
|
})}
|
|
validateOnChange={false}
|
|
validateOnBlur={false}
|
|
onSubmit={registerUser}>
|
|
{({
|
|
values,
|
|
errors,
|
|
handleChange,
|
|
handleSubmit,
|
|
}): JSX.Element => (
|
|
<form noValidate onSubmit={handleSubmit}>
|
|
<VerticallyCentered sx={{ mb: 1 }}>
|
|
<TextField
|
|
fullWidth
|
|
id="email"
|
|
name="email"
|
|
autoComplete="username"
|
|
type="email"
|
|
label={t('ENTER_EMAIL')}
|
|
value={values.email}
|
|
onChange={handleChange('email')}
|
|
error={Boolean(errors.email)}
|
|
helperText={errors.email}
|
|
autoFocus
|
|
disabled={loading}
|
|
/>
|
|
|
|
<TextField
|
|
fullWidth
|
|
id="password"
|
|
name="password"
|
|
autoComplete="new-password"
|
|
type={showPassword ? 'text' : 'password'}
|
|
label={t('PASSPHRASE_HINT')}
|
|
value={values.passphrase}
|
|
onChange={handleChange('passphrase')}
|
|
error={Boolean(errors.passphrase)}
|
|
helperText={errors.passphrase}
|
|
disabled={loading}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<ShowHidePassword
|
|
showPassword={showPassword}
|
|
handleClickShowPassword={
|
|
handleClickShowPassword
|
|
}
|
|
handleMouseDownPassword={
|
|
handleMouseDownPassword
|
|
}
|
|
/>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
<TextField
|
|
fullWidth
|
|
id="confirm-password"
|
|
name="confirm-password"
|
|
autoComplete="new-password"
|
|
type="password"
|
|
label={t('CONFIRM_PASSPHRASE')}
|
|
value={values.confirm}
|
|
onChange={handleChange('confirm')}
|
|
error={Boolean(errors.confirm)}
|
|
helperText={errors.confirm}
|
|
disabled={loading}
|
|
/>
|
|
<PasswordStrengthHint
|
|
password={values.passphrase}
|
|
/>
|
|
<FormGroup sx={{ width: '100%' }}>
|
|
<FormControlLabel
|
|
sx={{
|
|
color: 'text.muted',
|
|
ml: 0,
|
|
mt: 2,
|
|
}}
|
|
control={
|
|
<Checkbox
|
|
size="small"
|
|
disabled={loading}
|
|
checked={acceptTerms}
|
|
onChange={(e) =>
|
|
setAcceptTerms(e.target.checked)
|
|
}
|
|
color="accent"
|
|
/>
|
|
}
|
|
label={
|
|
<Typography variant="small">
|
|
<Trans
|
|
i18nKey={'TERMS_AND_CONDITIONS'}
|
|
components={{
|
|
a: (
|
|
<Link
|
|
href="https://ente.io/terms"
|
|
target="_blank"
|
|
/>
|
|
),
|
|
b: (
|
|
<Link
|
|
href="https://ente.io/privacy"
|
|
target="_blank"
|
|
/>
|
|
),
|
|
}}
|
|
/>
|
|
</Typography>
|
|
}
|
|
/>
|
|
</FormGroup>
|
|
</VerticallyCentered>
|
|
<Box my={4}>
|
|
<SubmitButton
|
|
sx={{ my: 0 }}
|
|
buttonText={t('CREATE_ACCOUNT')}
|
|
loading={loading}
|
|
disabled={
|
|
!acceptTerms ||
|
|
isWeakPassword(values.passphrase)
|
|
}
|
|
/>
|
|
{loading && (
|
|
<Typography
|
|
mt={1}
|
|
textAlign={'center'}
|
|
color="text.muted"
|
|
variant="small">
|
|
{t('KEY_GENERATION_IN_PROGRESS_MESSAGE')}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</form>
|
|
)}
|
|
</Formik>
|
|
|
|
<FormPaperFooter>
|
|
<LinkButton onClick={login}>{t('ACCOUNT_EXISTS')}</LinkButton>
|
|
</FormPaperFooter>
|
|
</>
|
|
);
|
|
}
|