Skip to content

Commit e1f3a98

Browse files
Merge pull request #666 from aXenDeveloper/captcha
feat: Add captcha
2 parents bcf7bb8 + 9bfa6f7 commit e1f3a98

File tree

37 files changed

+775
-374
lines changed

37 files changed

+775
-374
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
title: Captcha
3+
description: Protect your forms and API call with captcha validation.
4+
---
5+
6+
## Support
7+
8+
VitNode supports multiple captcha providers. You can choose the one that fits your needs. Currently, we support:
9+
10+
<Cards>
11+
<Card
12+
title="Cloudflare Turnstile"
13+
description="By Cloudflare"
14+
href="/docs/guides/captcha/cloudflare"
15+
/>
16+
<Card
17+
title="reCAPTCHA v3"
18+
description="By Google"
19+
href="/docs/guides/captcha/recaptcha"
20+
/>
21+
</Cards>
22+
23+
If you need more providers, feel free to open a **Feature Request** on our [GitHub repository](https://github.com/aXenDeveloper/vitnode/issues) :)
24+
25+
## Usage
26+
27+
In this example, we will show you how to use captcha in your forms. We will use the `AutoForm` component to render the form and handle the captcha validation.
28+
29+
import { Step, Steps } from 'fumadocs-ui/components/steps';
30+
31+
<Steps>
32+
<Step>
33+
34+
### Activate captcha in route
35+
36+
Add `withCaptcha` to your route config to enable captcha validation for this route.
37+
38+
```ts title="plugins/{plugin_name}/src/routes/example.ts"
39+
import { buildRoute } from '@vitnode/core/api/lib/route';
40+
41+
export const exampleRoute = buildRoute({
42+
...CONFIG_PLUGIN,
43+
route: {
44+
method: 'post',
45+
description: 'Create a new user',
46+
path: '/sign_up',
47+
withCaptcha: true, // [!code ++]
48+
},
49+
handler: async c => {},
50+
});
51+
```
52+
53+
</Step>
54+
<Step>
55+
56+
### Get config from middleware API
57+
58+
Get captcha config from middleware API in your view and pass it to your `'use client';` component.
59+
60+
```tsx title="plugins/{plugin_name}/src/app/sing_up/page.tsx"
61+
import { getMiddlewareApi } from '@vitnode/core/lib/api/get-middleware-api'; // [!code ++]
62+
63+
export const SignUpView = async () => {
64+
const { captcha } = await getMiddlewareApi(); // [!code ++]
65+
66+
return <FormSignUp captcha={captcha} />;
67+
};
68+
```
69+
70+
</Step>
71+
<Step>
72+
73+
### Use in form
74+
75+
Get the `captcha` config from the props and pass it to the `AutoForm` component. This will render the captcha widget in your form.
76+
77+
```tsx title="plugins/{plugin_name}/src/components/form/sign-up/sign-up.tsx"
78+
'use client';
79+
80+
import { AutoForm } from '@vitnode/core/components/form/auto-form';
81+
82+
export const FormSignUp = ({
83+
captcha, // [!code ++]
84+
}: {
85+
captcha: z.infer<typeof routeMiddlewareSchema>['captcha']; // [!code ++]
86+
}) => {
87+
return (
88+
<AutoForm<typeof formSchema>
89+
captcha={captcha} // [!code ++]
90+
fields={[]}
91+
formSchema={formSchema}
92+
/>
93+
);
94+
};
95+
```
96+
97+
<Card
98+
title="AutoForm"
99+
description="Lear more about the AutoForm component and how to use it."
100+
href="/docs/ui/auto-form"
101+
/>
102+
103+
</Step>
104+
<Step>
105+
106+
### Submit form with captcha
107+
108+
In your form submission handler, you can get the `captchaToken` from the form submission context and pass it to your mutation API.
109+
110+
```tsx title="plugins/{plugin_name}/src/components/form/sign-up/sign-up.tsx"
111+
'use client';
112+
113+
import {
114+
AutoForm,
115+
type AutoFormOnSubmit, // [!code ++]
116+
} from '@vitnode/core/components/form/auto-form';
117+
118+
export const FormSignUp = ({
119+
captcha,
120+
}: {
121+
captcha: z.infer<typeof routeMiddlewareSchema>['captcha'];
122+
}) => {
123+
const onSubmit: AutoFormOnSubmit<typeof formSchema> = async (
124+
values,
125+
form,
126+
{ captchaToken }, // [!code ++]
127+
) => {
128+
// Call your mutation API with captcha token
129+
await mutationApi({
130+
...values,
131+
captchaToken, // [!code ++]
132+
});
133+
134+
// Handle success or error
135+
};
136+
137+
return (
138+
<AutoForm<typeof formSchema>
139+
captcha={captcha}
140+
fields={[]}
141+
onSubmit={onSubmit} // [!code ++]
142+
formSchema={formSchema}
143+
/>
144+
);
145+
};
146+
```
147+
148+
Next, you need to set `captchaToken` in your mutation API call. This token is provided by the `AutoForm` component when the form is submitted.
149+
150+
```tsx title="plugins/{plugin_name}/src/components/form/sign-up/mutation-api.ts"
151+
'use server';
152+
153+
import type { z } from 'zod';
154+
155+
import { fetcher } from '@vitnode/core/lib/fetcher';
156+
157+
export const mutationApi = async ({
158+
captchaToken, // [!code ++]
159+
...input
160+
// [!code ++]
161+
}: z.infer<typeof zodSignUpSchema> & { captchaToken }) => {
162+
const res = await fetcher(usersModule, {
163+
path: '/sign_up',
164+
method: 'post',
165+
module: 'users',
166+
captchaToken, // [!code ++]
167+
args: {
168+
body: input,
169+
},
170+
});
171+
172+
if (res.status !== 201) {
173+
return { error: await res.text() };
174+
}
175+
176+
const data = await res.json();
177+
178+
return { data };
179+
};
180+
```
181+
182+
</Step>
183+
</Steps>
184+
185+
## Custom Usage
186+
187+
If you want to use captcha in your custom form or somewhere else, follow these steps.
188+
189+
<Steps>
190+
<Step>
191+
192+
### Activate captcha in route
193+
194+
```ts title="plugins/{plugin_name}/src/routes/example.ts"
195+
import { buildRoute } from '@vitnode/core/api/lib/route';
196+
197+
export const exampleRoute = buildRoute({
198+
...CONFIG_PLUGIN,
199+
route: {
200+
method: 'post',
201+
description: 'Create a new user',
202+
path: '/sign_up',
203+
withCaptcha: true, // [!code ++]
204+
},
205+
handler: async c => {},
206+
});
207+
```
208+
209+
</Step>
210+
<Step>
211+
212+
### Get config from middleware API
213+
214+
```tsx title="plugins/{plugin_name}/src/app/sing_up/page.tsx"
215+
import { getMiddlewareApi } from '@vitnode/core/lib/api/get-middleware-api'; // [!code ++]
216+
217+
export const SignUpView = async () => {
218+
const { captcha } = await getMiddlewareApi(); // [!code ++]
219+
220+
return <FormSignUp captcha={captcha} />;
221+
};
222+
```
223+
224+
</Step>
225+
<Step>
226+
227+
### Use `useCaptcha` hook
228+
229+
Inside your client component, use the `useCaptcha` hook to handle captcha rendering and validation. Remember to add `div` with `id="vitnode_captcha"` where you want the captcha widget to appear.
230+
231+
```tsx title="plugins/{plugin_name}/src/components/form/sign-up/sign-up.tsx"
232+
'use client';
233+
234+
import { AutoForm } from '@vitnode/core/components/form/auto-form';
235+
236+
export const FormSignUp = ({
237+
captcha, // [!code ++]
238+
}: {
239+
captcha: z.infer<typeof routeMiddlewareSchema>['captcha']; // [!code ++]
240+
}) => {
241+
// [!code ++]
242+
const { isReady, getToken, onReset } = useCaptcha(captcha);
243+
244+
const onSubmit = async () => {
245+
await mutationApi({
246+
// ...other values,
247+
captchaToken: await getToken(), // [!code ++]
248+
});
249+
250+
// Handle success or error
251+
// [!code ++]
252+
onReset(); // Reset captcha after submission
253+
};
254+
255+
return (
256+
<form onSubmit={onSubmit}>
257+
{/* Render captcha widget */}
258+
{/* [!code ++] */}
259+
<div id="vitnode_captcha" />
260+
261+
<Button disabled={!isReady}>Submit</Button>
262+
</form>
263+
);
264+
};
265+
```
266+
267+
</Step>
268+
<Step>
269+
270+
### Submit form with captcha
271+
272+
```tsx title="plugins/{plugin_name}/src/components/form/sign-up/mutation-api.ts"
273+
'use server';
274+
275+
import type { z } from 'zod';
276+
277+
import { fetcher } from '@vitnode/core/lib/fetcher';
278+
279+
export const mutationApi = async ({
280+
captchaToken, // [!code ++]
281+
}: {
282+
// [!code ++]
283+
captchaToken;
284+
}) => {
285+
await fetcher(usersModule, {
286+
path: '/test',
287+
method: 'post',
288+
module: 'blog',
289+
captchaToken, // [!code ++]
290+
});
291+
};
292+
```
293+
294+
</Step>
295+
</Steps>

apps/docs/content/docs/dev/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ npx create-vitnode-app@canary
2727
```
2828

2929
</Tabs>
30+
31+
## Why VitNode?
32+
33+
something here

apps/docs/content/docs/dev/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"---Framework---",
2222
"config",
2323
"logging",
24+
"captcha",
2425
"---Advanced---",
2526
"..."
2627
]

apps/docs/content/docs/guides/captcha/cloudflare.mdx

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ Follow the instructions to add your domain. If you have any trouble, you can che
3131

3232
Go to the `Turnstile` section and create a new widget.
3333

34-
// import homeTurnstile from '@/assets/guides/captcha/cloudflare.png';
34+
import homeTurnstile from './cloudflare.png';
3535

36-
{/* <ImgDocs src={homeTurnstile} alt="Cloudflare Turnstile Home" /> */}
36+
import { ImgDocs } from '@/components/fumadocs/img';
37+
38+
<ImgDocs src={homeTurnstile} alt="Cloudflare Turnstile Home" />
3739

3840
</Step>
3941

@@ -62,8 +64,8 @@ After you create the widget, you will get the `Site Key` and `Secret Key`. Save
6264
Add the keys to your `.env` file.
6365

6466
```bash
65-
CAPTCHA_SECRET_KEY=XXX
66-
CAPTCHA_SITE_KEY=XXX
67+
CLOUDFLARE_TURNSTILE_SITE_KEY=XXX
68+
CLOUDFLARE_TURNSTILE_SECRET_KEY=XXX
6769
```
6870

6971
</Step>
@@ -72,35 +74,17 @@ CAPTCHA_SITE_KEY=XXX
7274

7375
## Provide keys to VitNode
7476

75-
Edit your `app.module.ts` file and add `captcha` object to the `VitNodeModule` configuration.
76-
77-
```ts
78-
@Module({
79-
imports: [
80-
VitNodeCoreModule.register({
81-
database: {
82-
config: DATABASE_ENVS,
83-
schemaDatabase,
84-
},
85-
// [!code ++]
86-
captcha: {
87-
// [!code ++]
88-
type: 'cloudflare_turnstile',
89-
// [!code ++]
90-
site_key: process.env.CAPTCHA_SITE_KEY,
91-
// [!code ++]
92-
secret_key: process.env.CAPTCHA_SECRET_KEY,
93-
// [!code ++]
94-
},
95-
}),
96-
DatabaseModule,
97-
PluginsModule,
98-
CacheModule.register({
99-
isGlobal: true,
100-
}),
101-
],
102-
})
103-
export class AppModule {}
77+
```ts title="src/vitnode.api.config.ts"
78+
import { buildApiConfig } from '@vitnode/core/vitnode.config';
79+
80+
export const vitNodeApiConfig = buildApiConfig({
81+
// [!code ++:5]
82+
captcha: {
83+
type: 'cloudflare_turnstile',
84+
siteKey: process.env.CLOUDFLARE_TURNSTILE_SITE_KEY,
85+
secretKey: process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY,
86+
},
87+
});
10488
```
10589

10690
</Step>
35.9 KB
Loading

0 commit comments

Comments
 (0)