From 01925952b2cfa9c3e48d25f440bb3b61c503f373 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 8 Jun 2024 10:38:45 +0530 Subject: [PATCH] Extract components --- .../accounts/src/pages/passkeys/verify.tsx | 219 ++++++++++-------- 1 file changed, 120 insertions(+), 99 deletions(-) diff --git a/web/apps/accounts/src/pages/passkeys/verify.tsx b/web/apps/accounts/src/pages/passkeys/verify.tsx index 3ad3e6e6ae..5b415695e9 100644 --- a/web/apps/accounts/src/pages/passkeys/verify.tsx +++ b/web/apps/accounts/src/pages/passkeys/verify.tsx @@ -24,13 +24,21 @@ import { } from "services/passkey"; const Page = () => { - const [errored, setErrored] = useState(false); + /** + * The state of our component as we go through the passkey authentication + * flow. + * + * To avoid confusion with useState, we call it status instead. */ + type Status = + | "loading" /* Can happen multiple times in the flow */ + | "unknownRedirect" /* Unrecoverable error */ + | "failed" /* Generic error */ + | "waitingForUser"; /* ...to authenticate with their passkey */ - const [invalidInfo, setInvalidInfo] = useState(false); + const [status, setStatus] = useState("loading"); - const [loading, setLoading] = useState(true); - - const init = async () => { + /** (re)start the authentication flow */ + const authenticate = async () => { const searchParams = new URLSearchParams(window.location.search); // Extract redirect from the query params. @@ -41,8 +49,7 @@ const Page = () => { // "login" URL error to the user. if (!redirectURL || !isWhitelistedRedirect(redirectURL)) { log.error(`Redirect URL '${redirectURL}' is not whitelisted`); - setInvalidInfo(true); - setLoading(false); + setStatus("unknownRedirect"); return; } @@ -69,18 +76,17 @@ const Page = () => { // get passkeySessionID from the query params const passkeySessionID = searchParams.get("passkeySessionID") as string; - setLoading(true); + setStatus("loading"); let beginData: BeginPasskeyAuthenticationResponse; try { beginData = await beginAuthentication(passkeySessionID); + setStatus("waitingForUser"); } catch (e) { log.error("Couldn't begin passkey authentication", e); - setErrored(true); + setStatus("failed"); return; - } finally { - setLoading(false); } let credential: Credential | null = null; @@ -105,11 +111,11 @@ const Page = () => { if (!isWebAuthnSupported()) { alert("WebAuthn is not supported in this browser"); } - setErrored(true); + setStatus("failed"); return; } - setLoading(true); + setStatus("loading"); let finishData; @@ -121,11 +127,13 @@ const Page = () => { ); } catch (e) { log.error("Couldn't finish passkey authentication", e); - setErrored(true); - setLoading(false); + setStatus("failed"); return; } + // Conceptually we can `setStatus("done")` at this point, but we'll + // leave this page anyway, so no need to tickle React. + const encodedResponse = _sodium.to_base64(JSON.stringify(finishData)); // TODO-PK: Shouldn't this be URL encoded? @@ -182,98 +190,113 @@ const Page = () => { }; useEffect(() => { - init(); + void authenticate(); }, []); - if (loading) { - return ( - - - - ); - } + const components: Record = { + loading: , + unknownRedirect: , + failed: void authenticate()} />, + waitingForUser: , + }; - if (invalidInfo) { - return ( - - - - - - {t("PASSKEY_LOGIN_FAILED")} - - - {t("PASSKEY_LOGIN_URL_INVALID")} - - - + return components[status]; +}; + +export default Page; + +const Loading: React.FC = () => { + return ( + + + + ); +}; + +const UnknownRedirect: React.FC = () => { + return ( + + + + + + {t("PASSKEY_LOGIN_FAILED")} + + + {t("PASSKEY_LOGIN_URL_INVALID")} + + - ); - } + + ); +}; - if (errored) { - return ( - - - void; +} + +const Failed: React.FC = ({ onRetry }) => { + return ( + + + + + + {t("PASSKEY_LOGIN_FAILED")} + + + {t("PASSKEY_LOGIN_ERRORED")} + + - - - {t("PASSKEY_LOGIN_FAILED")} - - - {t("PASSKEY_LOGIN_ERRORED")} - - { - setErrored(false); - init(); - }} - fullWidth - style={{ - marginTop: "1rem", - }} - color="primary" - type="button" - variant="contained" - > - {t("TRY_AGAIN")} - - - {t("RECOVER_TWO_FACTOR")} - - - + {t("TRY_AGAIN")} + + + {t("RECOVER_TWO_FACTOR")} + + - ); - } + + ); +}; +const WaitingForUser: React.FC = () => { return ( <> { ); }; - -export default Page;