verify.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { t } from "i18next";
  2. import { useEffect, useState } from "react";
  3. import { Trans } from "react-i18next";
  4. import { UserVerificationResponse } from "@ente/accounts/types/user";
  5. import { PageProps } from "@ente/shared/apps/types";
  6. import { VerticallyCentered } from "@ente/shared/components/Container";
  7. import EnteSpinner from "@ente/shared/components/EnteSpinner";
  8. import FormPaper from "@ente/shared/components/Form/FormPaper";
  9. import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer";
  10. import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title";
  11. import LinkButton from "@ente/shared/components/LinkButton";
  12. import SingleInputForm, {
  13. SingleInputFormProps,
  14. } from "@ente/shared/components/SingleInputForm";
  15. import { ApiError } from "@ente/shared/error";
  16. import { getAccountsURL } from "@ente/shared/network/api";
  17. import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
  18. import { clearFiles } from "@ente/shared/storage/localForage/helpers";
  19. import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
  20. import {
  21. getLocalReferralSource,
  22. setIsFirstLogin,
  23. } from "@ente/shared/storage/localStorage/helpers";
  24. import { clearKeys } from "@ente/shared/storage/sessionStorage";
  25. import { KeyAttributes, User } from "@ente/shared/user/types";
  26. import { Box, Typography } from "@mui/material";
  27. import { HttpStatusCode } from "axios";
  28. import { putAttributes, sendOtt, verifyOtt } from "../api/user";
  29. import { PAGES } from "../constants/pages";
  30. import { configureSRP } from "../services/srp";
  31. import { logoutUser } from "../services/user";
  32. import { SRPSetupAttributes } from "../types/srp";
  33. export default function VerifyPage({ appContext, router, appName }: PageProps) {
  34. const [email, setEmail] = useState("");
  35. const [resend, setResend] = useState(0);
  36. useEffect(() => {
  37. const main = async () => {
  38. const user: User = getData(LS_KEYS.USER);
  39. const keyAttributes: KeyAttributes = getData(
  40. LS_KEYS.KEY_ATTRIBUTES,
  41. );
  42. if (!user?.email) {
  43. router.push(PAGES.ROOT);
  44. } else if (
  45. keyAttributes?.encryptedKey &&
  46. (user.token || user.encryptedToken)
  47. ) {
  48. router.push(PAGES.CREDENTIALS);
  49. } else {
  50. setEmail(user.email);
  51. }
  52. };
  53. main();
  54. appContext.showNavBar(true);
  55. }, []);
  56. const onSubmit: SingleInputFormProps["callback"] = async (
  57. ott,
  58. setFieldError,
  59. ) => {
  60. try {
  61. const referralSource = getLocalReferralSource();
  62. const resp = await verifyOtt(email, ott, referralSource);
  63. const {
  64. keyAttributes,
  65. encryptedToken,
  66. token,
  67. id,
  68. twoFactorSessionID,
  69. passkeySessionID,
  70. } = resp.data as UserVerificationResponse;
  71. if (passkeySessionID) {
  72. const user = getData(LS_KEYS.USER);
  73. setData(LS_KEYS.USER, {
  74. ...user,
  75. passkeySessionID,
  76. isTwoFactorEnabled: true,
  77. isTwoFactorPasskeysEnabled: true,
  78. });
  79. setIsFirstLogin(true);
  80. window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${
  81. window.location.origin
  82. }/passkeys/finish`;
  83. router.push(PAGES.CREDENTIALS);
  84. } else if (twoFactorSessionID) {
  85. setData(LS_KEYS.USER, {
  86. email,
  87. twoFactorSessionID,
  88. isTwoFactorEnabled: true,
  89. });
  90. setIsFirstLogin(true);
  91. router.push(PAGES.TWO_FACTOR_VERIFY);
  92. } else {
  93. setData(LS_KEYS.USER, {
  94. email,
  95. token,
  96. encryptedToken,
  97. id,
  98. isTwoFactorEnabled: false,
  99. });
  100. if (keyAttributes) {
  101. setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
  102. setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
  103. } else {
  104. if (getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)) {
  105. await putAttributes(
  106. token,
  107. getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES),
  108. );
  109. }
  110. if (getData(LS_KEYS.SRP_SETUP_ATTRIBUTES)) {
  111. const srpSetupAttributes: SRPSetupAttributes = getData(
  112. LS_KEYS.SRP_SETUP_ATTRIBUTES,
  113. );
  114. await configureSRP(srpSetupAttributes);
  115. }
  116. }
  117. clearFiles();
  118. setIsFirstLogin(true);
  119. const redirectURL = InMemoryStore.get(MS_KEYS.REDIRECT_URL);
  120. InMemoryStore.delete(MS_KEYS.REDIRECT_URL);
  121. if (keyAttributes?.encryptedKey) {
  122. clearKeys();
  123. router.push(redirectURL ?? PAGES.CREDENTIALS);
  124. } else {
  125. router.push(redirectURL ?? PAGES.GENERATE);
  126. }
  127. }
  128. } catch (e) {
  129. if (e instanceof ApiError) {
  130. if (e?.httpStatusCode === HttpStatusCode.Unauthorized) {
  131. setFieldError(t("INVALID_CODE"));
  132. } else if (e?.httpStatusCode === HttpStatusCode.Gone) {
  133. setFieldError(t("EXPIRED_CODE"));
  134. }
  135. } else {
  136. setFieldError(`${t("UNKNOWN_ERROR")} ${JSON.stringify(e)}`);
  137. }
  138. }
  139. };
  140. const resendEmail = async () => {
  141. setResend(1);
  142. await sendOtt(appName, email);
  143. setResend(2);
  144. setTimeout(() => setResend(0), 3000);
  145. };
  146. if (!email) {
  147. return (
  148. <VerticallyCentered>
  149. <EnteSpinner />
  150. </VerticallyCentered>
  151. );
  152. }
  153. return (
  154. <VerticallyCentered>
  155. <FormPaper>
  156. <FormPaperTitle sx={{ mb: 14, wordBreak: "break-word" }}>
  157. <Trans
  158. i18nKey="EMAIL_SENT"
  159. components={{
  160. a: <Box color="text.muted" component={"span"} />,
  161. }}
  162. values={{ email }}
  163. />
  164. </FormPaperTitle>
  165. <Typography color={"text.muted"} mb={2} variant="small">
  166. {t("CHECK_INBOX")}
  167. </Typography>
  168. <SingleInputForm
  169. fieldType="text"
  170. autoComplete="one-time-code"
  171. placeholder={t("ENTER_OTT")}
  172. buttonText={t("VERIFY")}
  173. callback={onSubmit}
  174. />
  175. <FormPaperFooter style={{ justifyContent: "space-between" }}>
  176. {resend === 0 && (
  177. <LinkButton onClick={resendEmail}>
  178. {t("RESEND_MAIL")}
  179. </LinkButton>
  180. )}
  181. {resend === 1 && <span>{t("SENDING")}</span>}
  182. {resend === 2 && <span>{t("SENT")}</span>}
  183. <LinkButton onClick={logoutUser}>
  184. {t("CHANGE_EMAIL")}
  185. </LinkButton>
  186. </FormPaperFooter>
  187. </FormPaper>
  188. </VerticallyCentered>
  189. );
  190. }