1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-06 22:26:32 +02:00

Captcha integration

This commit is contained in:
trendschau
2023-09-15 22:08:05 +02:00
parent 089af76dde
commit ea2821a2a4
11 changed files with 210 additions and 64 deletions

View File

@@ -15,7 +15,7 @@ class ControllerWebAuth extends Controller
{
return $this->c->get('view')->render($response, 'auth/login.twig', [
'recover' => $this->settings['recoverpw'] ?? false,
#'captcha' => $this->checkIfAddCaptcha(),
'captcha' => $this->settings['authcaptcha'] ?? false,
]);
}

View File

@@ -17,29 +17,33 @@ class TwigCaptchaExtension extends AbstractExtension
public function captchaImage($initialize = false)
{
if(isset($_SESSION['captcha']) OR $initialize)
{
$builder = new CaptchaBuilder;
$builder->build();
$error = '';
if(isset($_SESSION['captcha']) && $_SESSION['captcha'] === 'error')
{
$error = '<span class="error">The captcha was wrong.</span>';
$template = '<div class="my-2 error">' .
'<label for="captcha">Captcha</label>' .
'<input type="text" name="captcha" class="form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-red-500 bg-red-100 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none">' .
'<span class="text-xs">The captcha was wrong.</span>' .
'<img class="captcha my-2" src="' . $builder->inline() . '" />' .
'</div>';
}
else
{
$template = '<div class="my-2">' .
'<label for="captcha">Captcha</label>' .
'<input type="text" name="captcha" class="form-control block w-full px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none">' .
'<img class="captcha my-2" src="' . $builder->inline() . '" />' .
'</div>';
}
$_SESSION['phrase'] = $builder->getPhrase();
$_SESSION['captcha'] = true;
$template = '<div class="formElement">' .
'<label for="captcha">Captcha</label>' .
'<input type="text" name="captcha">' .
$error .
'<img class="captcha" src="' . $builder->inline() . '" />' .
'</div>';
return $template;
}
}

View File

@@ -28,7 +28,7 @@ class ApiAuthorization implements MiddlewareInterface
'message' => $message
]));
return $response->withStatus(403);
return $response->withStatus(403);
}
$response = $handler->handle($request);

View File

@@ -0,0 +1,138 @@
<?php
namespace Typemill\Middleware;
#use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
use Slim\Psr7\Response;
use Slim\Routing\RouteParser;
use Gregwar\Captcha\CaptchaBuilder;
class securityMiddleware implements MiddlewareInterface
{
protected $router;
protected $settings;
# protected $flash;
public function __construct(RouteParser $router, $settings)
{
$this->router = $router;
$this->settings = $settings;
# $this->flash = $flash;
}
public function process(Request $request, RequestHandler $handler) :Response
{
if($request->getMethod() == 'POST')
{
$params = $request->getParsedBody();
$referer = $request->getHeader('HTTP_REFERER');
# simple bot check with honeypot
if(
(isset($params['personal-honey-mail']))
&& (null !== $params['personal-honey-mail'])
&& ($params['personal-honey-mail'] != '')
)
{
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
{
\Typemill\Static\Helpers::addLogEntry('honeypot ' . $referer[0]);
}
$response = new Response();
return $response->withHeader('Location', $referer[0])->withStatus(302);
# return $response->withHeader('Location', $this->router->urlFor('auth.login'))->withStatus(302);
}
# check captcha
if(isset($_SESSION['captcha']))
{
# if captcha field was filled correctly
if(
(isset($params['captcha']))
&& (null !== $params['captcha'])
&& \Gregwar\Captcha\PhraseBuilder::comparePhrases($_SESSION['phrase'], $params['captcha'] )
)
{
# delete captcha because it is solved and should not show up again
unset($_SESSION['captcha']);
# delete phrase because can't use twice
unset($_SESSION['phrase']);
}
else
{
# delete phrase because can't use twice, but keep captcha so it shows up again
unset($_SESSION['phrase']);
# set session to error
$_SESSION['captcha'] = 'error';
if(
isset($this->settings['securitylog'])
&& $this->settings['securitylog']
)
{
\Typemill\Static\Helpers::addLogEntry('wrong captcha ' . $referer[0]);
}
# and add message that captcha is empty
# $this->flash->addMessage('error', 'Captcha is wrong.');
$response = new Response();
return $response->withHeader('Location', $referer[0])->withStatus(302);
}
}
/*
#check google recaptcha
if( null !== $request->getParam('g-recaptcha-response') )
{
$recaptchaApi = 'https://www.google.com/recaptcha/api/siteverify';
$settings = $this->c->get('settings');
$secret = isset($settings['plugins'][$pluginName]['recaptcha_secretkey']) ? $settings['plugins'][$pluginName]['recaptcha_secretkey'] : false;
$recaptchaRequest = ['secret' => $secret, 'response' => $request->getParam('g-recaptcha-response')];
# use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($recaptchaRequest),
'timeout' => 5
)
);
$context = stream_context_create($options);
$result = file_get_contents($recaptchaApi, false, $context);
$result = json_decode($result);
if ($result === FALSE || $result->success === FALSE)
{
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
{
\Typemill\Models\Helpers::addLogEntry('wrong google recaptcha ' . $referer[0]);
}
# and add message that captcha is empty
$this->flash->addMessage('error', 'Captcha is wrong.');
return $response->withRedirect($referer[0]);
}
}
*/
}
$response = $handler->handle($request);
return $response;
}
}

