mirror of
https://github.com/openvk/commitcaptcha.git
synced 2024-12-22 16:41:09 +03:00
Initial commit
This commit is contained in:
commit
5070bdcc6c
11 changed files with 277 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
vendor
|
118
CaptchaManager.php
Normal file
118
CaptchaManager.php
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace captcha;
|
||||||
|
use Nette\Utils\Image;
|
||||||
|
use Chandler\Session\Session;
|
||||||
|
use Chandler\Patterns\TSimpleSingleton;
|
||||||
|
|
||||||
|
class CaptchaManager
|
||||||
|
{
|
||||||
|
private $session;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->session = Session::i();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateColor(bool $maxOutRed = false): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"red" => $maxOutRed ? 255 : random_int(0, 150),
|
||||||
|
"green" => random_int(0, 100),
|
||||||
|
"blue" => random_int(0, 150),
|
||||||
|
"alpha" => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCode(): string
|
||||||
|
{
|
||||||
|
return str_replace("/", "+", base64_encode(openssl_random_pseudo_bytes(6)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyNoiseOnImage(Image $image, \Closure $fun): void
|
||||||
|
{
|
||||||
|
$_noise = (function() use ($image) {
|
||||||
|
foreach(range(1, 33) as $x)
|
||||||
|
foreach(range(1, 13) as $y)
|
||||||
|
$image->setPixel($x * 5, $y * 5, $this->generateColor());
|
||||||
|
});
|
||||||
|
|
||||||
|
$_noise();
|
||||||
|
$fun();
|
||||||
|
$_noise();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function applyLinesOnImage(Image $image, \Closure $fun): void
|
||||||
|
{
|
||||||
|
$_lines = (function() use ($image) {
|
||||||
|
foreach(range(1, random_int(2, 6)) as $i)
|
||||||
|
$image->line(random_int(0, 15), random_int(0, 65), random_int(150, 165), random_int(0, 65), $this->generateColor());
|
||||||
|
});
|
||||||
|
|
||||||
|
$_lines();
|
||||||
|
$fun();
|
||||||
|
$_lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateCaptchaImage(string $code): Image
|
||||||
|
{
|
||||||
|
$image = Image::fromBlank(165, 65, $this->generateColor());
|
||||||
|
|
||||||
|
$this->applyNoiseOnImage($image, function() use ($image, $code) {
|
||||||
|
$this->applyLinesOnImage($image, function() use ($image, $code) {
|
||||||
|
$length = iconv_strlen($code);
|
||||||
|
$offset = 165 / $length;
|
||||||
|
|
||||||
|
for($i = 0; $i < $length; $i++) {
|
||||||
|
$letter = $code[$i];
|
||||||
|
$font = __DIR__ . "/data/san-francissco.ttf";
|
||||||
|
$x = (int) ceil(0 + ($offset * $i));
|
||||||
|
$y = random_int(45, 55);
|
||||||
|
|
||||||
|
$image->ttfText(random_int(18, 28), random_int(-10, 10), $x, $y, $this->generateColor(true), $font, $letter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImage(): Image
|
||||||
|
{
|
||||||
|
$code = $this->generateCode();
|
||||||
|
$hash = hash("crc32b", mb_strtolower($code));
|
||||||
|
$image = $this->generateCaptchaImage($code);
|
||||||
|
|
||||||
|
$nonce = bin2hex(openssl_random_pseudo_bytes(SODIUM_CRYPTO_STREAM_NONCEBYTES / 2));
|
||||||
|
$key = substr(CHANDLER_ROOT_CONF["security"]["secret"], 0, SODIUM_CRYPTO_STREAM_KEYBYTES);
|
||||||
|
$encHash = sodium_crypto_stream_xor($hash, $nonce, $key);
|
||||||
|
$this->session->set("captcha", implode(":", [
|
||||||
|
time(),
|
||||||
|
$nonce,
|
||||||
|
base64_encode($encHash),
|
||||||
|
]));
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyCaptcha(string $input): bool
|
||||||
|
{
|
||||||
|
if(!CAPTCHA_ROOT_CONF["captcha"]["enable"])
|
||||||
|
return true;
|
||||||
|
|
||||||
|
$data = $this->session->get("captcha");
|
||||||
|
if(!$data)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$this->session->set("captcha", "");
|
||||||
|
|
||||||
|
[$time, $nonce, $encHash] = explode(":", $data);
|
||||||
|
if((time() - $time) > 3600)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$key = substr(CHANDLER_ROOT_CONF["security"]["secret"], 0, SODIUM_CRYPTO_STREAM_KEYBYTES);
|
||||||
|
$hash = sodium_crypto_stream_xor(base64_decode($encHash), $nonce, $key);
|
||||||
|
return hash_equals(hash("crc32b", mb_strtolower($input)), $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
use TSimpleSingleton;
|
||||||
|
}
|
20
Web/Presenters/CaptchaPresenter.php
Normal file
20
Web/Presenters/CaptchaPresenter.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
namespace captcha\Web\Presenters;
|
||||||
|
use Chandler\MVC\SimplePresenter;
|
||||||
|
use Nette\Utils\Image;
|
||||||
|
use captcha\CaptchaManager;
|
||||||
|
|
||||||
|
class CaptchaPresenter extends SimplePresenter
|
||||||
|
{
|
||||||
|
function renderCaptcha()
|
||||||
|
{
|
||||||
|
$manager = CaptchaManager::i();
|
||||||
|
$image = $manager->getImage();
|
||||||
|
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: Wed, 12 Feb 2003 00:00:00 GMT");
|
||||||
|
header("Cache-Control: no-cache, no-store, no-transform, must-revalidate");
|
||||||
|
$image->send(Image::WEBP, 32);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
2
Web/di.yml
Normal file
2
Web/di.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
services:
|
||||||
|
- captcha\Web\Presenters\CaptchaPresenter
|
5
Web/routes.yml
Normal file
5
Web/routes.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
static: "static"
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- url: "captcha.webp"
|
||||||
|
handler: "Captcha->captcha"
|
22
bootstrap.php
Normal file
22
bootstrap.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
use captcha\CaptchaManager;
|
||||||
|
|
||||||
|
function captcha_template(): string
|
||||||
|
{
|
||||||
|
$html = <<<'HTML'
|
||||||
|
<div class="captcha">
|
||||||
|
<img src="/captcha/captcha.webp" alt="Captcha" style="margin-bottom: 8px; width: 130px;" />
|
||||||
|
<br/>
|
||||||
|
<input type="text" name="captcha" placeholder="Enter 8 characters" />
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
return CAPTCHA_ROOT_CONF["captcha"]["enable"] ? $html : "You have already verified that you are not a robot.";
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_captcha(?string $input): bool
|
||||||
|
{
|
||||||
|
return CaptchaManager::i()->verifyCaptcha((string) $input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (function() {});
|
2
captcha.yml
Normal file
2
captcha.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
captcha:
|
||||||
|
enable: false
|
5
composer.json
Normal file
5
composer.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"nette/utils": "^3.1"
|
||||||
|
}
|
||||||
|
}
|
96
composer.lock
generated
Normal file
96
composer.lock
generated
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "141496a9fa1ec7cb854debfd40127510",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "nette/utils",
|
||||||
|
"version": "v3.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nette/utils.git",
|
||||||
|
"reference": "2c17d16d8887579ae1c0898ff94a3668997fd3eb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/nette/utils/zipball/2c17d16d8887579ae1c0898ff94a3668997fd3eb",
|
||||||
|
"reference": "2c17d16d8887579ae1c0898ff94a3668997fd3eb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nette/tester": "~2.0",
|
||||||
|
"phpstan/phpstan": "^0.12",
|
||||||
|
"tracy/tracy": "^2.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-gd": "to use Image",
|
||||||
|
"ext-iconv": "to use Strings::webalize() and toAscii()",
|
||||||
|
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
|
||||||
|
"ext-json": "to use Nette\\Utils\\Json",
|
||||||
|
"ext-mbstring": "to use Strings::lower() etc...",
|
||||||
|
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
|
||||||
|
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.1-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"src/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause",
|
||||||
|
"GPL-2.0-only",
|
||||||
|
"GPL-3.0-only"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "David Grudl",
|
||||||
|
"homepage": "https://davidgrudl.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nette Community",
|
||||||
|
"homepage": "https://nette.org/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||||
|
"homepage": "https://nette.org",
|
||||||
|
"keywords": [
|
||||||
|
"array",
|
||||||
|
"core",
|
||||||
|
"datetime",
|
||||||
|
"images",
|
||||||
|
"json",
|
||||||
|
"nette",
|
||||||
|
"paginator",
|
||||||
|
"password",
|
||||||
|
"slugify",
|
||||||
|
"string",
|
||||||
|
"unicode",
|
||||||
|
"utf-8",
|
||||||
|
"utility",
|
||||||
|
"validation"
|
||||||
|
],
|
||||||
|
"time": "2020-02-09T14:10:55+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
BIN
data/san-francissco.ttf
Normal file
BIN
data/san-francissco.ttf
Normal file
Binary file not shown.
6
manifest.yml
Normal file
6
manifest.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
name: "ViCaptcha"
|
||||||
|
description: "Captcha for libchandler apps"
|
||||||
|
author: "OpenVK contributors"
|
||||||
|
version: "0.0.1-alpha"
|
||||||
|
|
||||||
|
init: "bootstrap.php"
|
Loading…
Reference in a new issue