Skip to content

Commit 74a25e8

Browse files
committed
Update captcha provider and related variables
1 parent a38269c commit 74a25e8

File tree

6 files changed

+119
-96
lines changed

6 files changed

+119
-96
lines changed

README.md

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ If you want to run this application without installing Node.js or other tools, y
2323
```
2424
docker run \
2525
-e VERIFIED_ROLE_ID=x \
26+
-e CAPTCHA_PROVIDER=x \
2627
-e REQUIRE_VERIFIED_EMAIL=x \
2728
-e SERVER_ID=x \
2829
-e RECAPTCHA_SECRET=x \
2930
-e RECAPTCHA_SITEKEY=x \
31+
-e HCAPTCHA_SITEKEY=x \
32+
-e HCAPTCHA_SECRET=x \
33+
-e TURNSTILE_SITEKEY=x \
34+
-e TURNSTILE_SECRET=x \
3035
-e CALLBACK_URL=x \
3136
-e CLIENT_SECRET=x \
3237
-e TOKEN=x \
@@ -51,8 +56,16 @@ ghcr.io/ckt1031/discord-captcha-site
5156

5257
> Register new Recaptcha v2 API key [here](https://www.google.com/recaptcha/admin/create)
5358
59+
- `CAPTCHA_PROVIDER` - The captcha provider to use (default: `recaptcha`), available options: `recaptcha`, `hcaptcha`
60+
61+
> Above options are only required if you are using specified captcha provider
62+
5463
- `RECAPTCHA_SITEKEY` - The site key of your Google reCAPTCHA v2 API key (Settings -> reCAPTCHA keys -> Copy Site key)
5564
- `RECAPTCHA_SECRET` - The secret key of your Google reCAPTCHA v2 API key (Settings -> reCAPTCHA keys -> Copy Secret key)
65+
- `HCAPTCHA_SITEKEY` - The site key of your hCaptcha API key (Site -> Site key)
66+
- `HCAPTCHA_SECRET` - The secret key of your hCaptcha API key (Settings -> Secret key)
67+
- `TURNSTILE_SITEKEY` - The site key of your Turnstile API key (Turnstile -> Site -> Site key)
68+
- `TURNSTILE_SECRET` - The secret key of your Turnstile API key (Turnstile -> Site -> Secret key)
5669

5770
## Usage
5871

@@ -76,36 +89,3 @@ ghcr.io/ckt1031/discord-captcha-site
7689
## Captcha Verification Page
7790

7891
![Captcha Verification Page](./screenshots/verify-page.png)
79-
80-
## FAQ
81-
82-
1. **What is ZodError?**
83-
84-
```bash
85-
ZodError: [
86-
{
87-
"code": "invalid_type",
88-
"expected": "string",
89-
"received": "undefined",
90-
"path": [
91-
"REQUIRE_VERIFIED_EMAIL"
92-
],
93-
"message": "Required"
94-
},
95-
{
96-
"code": "invalid_type",
97-
"expected": "string",
98-
"received": "undefined",
99-
"path": [
100-
"VERIFIED_ROLE_ID"
101-
],
102-
"message": "Required"
103-
}
104-
]
105-
```
106-
107-
This error is caused by missing environment variables. Make sure you have set all the environment variables correctly and **add environment variables following to the `path` in the error message.**
108-
109-
From the example above, you need to add `REQUIRE_VERIFIED_EMAIL` and `VERIFIED_ROLE_ID` to the environment variables.
110-
111-
Please follow the [Environment Variables](#environment-variables) section for more information.

html/captcha.html

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@
44
<meta
55
name="viewport"
66
content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
7-
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
7+
<script src="<%= captcha_provider_script %>" async defer></script>
88
<link rel="stylesheet" href="../style.css" />
99
</head>
1010

1111
<body>
1212
<div class="main">
1313
<h1>Verification</h1>
1414
<h4>
15-
Please solve the reCAPTCHA following below to make sure you are not a
15+
Please solve the <%= captcha_provider %> following below to make sure you are not a
1616
robot.
1717
</h4>
18-
<form action="/verify/solve" method="POST" id="recaptchaForm">
18+
<form action="/verify/solve" method="POST" id="captcha_verification">
1919
<center>
2020
<div
21-
id="recaptcha_box"
22-
class="g-recaptcha"
23-
data-sitekey="<%= recaptcha_sitekey %>"
21+
class="g-recaptcha h-captcha cf-turnstile"
22+
data-sitekey="<%= captcha_sitekey %>"
2423
data-callback="onPassed"
2524
data-expired-callback="onExpired"></div>
2625
</center>
@@ -31,7 +30,7 @@ <h4>
3130
</div>
3231
<script>
3332
function onPassed() {
34-
document.getElementById('recaptchaForm').submit();
33+
document.getElementById('captcha_verification').submit();
3534
}
3635
function onExpired() {
3736
location.reload();

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ client.on('guildMemberAdd', member => {
4242
const embed = new EmbedBuilder()
4343
.setTitle('Verification')
4444
.setDescription(
45-
`Please solve reCAPTCHA here:${process.env.CALLBACK_URL}\nBefore accessing to the server!`,
45+
`Please solve captcha here:${process.env.CALLBACK_URL}\nBefore accessing to the server!`,
4646
);
4747

4848
member.send({ embeds: [embed] });

src/server.js

Lines changed: 87 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
const { resolve } = require('node:path');
44

5-
const { EmbedBuilder } = require('discord.js');
5+
// eslint-disable-next-line no-unused-vars
6+
const { EmbedBuilder, GuildMember } = require('discord.js');
67

78
const qs = require('qs');
89
// eslint-disable-next-line no-unused-vars
@@ -43,7 +44,7 @@ server.use(
4344
resave: true,
4445
proxy: true,
4546
saveUninitialized: true,
46-
cookie: { maxAge: 60000 * 60 * 12, secure: true },
47+
cookie: { maxAge: 60000 * 60 * 12, secure: "auto" },
4748
}),
4849
);
4950

@@ -67,80 +68,115 @@ server.get('/verify', async (req, res) => {
6768
}),
6869
};
6970

70-
const response = await axios(oauthApiOptions);
71+
try {
72+
const response = await axios(oauthApiOptions);
7173

74+
const oauth_parsed = response.data;
7275

73-
const oauth_parsed = response.data;
76+
/** @type {Axios.AxiosRequestConfig} */
77+
const apiUserOptions = {
78+
method: 'GET',
79+
url: 'https://discord.com/api/users/@me',
80+
headers: { authorization: `Bearer ${oauth_parsed.access_token}` },
81+
};
7482

75-
/** @type {Axios.AxiosRequestConfig} */
76-
const apiUserOptions = {
77-
method: 'GET',
78-
url: 'https://discord.com/api/users/@me',
79-
headers: { authorization: `Bearer ${oauth_parsed.access_token}` },
80-
};
83+
const response2 = await axios(apiUserOptions);
8184

82-
const response2 = await axios(apiUserOptions);
85+
const parsed = response2.data;
8386

84-
const parsed = response2.data;
87+
let fetchedGuild = client.guilds.cache.get(process.env.SERVER_ID);
8588

86-
const fetchedGuild = client.guilds.cache.get(process.env.SERVER_ID);
89+
if (!fetchedGuild) {
90+
fetchedGuild = await client.guilds.fetch(process.env.SERVER_ID);
91+
}
8792

88-
const userfetch = await client.users.fetch(parsed.id);
93+
/** @type {GuildMember} */
94+
let userfetch;
8995

90-
await fetchedGuild?.members.add(userfetch, {
91-
accessToken: oauth_parsed.access_token,
92-
});
96+
try {
97+
userfetch = await fetchedGuild.members.fetch(parsed.id);
98+
} catch {
99+
const userObject = await client.users.fetch(parsed.id);
100+
userfetch = await fetchedGuild.members.add(userObject, {
101+
accessToken: oauth_parsed.access_token,
102+
});
103+
}
93104

94-
const userguild = await fetchedGuild?.members.fetch(userfetch);
105+
if (userfetch.roles.cache.has(process.env.VERIFIED_ROLE_ID)) {
106+
res.render(resolve('./html/success.html'), {
107+
messageText: 'You already verified!',
108+
});
95109

96-
if (!userguild) {
97-
res.render(resolve('./html/error.html'), {
98-
messageText: 'You already verified!',
99-
});
110+
return;
111+
}
100112

101-
return;
113+
req.session.verify_userid = userfetch.id;
114+
115+
if (process.env.REQUIRE_VERIFIED_EMAIL === 'true' || parsed.verified) {
116+
req.session.verify_status = 'waiting_captcha';
117+
118+
let sitekey = process.env.RECAPTCHA_SITEKEY;
119+
let captcha_provider_script = 'https://www.google.com/recaptcha/api.js';
120+
121+
if (process.env.CAPTCHA_PROVIDER === 'hcaptcha') {
122+
sitekey = process.env.HCAPTCHA_SITEKEY;
123+
captcha_provider_script = 'https://js.hcaptcha.com/1/api.js';
124+
}
125+
126+
if (process.env.CAPTCHA_PROVIDER === 'turnstile') {
127+
sitekey = process.env.TURNSTILE_SITEKEY;
128+
captcha_provider_script = 'https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha';
129+
}
130+
131+
res.render(resolve('./html/captcha.html'), {
132+
captcha_provider: process.env.CAPTCHA_PROVIDER,
133+
captcha_provider_script,
134+
captcha_sitekey: sitekey,
135+
});
136+
} else {
137+
req.session.destroy(() => undefined);
138+
res.render(resolve('./html/error.html'), {
139+
messageText: 'Please verify your email!',
140+
});
141+
}
142+
} catch (error) {
143+
console.log(error);
144+
res.redirect(
145+
`https://discord.com/oauth2/authorize?client_id=${client.application?.id}&redirect_uri=${process.env.CALLBACK_URL}&response_type=code&scope=guilds.join%20email%20identify`,
146+
);
102147
}
148+
});
103149

