SignUp.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { sendOtt } from "@ente/accounts/api/user";
  2. import { isWeakPassword } from "@ente/accounts/utils";
  3. import { generateKeyAndSRPAttributes } from "@ente/accounts/utils/srp";
  4. import SubmitButton from "@ente/shared/components/SubmitButton";
  5. import {
  6. generateAndSaveIntermediateKeyAttributes,
  7. saveKeyInSessionStore,
  8. } from "@ente/shared/crypto/helpers";
  9. import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
  10. import { Formik, FormikHelpers } from "formik";
  11. import React, { useState } from "react";
  12. import * as Yup from "yup";
  13. import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength";
  14. import { PAGES } from "@ente/accounts/constants/pages";
  15. import { APPS } from "@ente/shared/apps/constants";
  16. import { VerticallyCentered } from "@ente/shared/components//Container";
  17. import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
  18. import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
  19. import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword";
  20. import LinkButton from "@ente/shared/components/LinkButton";
  21. import { logError } from "@ente/shared/sentry";
  22. import {
  23. setJustSignedUp,
  24. setLocalReferralSource,
  25. } from "@ente/shared/storage/localStorage/helpers";
  26. import { SESSION_KEYS } from "@ente/shared/storage/sessionStorage";
  27. import InfoOutlined from "@mui/icons-material/InfoOutlined";
  28. import {
  29. Box,
  30. Checkbox,
  31. FormControlLabel,
  32. FormGroup,
  33. IconButton,
  34. InputAdornment,
  35. Link,
  36. TextField,
  37. Tooltip,
  38. Typography,
  39. } from "@mui/material";
  40. import { t } from "i18next";
  41. import { NextRouter } from "next/router";
  42. import { Trans } from "react-i18next";
  43. interface FormValues {
  44. email: string;
  45. passphrase: string;
  46. confirm: string;
  47. referral: string;
  48. }
  49. interface SignUpProps {
  50. router: NextRouter;
  51. login: () => void;
  52. appName: APPS;
  53. }
  54. export default function SignUp({ router, appName, login }: SignUpProps) {
  55. const [acceptTerms, setAcceptTerms] = useState(false);
  56. const [loading, setLoading] = useState(false);
  57. const [showPassword, setShowPassword] = useState(false);
  58. const handleClickShowPassword = () => {
  59. setShowPassword(!showPassword);
  60. };
  61. const handleMouseDownPassword = (
  62. event: React.MouseEvent<HTMLButtonElement>,
  63. ) => {
  64. event.preventDefault();
  65. };
  66. const registerUser = async (
  67. { email, passphrase, confirm, referral }: FormValues,
  68. { setFieldError }: FormikHelpers<FormValues>,
  69. ) => {
  70. try {
  71. if (passphrase !== confirm) {
  72. setFieldError("confirm", t("PASSPHRASE_MATCH_ERROR"));
  73. return;
  74. }
  75. setLoading(true);
  76. try {
  77. setData(LS_KEYS.USER, { email });
  78. setLocalReferralSource(referral);
  79. await sendOtt(appName, email);
  80. } catch (e) {
  81. setFieldError("confirm", `${t("UNKNOWN_ERROR")} ${e.message}`);
  82. throw e;
  83. }
  84. try {
  85. const { keyAttributes, masterKey, srpSetupAttributes } =
  86. await generateKeyAndSRPAttributes(passphrase);
  87. setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
  88. setData(LS_KEYS.SRP_SETUP_ATTRIBUTES, srpSetupAttributes);
  89. await generateAndSaveIntermediateKeyAttributes(
  90. passphrase,
  91. keyAttributes,
  92. masterKey,
  93. );
  94. await saveKeyInSessionStore(
  95. SESSION_KEYS.ENCRYPTION_KEY,
  96. masterKey,
  97. );
  98. setJustSignedUp(true);
  99. router.push(PAGES.VERIFY);
  100. } catch (e) {
  101. setFieldError("confirm", t("PASSWORD_GENERATION_FAILED"));
  102. throw e;
  103. }
  104. } catch (err) {
  105. logError(err, "signup failed");
  106. }
  107. setLoading(false);
  108. };
  109. return (
  110. <>
  111. <FormPaperTitle> {t("SIGN_UP")}</FormPaperTitle>
  112. <Formik<FormValues>
  113. initialValues={{
  114. email: "",
  115. passphrase: "",
  116. confirm: "",
  117. referral: "",
  118. }}
  119. validationSchema={Yup.object().shape({
  120. email: Yup.string()
  121. .email(t("EMAIL_ERROR"))
  122. .required(t("REQUIRED")),
  123. passphrase: Yup.string().required(t("REQUIRED")),
  124. confirm: Yup.string().required(t("REQUIRED")),
  125. })}
  126. validateOnChange={false}
  127. validateOnBlur={false}
  128. onSubmit={registerUser}
  129. >
  130. {({
  131. values,
  132. errors,
  133. handleChange,
  134. handleSubmit,
  135. }): JSX.Element => (
  136. <form noValidate onSubmit={handleSubmit}>
  137. <VerticallyCentered sx={{ mb: 1 }}>
  138. <TextField
  139. fullWidth
  140. id="email"
  141. name="email"
  142. autoComplete="username"
  143. type="email"
  144. label={t("ENTER_EMAIL")}
  145. value={values.email}
  146. onChange={handleChange("email")}
  147. error={Boolean(errors.email)}
  148. helperText={errors.email}
  149. autoFocus
  150. disabled={loading}
  151. />
  152. <TextField
  153. fullWidth
  154. id="password"
  155. name="password"
  156. autoComplete="new-password"
  157. type={showPassword ? "text" : "password"}
  158. label={t("PASSPHRASE_HINT")}
  159. value={values.passphrase}
  160. onChange={handleChange("passphrase")}
  161. error={Boolean(errors.passphrase)}
  162. helperText={errors.passphrase}
  163. disabled={loading}
  164. InputProps={{
  165. endAdornment: (
  166. <ShowHidePassword
  167. showPassword={showPassword}
  168. handleClickShowPassword={
  169. handleClickShowPassword
  170. }
  171. handleMouseDownPassword={
  172. handleMouseDownPassword
  173. }
  174. />
  175. ),
  176. }}
  177. />
  178. <TextField
  179. fullWidth
  180. id="confirm-password"
  181. name="confirm-password"
  182. autoComplete="new-password"
  183. type="password"
  184. label={t("CONFIRM_PASSPHRASE")}
  185. value={values.confirm}
  186. onChange={handleChange("confirm")}
  187. error={Boolean(errors.confirm)}
  188. helperText={errors.confirm}
  189. disabled={loading}
  190. />
  191. <PasswordStrengthHint
  192. password={values.passphrase}
  193. />
  194. <Box sx={{ width: "100%" }}>
  195. <Typography
  196. textAlign={"left"}
  197. color="text.secondary"
  198. mt={"24px"}
  199. >
  200. {t("REFERRAL_CODE_HINT")}
  201. </Typography>
  202. <TextField
  203. hiddenLabel
  204. InputProps={{
  205. endAdornment: (
  206. <InputAdornment position="end">
  207. <Tooltip
  208. title={t("REFERRAL_INFO")}
  209. >
  210. <IconButton
  211. tabIndex={-1}
  212. color="secondary"
  213. edge={"end"}
  214. >
  215. <InfoOutlined />
  216. </IconButton>
  217. </Tooltip>
  218. </InputAdornment>
  219. ),
  220. }}
  221. fullWidth
  222. name="referral"
  223. type="text"
  224. value={values.referral}
  225. onChange={handleChange("referral")}
  226. error={Boolean(errors.referral)}
  227. disabled={loading}
  228. />
  229. </Box>
  230. <FormGroup sx={{ width: "100%" }}>
  231. <FormControlLabel
  232. sx={{
  233. color: "text.muted",
  234. ml: 0,
  235. mt: 2,
  236. mb: 0,
  237. }}
  238. control={
  239. <Checkbox
  240. size="small"
  241. disabled={loading}
  242. checked={acceptTerms}
  243. onChange={(e) =>
  244. setAcceptTerms(e.target.checked)
  245. }
  246. color="accent"
  247. />
  248. }
  249. label={
  250. <Typography variant="small">
  251. <Trans
  252. i18nKey={"TERMS_AND_CONDITIONS"}
  253. components={{
  254. a: (
  255. <Link
  256. href="https://ente.io/terms"
  257. target="_blank"
  258. />
  259. ),
  260. b: (
  261. <Link
  262. href="https://ente.io/privacy"
  263. target="_blank"
  264. />
  265. ),
  266. }}
  267. />
  268. </Typography>
  269. }
  270. />
  271. </FormGroup>
  272. </VerticallyCentered>
  273. <Box mb={4}>
  274. <SubmitButton
  275. sx={{ my: 0 }}
  276. buttonText={t("CREATE_ACCOUNT")}
  277. loading={loading}
  278. disabled={
  279. !acceptTerms ||
  280. isWeakPassword(values.passphrase)
  281. }
  282. />
  283. {loading && (
  284. <Typography
  285. mt={1}
  286. textAlign={"center"}
  287. color="text.muted"
  288. variant="small"
  289. >
  290. {t("KEY_GENERATION_IN_PROGRESS_MESSAGE")}
  291. </Typography>
  292. )}
  293. </Box>
  294. </form>
  295. )}
  296. </Formik>
  297. <FormPaperFooter>
  298. <LinkButton onClick={login}>{t("ACCOUNT_EXISTS")}</LinkButton>
  299. </FormPaperFooter>
  300. </>
  301. );
  302. }