diff options
| author | ilotterytea <iltsu@alright.party> | 2025-05-15 15:20:53 +0500 |
|---|---|---|
| committer | ilotterytea <iltsu@alright.party> | 2025-05-15 15:20:53 +0500 |
| commit | 20ae2ce5e02539719b971e53222f3e3328ff82a6 (patch) | |
| tree | 1ff0014bf73d73c2f42819a45d50d3a04c53d680 | |
| parent | a3522672930578959980e39b7041b120c13cd6cf (diff) | |
feat: custom captcha
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | public/captcha.php | 92 | ||||
| -rw-r--r-- | public/emotes/upload.php | 12 | ||||
| -rw-r--r-- | src/accounts.php | 5 | ||||
| -rw-r--r-- | src/captcha.php | 151 | ||||
| -rw-r--r-- | src/config.sample.php | 5 |
6 files changed, 211 insertions, 57 deletions
@@ -2,4 +2,5 @@ userdata/ *.db config.php -custom_static/
\ No newline at end of file +custom_static/ +captcha/
\ No newline at end of file diff --git a/public/captcha.php b/public/captcha.php index 58283bf..b454b7d 100644 --- a/public/captcha.php +++ b/public/captcha.php @@ -1,65 +1,59 @@ <?php include_once "../src/config.php"; include_once "../src/alert.php"; +include_once "../src/captcha.php"; +include_once "../src/utils.php"; session_start(); -if (!HCAPTCHA_ENABLE) { - $_SESSION["captcha_solved"] = true; - header("Location: /"); +if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["answer"])) { + if ($_POST["answer"] == ($_SESSION["captcha_word"] ?? "")) { + $_SESSION["captcha_solved"] = true; + echo json_response([ + "status_code" => 200, + "message" => "Solved!", + "data" => null + ]); + } else { + echo json_response([ + "status_code" => 400, + "message" => "Wrong answer!", + "data" => null + ], 400); + } exit; } -if (isset($_SESSION["captcha_solved"]) && $_SESSION["captcha_solved"]) { - header("Location: /"); +$file_folder = $_SERVER["DOCUMENT_ROOT"] . '/static/img/captcha'; + +if (!CAPTCHA_ENABLE || ($_SESSION["captcha_solved"] ?? false) || !is_dir($file_folder)) { + $_SESSION["captcha_solved"] = true; + echo json_response([ + "status_code" => 200, + "message" => "No need to solve captcha", + "data" => null + ]); exit; } -if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["h-captcha-response"])) { - // sending a request to captcha api - $request = curl_init("https://hcaptcha.com/siteverify"); - curl_setopt($request, CURLOPT_POST, 1); - curl_setopt($request, CURLOPT_HTTPHEADER, [sprintf("User-Agent: %s/1.0", INSTANCE_NAME)]); - curl_setopt( - $request, - CURLOPT_POSTFIELDS, - http_build_query(array("secret" => HCAPTCHA_SECRETKEY, "response" => $_POST["h-captcha-response"])) - ); - curl_setopt($request, CURLOPT_RETURNTRANSFER, true); - - $response = curl_exec($request); - curl_close($request); - - $json = json_decode($response); - - if ($json->success) { - $_SESSION["captcha_solved"] = true; - header("Location: /"); - exit; - } -} -?> +$files = scandir($file_folder); +array_splice($files, 0, 2); -<html> +$filename = $files[random_int(0, count($files) - 1)]; +$filename = basename($filename, ".png"); -<head> - <title>Resolving a hCaptcha - <?php echo INSTANCE_NAME ?></title> - <link rel="stylesheet" href="/static/style.css"> - <link rel="shortcut icon" href="/static/favicon.ico" type="image/x-icon"> - <script src='https://www.hCaptcha.com/1/api.js' async defer></script> -</head> +$_SESSION["captcha_word"] = $filename; -<body> - <noscript>JavaScript is required to solve hCaptcha</noscript> - <div class="container"> - <div class="wrapper"> - <section class="row" style="padding: 4px; justify-content: center;"> - <section class="box"> - <div class="h-captcha" data-sitekey="<?php echo HCAPTCHA_SITEKEY ?>"></div> - </section> - </section> - </div> - </div> -</body> +$image = generate_image_captcha( + CAPTCHA_SIZE[0], + CAPTCHA_SIZE[1], + random_int(1, 3), + $filename, + $file_folder +); -</html>
\ No newline at end of file +echo json_response([ + "status_code" => 200, + "message" => null, + "data" => $image +]);
\ No newline at end of file diff --git a/public/emotes/upload.php b/public/emotes/upload.php index e4ff6cc..4506152 100644 --- a/public/emotes/upload.php +++ b/public/emotes/upload.php @@ -2,6 +2,7 @@ include "../../src/accounts.php"; include_once "../../src/config.php"; include_once "../../src/alert.php"; +include_once "../../src/captcha.php"; if (!EMOTE_UPLOAD) { generate_alert("/404.php", "Emote upload is disabled", 403); @@ -135,6 +136,12 @@ if ($_SERVER['REQUEST_METHOD'] != "POST") { </div> </section> + <?php + if (CAPTCHA_ENABLE && (CAPTCHA_FORCE_USERS || !isset($_SESSION["user_id"]))) { + html_captcha_form(); + } + ?> + <section class="box column" id="emote-showcase" style="display: none;"> <div class="emote-showcase"> <div class="emote-image"> @@ -319,6 +326,11 @@ if ($_SERVER['REQUEST_METHOD'] != "POST") { exit; } +if (!CLIENT_REQUIRES_JSON && CAPTCHA_ENABLE && !isset($_SESSION["captcha_solved"])) { + generate_alert("/404.php", "You haven't solved captcha yet.", 403); + exit; +} + $is_manual = intval($_POST["manual"] ?? "0") == 1; if ($is_manual && !isset($_FILES["file-1x"], $_FILES["file-2x"], $_FILES["file-3x"])) { diff --git a/src/accounts.php b/src/accounts.php index 99d1a9c..72c766f 100644 --- a/src/accounts.php +++ b/src/accounts.php @@ -5,11 +5,6 @@ function authorize_user(bool $required = false): bool { session_start(); - if (!isset($_SESSION["captcha_solved"]) && !CLIENT_REQUIRES_JSON) { - header("Location: /captcha.php"); - exit; - } - if (!isset($_COOKIE["secret_key"]) && !isset($_SERVER["HTTP_AUTHORIZATION"])) { if (isset($_SESSION["user_id"])) { session_unset(); diff --git a/src/captcha.php b/src/captcha.php new file mode 100644 index 0000000..d6d2547 --- /dev/null +++ b/src/captcha.php @@ -0,0 +1,151 @@ +<?php +function generate_image_captcha(int $width, int $height, int $difficulty, string $file_name, string $file_folder): string +{ + $image = imagecreatetruecolor($width, $height); + + $background = imagecolorallocate($image, 0xDD, 0xDD, 0xDD); + imagefilledrectangle($image, 0, 0, $width, $height, $background); + + $files = scandir($file_folder); + array_splice($files, 0, 2); + + for ($i = 0; $i < 50 * $difficulty; $i++) { + $unprocessed = imagecreatefrompng("$file_folder/" . $files[random_int(0, count($files) - 1)]); + + $oldw = imagesx($unprocessed); + $oldh = imagesy($unprocessed); + + $w = random_int(round($oldw / 4), round($oldw / 2)); + $h = random_int(round($oldh / 4), round($oldh / 2)); + + $file = imagecreatetruecolor($w, $h); + imagealphablending($file, false); + $transparent = imagecolorallocatealpha($file, 0, 0, 0, 127); + imagefill($file, 0, 0, $transparent); + imagesavealpha($file, true); + + imagecopyresampled($file, $unprocessed, 0, 0, 0, 0, $w, $h, $oldw, $oldh); + + $angle = random_int(0, 360); + + $file = imagerotate($file, $angle, $transparent); + + for ($j = 0; $j < random_int(2, 5 * $difficulty); $j++) { + imagefilter($file, IMG_FILTER_GAUSSIAN_BLUR); + } + + if (random_int(0, 15) % 3 == 0) { + imagefilter($file, IMG_FILTER_NEGATE); + } + + if (random_int(0, 20) % 4 == 0) { + imagefilter($file, IMG_FILTER_PIXELATE, 4); + } + + $w = imagesx($file); + $h = imagesy($file); + + imagecopy( + $image, + $file, + random_int(0, $width - $w), + random_int(0, $height - $h), + 0, + 0, + $w, + $h + ); + } + + $foreground = imagecreatefrompng("$file_folder/$file_name.png"); + $transparent = imagecolorallocatealpha($foreground, 0, 0, 0, 127); + $angle = random_int(0, max: 180); + $foreground = imagerotate($foreground, $angle, $transparent); + $w = imagesx($foreground); + $h = imagesy($foreground); + imagecopy( + $image, + $foreground, + random_int(0, $width - $w), + random_int(0, $height - $h), + 0, + 0, + $w, + $h + ); + + ob_start(); + imagepng($image); + $source = ob_get_contents(); + ob_clean(); + + return "data:image/png;base64," . base64_encode($source); +} + +function html_captcha_form() +{ + echo '' ?> + <div class="box" id="form-captcha-wrapper" style="display: none;"> + <div class="box navtab"> + Solve captcha + </div> + <div class="box content"> + <noscript>JavaScript is required for captcha!</noscript> + <form id="form-captcha"> + <img src="" alt="Generating captcha..." id="form-captcha-img" width="256"> + <div class="column small-gap"> + <div class="row small-gap"> + <input type="text" name="answer" placeholder="Enter emote name..." class="grow" + id="form-captcha-answer"> + <button type="submit" class="green" id="form-captcha-solve">Solve</button> + </div> + </div> + </form> + </div> + </div> + <script> + const formElement = document.getElementById("form-captcha"); + const formWrapper = document.getElementById("form-captcha-wrapper"); + + function get_captcha() { + fetch("/captcha.php") + .then((response) => response.json()) + .then((json) => { + if (json.data == null) { + formWrapper.style.display = "none"; + return; + } + + document.getElementById("form-captcha-answer").value = null; + + formWrapper.style.display = "flex"; + + document.getElementById("form-captcha-img").setAttribute("src", json.data); + }); + } + + get_captcha(); + + formElement.addEventListener("submit", (e) => { + e.preventDefault(); + + const answer = document.getElementById("form-captcha-answer"); + const body = new FormData(formElement); + + fetch("/captcha.php", { + "method": "POST", + "body": body + }) + .then((response) => response.json()) + .then((json) => { + if (json.status_code == 200 && json.data == null) { + formWrapper.style.display = "none"; + return; + } + + get_captcha(); + }); + }); + </script> + <?php ; +}
\ No newline at end of file diff --git a/src/config.sample.php b/src/config.sample.php index ed206f6..3d30044 100644 --- a/src/config.sample.php +++ b/src/config.sample.php @@ -65,9 +65,10 @@ define("TWITCH_CLIENT_ID", "AAAAAAAAA"); // Client ID of your Twitch application define("TWITCH_SECRET_KEY", "BBBBBBBBB"); // Secret key of your Twitch application. define("TWITCH_REDIRECT_URI", ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http") . "://$_SERVER[HTTP_HOST]/account/login/twitch.php"); // Redirect URI of your Twitch application. -// HCAPTCHA +// CAPTCHA define("CAPTCHA_ENABLE", true); // Enable built-in captcha. -define("CAPTCHA_WORDS", ["hello", "apple", "cat"]); // Captcha words. +define("CAPTCHA_SIZE", [580, 220]); // Captcha size. +define("CAPTCHA_FORCE_USERS", false); // Force authorized users to solve captcha. // FOR DEVELOPERS define("CLIENT_REQUIRES_JSON", isset($_SERVER["HTTP_ACCEPT"]) && $_SERVER["HTTP_ACCEPT"] == "application/json");
\ No newline at end of file |
