Initial commit

This commit is contained in:
Jill Stingray 2020-06-17 18:37:12 +03:00
commit 5070bdcc6c
11 changed files with 277 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor

118
CaptchaManager.php Normal file
View 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;
}

View 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
View File

@ -0,0 +1,2 @@
services:
- captcha\Web\Presenters\CaptchaPresenter

5
Web/routes.yml Normal file
View File

@ -0,0 +1,5 @@
static: "static"
routes:
- url: "captcha.webp"
handler: "Captcha->captcha"

22
bootstrap.php Normal file
View 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
View File

@ -0,0 +1,2 @@
captcha:
enable: false

5
composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"nette/utils": "^3.1"
}
}

96
composer.lock generated Normal file
View 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

Binary file not shown.

6
manifest.yml Normal file
View File

@ -0,0 +1,6 @@
name: "ViCaptcha"
description: "Captcha for libchandler apps"
author: "OpenVK contributors"
version: "0.0.1-alpha"
init: "bootstrap.php"