diff --git a/config.example.json b/config.example.json index 077da9e..7280dd7 100644 --- a/config.example.json +++ b/config.example.json @@ -4,6 +4,7 @@ "MaxFileSize": 104857600, "BlockedMD5": [], "RateLimit": 10, + "hCaptchaSecret": "abcdefghijklmnopqrstuvwxyz1234567890", "LogDir": "/var/log/agent", "DiscordWebhook": "https://discordapp.com/api/webhooks/1234567890/abcdefghijklmnopqrstuvwxyz", "VMs": [ diff --git a/package.json b/package.json index 3d2b8f5..93d73fc 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,10 @@ "typescript": "^5.3.2" }, "dependencies": { + "@hcaptcha/types": "^1.0.3", "@types/md5": "^2.3.5", "async-mutex": "^0.4.0", + "axios": "^1.6.2", "discord.js": "^14.14.1", "fastify": "^4.24.3", "md5": "^2.3.0", diff --git a/src/IConfig.ts b/src/IConfig.ts index f740c78..6668b46 100644 --- a/src/IConfig.ts +++ b/src/IConfig.ts @@ -4,6 +4,7 @@ export default interface IConfig { MaxFileSize : number; BlockedMD5: string[]; RateLimit : number; + hCaptchaSecret? : string; LogDir? : string; DiscordWebhook? : string; VMs : { diff --git a/src/hCaptchaResponse.ts b/src/hCaptchaResponse.ts new file mode 100644 index 0000000..d92cec8 --- /dev/null +++ b/src/hCaptchaResponse.ts @@ -0,0 +1,9 @@ +export default interface hCaptchaResponse { + success : boolean; + challenge_ts : string; + hostname : string; + credit? : boolean; + "error-codes"? : string[]; + score? : number; + score_reason? : string[]; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 59fe743..a8cfe02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ import log from './log.js'; import RateLimit from './Ratelimit.js'; import * as fs from 'fs'; import { EmbedBuilder, WebhookClient } from 'discord.js'; +import axios from 'axios'; +import hCaptchaResponse from './hCaptchaResponse.js'; log("INFO", "CollabVM Agent Server Starting up..."); // Load the config file @@ -63,6 +65,23 @@ app.put("/:vm/:filename", async (req, res) => { res.header("Content-Type", "application/json"); res.header("Access-Control-Allow-Origin", "*") const { vm, filename }: {vm : string, filename : string} = (req.params as any); + if (config.hCaptchaSecret) { + const { captcha }: {captcha? : string} = (req.query as any); + if (!captcha) { + res.status(400); + return { success: false, result: "Missing captcha" }; + } + var captchares = await axios.post("https://hcaptcha.com/siteverify", new URLSearchParams({ + secret: config.hCaptchaSecret, + response: captcha, + remoteip: req.ip + })); + var captchadata = captchares.data as hCaptchaResponse; + if (!captchadata.success) { + res.status(400); + return { success: false, result: "Invalid captcha" }; + } + } log("INFO", `${vm}: ${req.ip} is uploading "${filename}"`); if (req.headers['content-type'] !== "application/octet-stream") { res.status(400);