View File

@@ -46,6 +46,16 @@
<input type="text" name="personal-honey-mail">
</div>
{% if captcha == 'standard' %}
{{ captcha(true) }}
{% elseif captcha == 'aftererror' %}
{{ captcha(old) }}
{% endif %}
<input
type="submit"
value="{{ translate('Login') }}"

View File

@@ -152,7 +152,7 @@ navigation.component('navilevel',{
:expanded="expanded"
>
<template #item="{ element }">
<li :class="element.elementType" :id="element.keyPath" :data-url="element.urlRelWoF" :data-active="element.active">
<li :class="element.elementType" :id="element.keyPath" :data-url="element.urlRelWoF" :data-active="element.active" :data-hide="element.hide">
<div class="flex w-full my-px border-b border-stone-200 relative" :class="element.elementType == 'folder' ? 'font-bold' : ''">
<div class="border-l-4" :class="getStatusClass(element.status)"></div>
<a :href="getUrl(element.urlRelWoF)" class="flex-grow border-l-2 border-stone-50 p-1 hover:bg-teal-500 hover:text-stone-50" :class="getNaviClass(element.active, element.activeParent, element.keyPathArray)">
@@ -164,6 +164,11 @@ navigation.component('navilevel',{
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<div v-if="element.hide" class="p-1 absolute right-0">
<svg class="icon icon-eye-blocked">
<use xlink:href="#icon-eye-blocked"></use>
</svg>
</div>
<div v-if="element.elementType == 'folder' && element.contains == 'pages'" class=" p-1 bg-transparent absolute right-0" @click="callToggle(element.name)">
<svg v-if="isExpanded(element.name)" class="icon icon-cheveron-up">
<use xlink:href="#icon-cheveron-up"></use>

View File

@@ -154,10 +154,21 @@ fieldsetaccess:
type: checkbox
label: Wrap restriction notice
checkboxlabel: Wrap the restriction notice above into a notice-4 element (which can be designed as special box)
fieldsetrecovery:
fieldsetsecurity:
type: fieldset
legend: PW recovery
legend: Security
fields:
securitylog:
type: checkbox
label: Security log
checkboxlabel: Track spam and suspicious actions in a logfile
authcaptcha:
type: radio
label: Use captcha in authentication forms
options:
disabled: Disable
standard: Always show
aftererror: Show after first wrong input
recoverpw:
type: checkbox
label: Recover password
@@ -184,10 +195,6 @@ fieldsetdeveloper:
type: checkbox
label: Error reporting
checkboxlabel: Display application errors
securitylog:
type: checkbox
label: Security log
checkboxlabel: Track spam and suspicious actions in a logfile
twigcache:
type: checkbox
label: Twig cache

View File

@@ -27,6 +27,7 @@ use Typemill\Middleware\ValidationErrorsMiddleware;
use Typemill\Middleware\JsonBodyParser;
use Typemill\Middleware\FlashMessages;
use Typemill\Middleware\AssetMiddleware;
use Typemill\Middleware\SecurityMiddleware;
use Typemill\Extensions\TwigCsrfExtension;
use Typemill\Extensions\TwigUrlExtension;
use Typemill\Extensions\TwigUserExtension;
@@ -53,7 +54,6 @@ error_reporting(E_ALL);
$settingsModel = new Settings();
$settings = $settingsModel->loadSettings();
# $settings = Settings::loadSettings();
/****************************
* HANDLE DISPLAY ERRORS *
@@ -244,39 +244,11 @@ $container->set('dispatcher', function() use ($dispatcher){ return $dispatcher;
$assets = new \Typemill\Assets($urlinfo['basepath']);
$container->set('assets', function() use ($assets){ return $assets; });
# Register Middleware On Container
$csrf = false;
/*
if(isset($_SESSION))
{
# add flash messsages
$container->set('flash', function(){ return new Messages(); });
# Register Middleware On Container
$csrf = new Guard($responseFactory);
$csrf->setPersistentTokenMode(true);
$request = $responseFactory->;
$csrf->setFailureHandler(
function (ServerRequestInterface $request, RequestHandlerInterface $handler)
{
$request = $request->withAttribute("csrf_status", false);
return $handler->handle($request);
}
);
$container->set('csrf', function () use ($csrf){ return $csrf; });
# Add Validation Errors Middleware
# $app->add(new ValidationErrors($container->get('view')));
}
*/
/****************************
* TWIG TO CONTAINER *
****************************/
$container->set('view', function() use ($settings, $csrf, $urlinfo, $translations) {
$container->set('view', function() use ($settings, $urlinfo, $translations) {
$twig = Twig::create(
[
@@ -306,20 +278,10 @@ $container->set('view', function() use ($settings, $csrf, $urlinfo, $translation
$twig->addExtension(new TwigMetaExtension());
$twig->addExtension(new TwigCaptchaExtension());
# start csrf only if session is active
/*
if($csrf)
{
$twig->addExtension(new TwigCsrfExtension($csrf));
}
*/
return $twig;
});
/****************************
* MIDDLEWARE *
****************************/
@@ -328,6 +290,8 @@ $app->add(new AssetMiddleware($assets, $container->get('view')));
$app->add(new ValidationErrorsMiddleware($container->get('view')));
$app->add(new SecurityMiddleware($routeParser, $container->get('settings')));
$app->add(new OldInputMiddleware($container->get('view')));
$app->add(new FlashMessages($container));