nextjs-turnstile 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/License ADDED
@@ -0,0 +1,19 @@
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # Next.js Turnstile CAPTCHA Package
2
+ ![npm](https://img.shields.io/npm/v/nextjs-turnstile)
3
+ ![License](https://img.shields.io/npm/l/nextjs-turnstile)
4
+ ![npm](https://img.shields.io/npm/dw/nextjs-turnstile)
5
+
6
+ This package provides components and utilities to integrate Cloudflare Turnstile CAPTCHA into your Next.js applications. It supports both implicit and explicit CAPTCHA modes.
7
+
8
+ You can find the document of Cloudflare Turnstile [here](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/).
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install nextjs-turnstile
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ### Components
19
+ **TurnstileImplicit:**
20
+
21
+ ```javascript
22
+ import React from 'react';
23
+ import { TurnstileImplicit, verifyTurnstile } from 'nextjs-turnstile';
24
+
25
+ export default function MyForm() {
26
+ const handleSubmit = async (e) => {
27
+ e.preventDefault();
28
+ const token1 = e.target['cf-turnstile-response-1'].value;
29
+ const token2 = e.target['cf-turnstile-response-2'].value;
30
+
31
+ // Verify the first CAPTCHA response
32
+ const success1 = await verifyTurnstile(token1);
33
+ if (!success1) {
34
+ alert('First CAPTCHA verification failed');
35
+ return;
36
+ }
37
+
38
+ // Verify the second CAPTCHA response
39
+ const success2 = await verifyTurnstile(token2);
40
+ if (!success2) {
41
+ alert('Second CAPTCHA verification failed');
42
+ return;
43
+ }
44
+ };
45
+
46
+ return (
47
+ <form onSubmit={handleSubmit}>
48
+ <h2>Turnstile Implicit CAPTCHA Example</h2>
49
+
50
+ {/* First CAPTCHA */}
51
+ <TurnstileImplicit
52
+ theme="dark"
53
+ size="normal"
54
+ responseFieldName="cf-turnstile-response-1"
55
+ />
56
+
57
+ {/* Second CAPTCHA */}
58
+ <TurnstileImplicit
59
+ theme="light"
60
+ size="compact"
61
+ responseFieldName="cf-turnstile-response-2"
62
+ />
63
+
64
+ <button type="submit">Submit</button>
65
+ </form>
66
+ );
67
+ }
68
+ ```
69
+
70
+ **TurnstileExplicit:**
71
+ ```javascript
72
+ import React from 'react';
73
+ import { TurnstileExplicit, verifyTurnstile } from 'nextjs-turnstile';
74
+
75
+ export default function MyForm() {
76
+ const handleSubmit = async (e) => {
77
+ e.preventDefault();
78
+ const token1 = e.target['cf-turnstile-response-3'].value;
79
+ const token2 = e.target['cf-turnstile-response-4'].value;
80
+
81
+ // Verify the first CAPTCHA response
82
+ const success1 = await verifyTurnstile(token1);
83
+ if (!success1) {
84
+ alert('First CAPTCHA verification failed');
85
+ return;
86
+ }
87
+
88
+ // Verify the second CAPTCHA response
89
+ const success2 = await verifyTurnstile(token2);
90
+ if (!success2) {
91
+ alert('Second CAPTCHA verification failed');
92
+ return;
93
+ }
94
+ };
95
+
96
+ return (
97
+ <form onSubmit={handleSubmit}>
98
+ <h2>Turnstile Explicit CAPTCHA Example</h2>
99
+
100
+ {/* Developers must place the divs in their HTML */}
101
+
102
+ {/* First CAPTCHA */}
103
+ <div id="cf-turnstile-response-3"></div>
104
+ <TurnstileExplicit
105
+ theme="dark"
106
+ size="normal"
107
+ responseFieldName="cf-turnstile-response-3"
108
+ />
109
+
110
+ {/* Second CAPTCHA */}
111
+ <div id="cf-turnstile-response-4"></div>
112
+ <TurnstileExplicit
113
+ theme="light"
114
+ size="compact"
115
+ responseFieldName="cf-turnstile-response-4"
116
+ />
117
+
118
+ <button type="submit">Submit</button>
119
+ </form>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ### Verification Utility
125
+ In your API routes:
126
+ ß
127
+ ```javascript
128
+ import { verifyTurnstile } from 'nextjs-turnstile';
129
+
130
+ export default async function handler(req, res) {
131
+ const { token } = req.body;
132
+
133
+ const success = await verifyTurnstile(token, false, req);
134
+ // const success = await verifyTurnstile(token, req.connection.remoteAddress);
135
+
136
+ if (success) {
137
+ return res.status(200).json({ success: true });
138
+ } else {
139
+ return res.status(400).json({ success: false, message: 'CAPTCHA verification failed' });
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Environment Variables
145
+ You need to add the following environment variables to your .env.local file:
146
+
147
+ ```plaintext
148
+ NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
149
+ TURNSTILE_SECRET_KEY=your_secret_key_here
150
+ ```
151
+
152
+ ## License
153
+ This project is licensed under the MIT License - see the [License](./License) file for details.
154
+
155
+
156
+ ## Author
157
+ Davod Mozafari - [Twitter](https://twitter.com/davodmozafari)
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "nextjs-turnstile",
3
+ "version": "0.0.1",
4
+ "description": "Integrate Cloudflare Turnstile CAPTCHA in Next.js applications",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "build": "next build",
8
+ "test": "jest"
9
+ },
10
+ "dependencies": {
11
+ "node-fetch": "^3.3.0"
12
+ },
13
+ "peerDependencies": {
14
+ "react": "^18.2.0",
15
+ "react-dom": "^18.2.0",
16
+ "next": "^12.0.0"
17
+ },
18
+ "author": "Davod Mozafari",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/davodm/nextjs-turnstile.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/davodm/nextjs-turnstile/issues"
26
+ },
27
+ "homepage": "https://github.com/davodm/nextjs-turnstile",
28
+ "keywords": [
29
+ "nextjs",
30
+ "cloudflare",
31
+ "turnstile",
32
+ "captcha"
33
+ ]
34
+ }
@@ -0,0 +1,73 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ let explicitFields = []; // Array to track the explicit CAPTCHA fields
4
+
5
+ /**
6
+ * TurnstileExplicit component registers and initializes the Cloudflare Turnstile CAPTCHA in explicit mode.
7
+ *
8
+ * This component registers CAPTCHA instances, which must be manually initialized using the provided
9
+ * response field name as the ID for the corresponding `<div>` element. The initialization script will
10
+ * render the CAPTCHA based on the specified theme and size.
11
+ *
12
+ * @component
13
+ * @param {string} [theme='light'] - The theme of the CAPTCHA widget. Options are 'light' or 'dark'.
14
+ * @param {string} [size='normal'] - The size of the CAPTCHA widget. Options are 'normal' or 'compact'.
15
+ * @param {string} [responseFieldName='cf-turnstile-response'] - The name of the hidden input field and the ID of the `<div>` that will contain the CAPTCHA widget.
16
+ * @param {string} [siteKey=process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY] - The site key for Turnstile CAPTCHA. Defaults to the value in the environment variables.
17
+ * @returns {null} No visible component is rendered, only registers the field for later initialization.
18
+ */
19
+ const TurnstileExplicit = ({
20
+ theme = "light",
21
+ size = "normal",
22
+ responseFieldName = "cf-turnstile-response",
23
+ }) => {
24
+ const siteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
25
+ const JSUrl = "https://challenges.cloudflare.com/turnstile/v0/api.js?onload=turnstileReady";
26
+
27
+ useEffect(() => {
28
+ // Register the field for later initialization
29
+ // Check if the field is already registered
30
+ if (explicitFields.some((field) => field.fieldName === responseFieldName)) {
31
+ return;
32
+ }
33
+ explicitFields.push({
34
+ fieldName: responseFieldName,
35
+ size,
36
+ theme,
37
+ });
38
+
39
+ return () => {
40
+ // Cleanup: Remove the field if the component unmounts
41
+ explicitFields = explicitFields.filter(
42
+ (field) => field.fieldName !== responseFieldName
43
+ );
44
+ };
45
+ }, [responseFieldName, size, theme]);
46
+
47
+ useEffect(() => {
48
+ // Ensure the Turnstile script is loaded only once
49
+ if (!document.querySelector('script[src="' + JSUrl + '"]')) {
50
+ const script = document.createElement("script");
51
+ script.src = JSUrl;
52
+ script.async = true;
53
+ document.body.appendChild(script);
54
+
55
+ script.onload = () => {
56
+ window.turnstileReady = function () {
57
+ explicitFields.forEach(function (field) {
58
+ window.turnstile.render(`#${field.fieldName}`, {
59
+ sitekey: siteKey,
60
+ "response-field-name": field.fieldName,
61
+ size: field.size,
62
+ theme: field.theme,
63
+ });
64
+ });
65
+ };
66
+ };
67
+ }
68
+ }, []);
69
+
70
+ return null; // No need to render anything, just register the field
71
+ };
72
+
73
+ export default TurnstileExplicit;
@@ -0,0 +1,47 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ /**
4
+ * TurnstileImplicit component renders the Cloudflare Turnstile CAPTCHA in implicit mode.
5
+ *
6
+ * This component automatically loads the Turnstile script and renders the CAPTCHA based on the specified
7
+ * theme, size, and response field name.
8
+ *
9
+ * @component
10
+ * @param {string} [theme='light'] - The theme of the CAPTCHA widget. Options are 'light' or 'dark'.
11
+ * @param {string} [size='normal'] - The size of the CAPTCHA widget. Options are 'normal' or 'compact'.
12
+ * @param {string} [responseFieldName='cf-turnstile-response'] - The name of the hidden input field that stores the CAPTCHA response token.
13
+ * @returns {JSX.Element} The TurnstileImplicit component
14
+ */
15
+ const TurnstileImplicit = ({ theme = 'light', size = 'normal', responseFieldName = 'cf-turnstile-response' }) => {
16
+ const siteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
17
+ const JSUrl="https://challenges.cloudflare.com/turnstile/v0/api.js";
18
+
19
+ useEffect(() => {
20
+ // Load the Turnstile script only once
21
+ if (!document.querySelector('script[src="' + JSUrl + '"]')) {
22
+ const script = document.createElement('script');
23
+ script.src = JSUrl;
24
+ script.async = true;
25
+ document.body.appendChild(script);
26
+
27
+ return () => {
28
+ document.body.removeChild(script);
29
+ };
30
+ }
31
+ }, []);
32
+
33
+ return (
34
+ <>
35
+ <div
36
+ className="cf-turnstile"
37
+ data-sitekey={siteKey}
38
+ data-theme={theme}
39
+ data-size={size}
40
+ data-response-field={responseFieldName}
41
+ >
42
+ </div>
43
+ </>
44
+ );
45
+ };
46
+
47
+ export default TurnstileImplicit;
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as TurnstileImplicit } from './components/TurnstileImplicit';
2
+ export { default as TurnstileExplicit } from './components/TurnstileExplicit';
3
+ export { verifyTurnstile } from './utils/TurnstileVerify';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Verifies the Cloudflare Turnstile CAPTCHA response token with the Turnstile API.
3
+ *
4
+ * @async
5
+ * @function verifyTurnstile
6
+ * @param {string} token - The CAPTCHA response token provided by the client-side Turnstile widget.
7
+ * @param {string} [userIP] - The IP address of the user. If not provided, the function will attempt to extract it from the request headers.
8
+ * @param {Object} [req] - The request object (optional). If provided, it is used to extract the user's IP address from headers like 'cf-connecting-ip' or 'x-forwarded-for'.
9
+ * @returns {Promise<boolean>} - Returns `true` if the CAPTCHA verification was successful, otherwise `false`.
10
+ *
11
+ * @throws {Error} If the request to the Turnstile API fails or if the CAPTCHA verification fails, an error is thrown with a descriptive message.
12
+ */
13
+ export const verifyTurnstile = async (token, userIP, req=undefined) => {
14
+ const secretKey = process.env.TURNSTILE_SECRET_KEY;
15
+
16
+ // Fetch the user's IP address if not provided
17
+ let userIP = ipAddress;
18
+ if (!userIP && req?.headers) {
19
+ userIP = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
20
+ }
21
+
22
+ try {
23
+ const response = await fetch(`https://challenges.cloudflare.com/turnstile/v0/siteverify`, {
24
+ method: 'POST',
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ },
28
+ body: JSON.stringify({
29
+ secret: secretKey,
30
+ response: token,
31
+ remoteip: userIP,
32
+ }),
33
+ });
34
+
35
+ // Check for a successful response
36
+ if (!response.ok) {
37
+ throw new Error(`Verification request failed with status ${response.status}`);
38
+ }
39
+
40
+ const data = await response.json();
41
+
42
+ // Check if the success field is true
43
+ if (!data.success) {
44
+ throw new Error(`Verification failed: ${data['error-codes'] ? data['error-codes'].join(', ') : 'Unknown error'}`);
45
+ }
46
+
47
+ return true;
48
+ } catch (error) {
49
+ console.error('Error verifying Turnstile CAPTCHA:', error.message);
50
+ return false;
51
+ }
52
+ };