From 0ee501fe9dbb649a38fb1916efb2793e57c1fd4c Mon Sep 17 00:00:00 2001 From: giancarlo Date: Sat, 27 Apr 2024 20:00:28 +0700 Subject: [PATCH] Improve CAPTCHA handling in signup methods This commit refines the usage of CAPTCHA in signup methods by storing the CAPTCHA token and instance, providing a reset function, and managing validation on server-side. A new context instance has been introduced to keep track of the CAPTCHA instance besides the token. Proper error reporting is now in place for CAPTCHA verification failure. --- .../auth/src/captcha/client/captcha-provider.tsx | 11 ++++++++++- .../src/captcha/client/captcha-token-setter.tsx | 13 +++++++++++-- .../auth/src/captcha/client/use-captcha-token.ts | 14 ++++++++++++-- .../auth/src/captcha/server/verify-captcha.tsx | 9 ++++++--- .../src/components/sign-up-methods-container.tsx | 2 +- 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/features/auth/src/captcha/client/captcha-provider.tsx b/packages/features/auth/src/captcha/client/captcha-provider.tsx index 3bfc70c64..8dcb769d0 100644 --- a/packages/features/auth/src/captcha/client/captcha-provider.tsx +++ b/packages/features/auth/src/captcha/client/captcha-provider.tsx @@ -2,22 +2,31 @@ import { createContext, useState } from 'react'; +import { TurnstileInstance } from '@marsidev/react-turnstile'; + export const Captcha = createContext<{ token: string; setToken: (token: string) => void; + instance: TurnstileInstance | null; + setInstance: (ref: TurnstileInstance) => void; }>({ token: '', + instance: null, setToken: (_: string) => { // do nothing return ''; }, + setInstance: () => { + // do nothing + }, }); export function CaptchaProvider(props: { children: React.ReactNode }) { const [token, setToken] = useState(''); + const [instance, setInstance] = useState(null); return ( - + {props.children} ); diff --git a/packages/features/auth/src/captcha/client/captcha-token-setter.tsx b/packages/features/auth/src/captcha/client/captcha-token-setter.tsx index bfb8f51de..237b4b2d1 100644 --- a/packages/features/auth/src/captcha/client/captcha-token-setter.tsx +++ b/packages/features/auth/src/captcha/client/captcha-token-setter.tsx @@ -10,7 +10,7 @@ export function CaptchaTokenSetter(props: { siteKey: string | undefined; options?: TurnstileProps; }) { - const { setToken } = useContext(Captcha); + const { setToken, setInstance } = useContext(Captcha); if (!props.siteKey) { return null; @@ -23,6 +23,15 @@ export function CaptchaTokenSetter(props: { }; return ( - + { + if (instance) { + setInstance(instance); + } + }} + siteKey={props.siteKey} + onSuccess={setToken} + {...options} + /> ); } diff --git a/packages/features/auth/src/captcha/client/use-captcha-token.ts b/packages/features/auth/src/captcha/client/use-captcha-token.ts index 461911ac7..a027dddd2 100644 --- a/packages/features/auth/src/captcha/client/use-captcha-token.ts +++ b/packages/features/auth/src/captcha/client/use-captcha-token.ts @@ -1,7 +1,12 @@ -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import { Captcha } from './captcha-provider'; +/** + * @name useCaptchaToken + * @description A hook to get the captcha token and reset function + * @returns The captcha token and reset function + */ export function useCaptchaToken() { const context = useContext(Captcha); @@ -9,5 +14,10 @@ export function useCaptchaToken() { throw new Error(`useCaptchaToken must be used within a CaptchaProvider`); } - return context.token; + return useMemo(() => { + return { + captchaToken: context.token, + resetCaptchaToken: () => context.instance?.reset(), + }; + }, [context]); } diff --git a/packages/features/auth/src/captcha/server/verify-captcha.tsx b/packages/features/auth/src/captcha/server/verify-captcha.tsx index bf306612c..e09dbb162 100644 --- a/packages/features/auth/src/captcha/server/verify-captcha.tsx +++ b/packages/features/auth/src/captcha/server/verify-captcha.tsx @@ -23,11 +23,14 @@ export async function verifyCaptchaToken(token: string) { const res = await fetch(verifyEndpoint, { method: 'POST', body: formData, - headers: { - 'content-type': 'application/x-www-form-urlencoded', - }, }); + if (!res.ok) { + console.error(`Captcha failed:`, res.statusText); + + throw new Error('Failed to verify CAPTCHA token'); + } + const data = await res.json(); if (!data.success) { diff --git a/packages/features/auth/src/components/sign-up-methods-container.tsx b/packages/features/auth/src/components/sign-up-methods-container.tsx index 0b024efe8..51f5bb54a 100644 --- a/packages/features/auth/src/components/sign-up-methods-container.tsx +++ b/packages/features/auth/src/components/sign-up-methods-container.tsx @@ -28,7 +28,7 @@ export function SignUpMethodsContainer(props: { inviteToken?: string; }) { const redirectUrl = getCallbackUrl(props); - const captchaToken = useCaptchaToken(); + const { captchaToken } = useCaptchaToken(); return ( <>