mirror of
https://github.com/openvk/commitcaptcha.git
synced 2024-11-14 19:19: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