104-
if (userguild.roles.cache.has(process.env.VERIFIED_ROLE_ID)) {
105-
res.render(resolve('./html/success.html'), {
106-
messageText: 'You already verified!',
107-
});
150+
server.post('/verify/solve', async (req, res) => {
151+
let response_body_field = 'g-recaptcha-response';
152+
let captha_server = 'https://www.google.com/recaptcha/api/siteverify';
153+
let captcha_secret = process.env.RECAPTCHA_SECRET;
108154

109-
return;
155+
if (process.env.CAPTCHA_PROVIDER === 'hcaptcha') {
156+
response_body_field = 'h-captcha-response';
157+
captha_server = 'https://hcaptcha.com/siteverify';
158+
captcha_secret = process.env.HCAPTCHA_SECRET;
110159
}
111160

112-
req.session.verify_userid = parsed.id;
113-
114-
if (process.env.REQUIRE_VERIFIED_EMAIL === 'true' || parsed.verified) {
115-
req.session.verify_status = 'waiting_recaptcha';
116-
res.render(resolve('./html/captcha.html'), {
117-
recaptcha_sitekey: process.env.RECAPTCHA_SITEKEY,
118-
});
119-
} else {
120-
req.session.destroy(() => undefined);
121-
res.render(resolve('./html/error.html'), {
122-
messageText: 'Please verify your email!',
123-
});
161+
if (process.env.CAPTCHA_PROVIDER === 'turnstile') {
162+
response_body_field = 'g-recaptcha-response';
163+
captha_server = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
164+
captcha_secret = process.env.TURNSTILE_SECRET;
124165
}
125-
});
126166

127-
server.post('/verify/solve/', async (req, res) => {
128-
console.log(req.session.verify_userid);
129-
if (!req.session.verify_userid || !req.body['g-recaptcha-response']) {
167+
console.log(req.session.verify_userid, req.body[response_body_field]);
168+
169+
if (!req.session.verify_userid || !req.body[response_body_field]) {
130170
return res.redirect('/verify');
131171
}
132172

133173
/** @type {Axios.AxiosRequestConfig} */
134174
const options = {
135175
method: 'POST',
136-
url: 'https://www.google.com/recaptcha/api/siteverify',
137-
headers: {
138-
'content-type':
139-
'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
140-
},
176+
url: captha_server,
141177
data: qs.stringify({
142-
secret: process.env.RECAPTCHA_SECRET,
143-
response: req.body['g-recaptcha-response'],
178+
secret: captcha_secret,
179+
response: req.body[response_body_field],
144180
}),
145181
};
146182

src/type.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ declare global {
1111
declare module 'express-session' {
1212
export interface SessionData {
1313
verify_userid: string;
14-
verify_status: 'waiting_recaptcha' | 'done';
14+
verify_status: 'waiting_captcha' | 'done';
1515
}
1616
}

src/validate-env.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1-
const { object, parse, string } = require('valibot');
1+
const { object, parse, string, enum_, optional } = require('valibot');
22

33
const EnvironmentVariableSchema = object({
44
TOKEN: string(),
55
CLIENT_SECRET: string(),
66
CALLBACK_URL: string(),
77

8-
RECAPTCHA_SITEKEY: string(),
9-
RECAPTCHA_SECRET: string(),
8+
CAPTCHA_PROVIDER: enum_(['hcaptcha', 'recaptcha', 'turnstile']),
9+
10+
RECAPTCHA_SITEKEY: optional(string()),
11+
RECAPTCHA_SECRET: optional(string()),
12+
13+
HCAPTCHA_SITEKEY: optional(string()),
14+
HCAPTCHA_SECRET: optional(string()),
15+
16+
TURNSTILE_SECRET: optional(string()),
17+
TURNSTILE_SITEKEY: optional(string()),
1018

1119
SERVER_ID: string(),
1220

0 commit comments

Comments
 (0)