mirror of
https://github.com/typemill/typemill.git
synced 2025-08-06 22:26:32 +02:00
Captcha integration
This commit is contained in:
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ class ApiAuthorization implements MiddlewareInterface
|
||||
'message' => $message
|
||||
]));
|
||||
|
||||
return $response->withStatus(403);
|
||||
return $response->withStatus(403);
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
138
system/typemill/Middleware/SecurityMiddleware.php
Normal file
138
system/typemill/Middleware/SecurityMiddleware.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -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') }}"
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
|
Reference in New Issue
Block a user