B2B-88: add starter kit structure and elements
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useCallback, useRef, 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 instanceRef = useRef<TurnstileInstance | null>(null);
|
||||
|
||||
const setInstance = useCallback((ref: TurnstileInstance) => {
|
||||
instanceRef.current = ref;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Captcha.Provider
|
||||
value={{ token, setToken, instance: instanceRef.current, setInstance }}
|
||||
>
|
||||
{props.children}
|
||||
</Captcha.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { Turnstile, TurnstileProps } from '@marsidev/react-turnstile';
|
||||
|
||||
import { Captcha } from './captcha-provider';
|
||||
|
||||
export function CaptchaTokenSetter(props: {
|
||||
siteKey: string | undefined;
|
||||
options?: TurnstileProps;
|
||||
}) {
|
||||
const { setToken, setInstance } = useContext(Captcha);
|
||||
|
||||
if (!props.siteKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const options = props.options ?? {
|
||||
options: {
|
||||
size: 'invisible',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Turnstile
|
||||
ref={(instance) => {
|
||||
if (instance) {
|
||||
setInstance(instance);
|
||||
}
|
||||
}}
|
||||
siteKey={props.siteKey}
|
||||
onSuccess={setToken}
|
||||
{...options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
3
packages/features/auth/src/captcha/client/index.ts
Normal file
3
packages/features/auth/src/captcha/client/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './captcha-token-setter';
|
||||
export * from './use-captcha-token';
|
||||
export * from './captcha-provider';
|
||||
@@ -0,0 +1,23 @@
|
||||
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);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`useCaptchaToken must be used within a CaptchaProvider`);
|
||||
}
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
captchaToken: context.token,
|
||||
resetCaptchaToken: () => context.instance?.reset(),
|
||||
};
|
||||
}, [context]);
|
||||
}
|
||||
1
packages/features/auth/src/captcha/server/index.ts
Normal file
1
packages/features/auth/src/captcha/server/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './verify-captcha';
|
||||
39
packages/features/auth/src/captcha/server/verify-captcha.tsx
Normal file
39
packages/features/auth/src/captcha/server/verify-captcha.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'server-only';
|
||||
|
||||
const verifyEndpoint =
|
||||
'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
|
||||
const CAPTCHA_SECRET_TOKEN = process.env.CAPTCHA_SECRET_TOKEN;
|
||||
|
||||
/**
|
||||
* @name verifyCaptchaToken
|
||||
* @description Verify the CAPTCHA token with the CAPTCHA service
|
||||
* @param token - The CAPTCHA token to verify
|
||||
*/
|
||||
export async function verifyCaptchaToken(token: string) {
|
||||
if (!CAPTCHA_SECRET_TOKEN) {
|
||||
throw new Error('CAPTCHA_SECRET_TOKEN is not set');
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('secret', CAPTCHA_SECRET_TOKEN);
|
||||
formData.append('response', token);
|
||||
|
||||
const res = await fetch(verifyEndpoint, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
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) {
|
||||
throw new Error('Invalid CAPTCHA token');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user