From b1fa1890b48f6c44dae8d32065539de08f11c17d Mon Sep 17 00:00:00 2001 From: Amir Angel <36531255+17Amir17@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:10:04 +0300 Subject: [PATCH] forgot password --- .../screens/Home/NewHomeScreen.tsx | 14 +- .../Login/NewAlreadyRegisteredScreen.tsx | 4 +- .../screens/Login/NewCreatePasswordScreen.tsx | 189 +++++++++++++++++- .../shared/Login/NewPasswordForm2.tsx | 31 +-- .../shared/Modals/ChangePasswordModal.tsx | 8 + apps/passport-client/src/dispatch.ts | 15 +- 6 files changed, 232 insertions(+), 29 deletions(-) diff --git a/apps/passport-client/new-components/screens/Home/NewHomeScreen.tsx b/apps/passport-client/new-components/screens/Home/NewHomeScreen.tsx index cb853c14ed..238a1f746e 100644 --- a/apps/passport-client/new-components/screens/Home/NewHomeScreen.tsx +++ b/apps/passport-client/new-components/screens/Home/NewHomeScreen.tsx @@ -7,6 +7,11 @@ import { } from "@pcd/eddsa-ticket-pcd"; import { Spacer } from "@pcd/passport-ui"; import { PCD } from "@pcd/pcd-types"; +import { + PODTicketPCD, + PODTicketPCDTypeName, + isPODTicketPCD +} from "@pcd/pod-ticket-pcd"; import { ReactElement, useEffect, @@ -17,17 +22,12 @@ import { import { useNavigate } from "react-router-dom"; import styled, { FlattenSimpleInterpolation, css } from "styled-components"; import { AppContainer } from "../../../components/shared/AppContainer"; +import { CardBody } from "../../../components/shared/PCDCard"; import { usePCDs, useSelf } from "../../../src/appHooks"; import { useSyncE2EEStorage } from "../../../src/useSyncE2EEStorage"; import { FloatingMenu } from "../../shared/FloatingMenu"; -import { CardBody } from "../../../components/shared/PCDCard"; -import { TicketCard } from "../../shared/TicketCard"; import { NewModals } from "../../shared/Modals/NewModals"; -import { - PODTicketPCD, - PODTicketPCDTypeName, - isPODTicketPCD -} from "@pcd/pod-ticket-pcd"; +import { TicketCard } from "../../shared/TicketCard"; import { Typography } from "../../shared/Typography"; const GAP = 4; diff --git a/apps/passport-client/new-components/screens/Login/NewAlreadyRegisteredScreen.tsx b/apps/passport-client/new-components/screens/Login/NewAlreadyRegisteredScreen.tsx index 36ba2cf24a..422438449e 100644 --- a/apps/passport-client/new-components/screens/Login/NewAlreadyRegisteredScreen.tsx +++ b/apps/passport-client/new-components/screens/Login/NewAlreadyRegisteredScreen.tsx @@ -175,9 +175,9 @@ export const NewAlreadyRegisteredScreen: React.FC = () => { useEffect(() => { if (self || !email || !identityCommitment) { if (hasPendingRequest()) { - window.location.hash = "#/login-interstitial"; + window.location.hash = "#/new/login-interstitial"; } else { - window.location.hash = "#/"; + window.location.hash = "#/new"; } } }, [self, email, identityCommitment]); diff --git a/apps/passport-client/new-components/screens/Login/NewCreatePasswordScreen.tsx b/apps/passport-client/new-components/screens/Login/NewCreatePasswordScreen.tsx index 2ebfcd297c..25ae54860e 100644 --- a/apps/passport-client/new-components/screens/Login/NewCreatePasswordScreen.tsx +++ b/apps/passport-client/new-components/screens/Login/NewCreatePasswordScreen.tsx @@ -1,3 +1,188 @@ -export const NewCreatePasswordScreen = (): JSX.Element => { - return <>; +import { requestVerifyToken } from "@pcd/passport-interface"; +import { sleep, validateEmail } from "@pcd/util"; +import { useCallback, useEffect, useState } from "react"; +import { AppContainer } from "../../../components/shared/AppContainer"; +import { appConfig } from "../../../src/appConfig"; +import { useDispatch, useQuery, useSelf } from "../../../src/appHooks"; +import { hasPendingRequest } from "../../../src/sessionStorage"; +import { + LoginContainer, + LoginTitleContainer +} from "../../shared/Login/LoginComponents"; +import { NewPasswordForm2 } from "../../shared/Login/NewPasswordForm2"; +import { Typography } from "../../shared/Typography"; + +export const NewCreatePasswordScreen = (): JSX.Element | null => { + const dispatch = useDispatch(); + const self = useSelf(); + const query = useQuery(); + const email = query?.get("email"); + const token = query?.get("token"); + const autoRegister = query?.get("autoRegister") === "true"; + const targetFolder = query?.get("targetFolder"); + const [error, setError] = useState(); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [revealPassword, setRevealPassword] = useState(false); + const [settingPassword, setSettingPassword] = useState(false); + const [skipConfirm, setSkipConfirm] = useState(false); + + const redirectToLoginPageWithError = useCallback((e: Error | string) => { + console.error(e); + window.location.hash = "#/login"; + window.location.reload(); + }, []); + + const onSkipPassword = useCallback(async () => { + try { + // If email or token are undefined, we will already have redirected to + // login, so this is just for type-checking + if (email && token) { + setSettingPassword(true); + await sleep(); + await dispatch({ + type: "create-user-skip-password", + email, + token, + targetFolder, + autoRegister, + newUi: true + }); + } + } finally { + setSettingPassword(false); + if (autoRegister) { + window.location.href = "#/new"; + } + } + }, [dispatch, email, token, targetFolder, autoRegister]); + + const checkIfShouldRedirect = useCallback(async () => { + // Redirect to home if already logged in + if (self) { + // Present alert if we had tried to auto-register with a different + // email than the currently logged-in email. + if (autoRegister && !self.emails.includes(email as string)) { + alert( + `You are already logged in as ${ + self.emails.length === 1 + ? self.emails?.[0] + : "an account that owns the following email addresses: " + + self.emails.join(", ") + }. Please log out and try navigating to the link again.` + ); + } + + if (hasPendingRequest()) { + window.location.hash = "#/new/login-interstitial"; + } else { + window.location.hash = "#/new"; + } + return; + } + if (!email || !validateEmail(email) || !token) { + return redirectToLoginPageWithError( + "Invalid email or token, redirecting to login" + ); + } + + if (autoRegister) { + await onSkipPassword(); + } else { + const verifyTokenResult = await requestVerifyToken( + appConfig.zupassServer, + email, + token + ); + + if (!verifyTokenResult.success) { + return redirectToLoginPageWithError( + "Invalid email or token, redirecting to login" + ); + } + } + }, [ + self, + email, + redirectToLoginPageWithError, + token, + autoRegister, + onSkipPassword + ]); + + useEffect(() => { + checkIfShouldRedirect(); + }, [checkIfShouldRedirect]); + + const onSetPassword = useCallback(async () => { + try { + // If email or token are undefined, we will already have redirected to + // login, so this is just for type-checking + if (email && token) { + setSettingPassword(true); + await sleep(); + await dispatch({ + type: "login", + email, + token, + password, + newUi: true + }); + } + } finally { + setSettingPassword(false); + } + }, [dispatch, email, password, token]); + + const onCancelClick = useCallback(() => { + window.location.href = "#/new"; + }, []); + + // If either email or token are undefined, we will already have redirected + if (!email || !token) { + return null; + } + + return ( + + + + + ADD PASSWORD + + + Make sure that your password is secure, unique, and memorable. If + you forget your password, you'll have to reset your account, and you + will lose access to all your PCDs. + + + { + // onSkipPassword(); + // }} + style={{ width: "100%", marginBottom: 24 }} + /> + + + ); }; diff --git a/apps/passport-client/new-components/shared/Login/NewPasswordForm2.tsx b/apps/passport-client/new-components/shared/Login/NewPasswordForm2.tsx index 381d2a3f94..45d254e102 100644 --- a/apps/passport-client/new-components/shared/Login/NewPasswordForm2.tsx +++ b/apps/passport-client/new-components/shared/Login/NewPasswordForm2.tsx @@ -1,6 +1,5 @@ import { Dispatch, SetStateAction, UIEvent, useRef } from "react"; import styled from "styled-components"; -import { useDispatch } from "../../../src/appHooks"; import { PASSWORD_MINIMUM_LENGTH, checkPasswordStrength @@ -18,11 +17,15 @@ interface NewPasswordForm { revealPassword: boolean; setRevealPassword: Dispatch>; onSuccess: () => void; + onCancel: () => void; submitButtonText: string; passwordInputPlaceholder?: string; // Override placeholder on the first input autoFocus?: boolean; setError: Dispatch>; error?: string; + showSkipConfirm?: boolean; + onSkipConfirm?: () => void; + style?: React.CSSProperties; } export const NewPasswordForm2 = ({ @@ -35,13 +38,16 @@ export const NewPasswordForm2 = ({ revealPassword, setRevealPassword, onSuccess, + onCancel, submitButtonText, passwordInputPlaceholder, autoFocus, setError, - error + error, + showSkipConfirm, + onSkipConfirm, + style }: NewPasswordForm): JSX.Element => { - const dispatch = useDispatch(); const confirmPasswordRef = useRef(null); const checkPasswordAndSubmit = (e: UIEvent): void => { @@ -87,7 +93,7 @@ export const NewPasswordForm2 = ({ }; return ( - + {/* For password manager autofill */} @@ -125,19 +131,14 @@ export const NewPasswordForm2 = ({ {submitButtonText} - { - dispatch({ - type: "set-bottom-modal", - modal: { - modalType: "settings" - } - }); - }} - variant="secondary" - > + Back + {showSkipConfirm && ( + + Skip for now + + )} ); diff --git a/apps/passport-client/new-components/shared/Modals/ChangePasswordModal.tsx b/apps/passport-client/new-components/shared/Modals/ChangePasswordModal.tsx index ec97b2266c..cd4bf3814c 100644 --- a/apps/passport-client/new-components/shared/Modals/ChangePasswordModal.tsx +++ b/apps/passport-client/new-components/shared/Modals/ChangePasswordModal.tsx @@ -157,6 +157,14 @@ export const ChangePasswordModal = (): JSX.Element | null => { setPassword={setNewPassword} setConfirmPassword={setConfirmPassword} onSuccess={onChangePassword} + onCancel={() => { + dispatch({ + type: "set-bottom-modal", + modal: { + modalType: "settings" + } + }); + }} /> diff --git a/apps/passport-client/src/dispatch.ts b/apps/passport-client/src/dispatch.ts index b26ad8a77c..7c12e1a0de 100644 --- a/apps/passport-client/src/dispatch.ts +++ b/apps/passport-client/src/dispatch.ts @@ -99,6 +99,7 @@ export type Action = email: string; password: string; token: string; + newUi?: boolean; } | { type: "one-click-login"; @@ -234,7 +235,8 @@ export async function dispatch( action.token, action.password, state, - update + update, + action.newUi ); case "one-click-login": return oneClickLogin( @@ -520,7 +522,8 @@ async function createNewUserWithPassword( token: string, password: string, state: AppState, - update: ZuUpdate + update: ZuUpdate, + newUi = false ): Promise { const crypto = await PCDCrypto.newInstance(); const { salt: newSalt, key: encryptionKey } = @@ -544,7 +547,13 @@ async function createNewUserWithPassword( ); if (newUserResult.success) { - return finishAccountCreation(newUserResult.value, state, update); + return finishAccountCreation( + newUserResult.value, + state, + update, + undefined, + newUi + ); } update({