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.
This commit is contained in:
giancarlo
2024-04-27 20:00:28 +07:00
parent 84cacb81c2
commit 0ee501fe9d
5 changed files with 40 additions and 9 deletions

View File

@@ -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<string>('');
const [instance, setInstance] = useState<TurnstileInstance | null>(null);
return (
<Captcha.Provider value={{ token, setToken }}>
<Captcha.Provider value={{ token, setToken, instance, setInstance }}>
{props.children}
</Captcha.Provider>
);

View File

@@ -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 (
<Turnstile siteKey={props.siteKey} onSuccess={setToken} {...options} />
<Turnstile
ref={(instance) => {
if (instance) {
setInstance(instance);
}
}}
siteKey={props.siteKey}
onSuccess={setToken}
{...options}
/>
);
}

View File

@@ -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]);
}

View File

@@ -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) {

View File

@@ -28,7 +28,7 @@ export function SignUpMethodsContainer(props: {
inviteToken?: string;
}) {
const redirectUrl = getCallbackUrl(props);
const captchaToken = useCaptchaToken();
const { captchaToken } = useCaptchaToken();
return (
<>