feat(altcha): add altcha validation layer to login
Some checks failed
Cadoles/hydra-sql/pipeline/head There was a failure building this commit
Some checks failed
Cadoles/hydra-sql/pipeline/head There was a failure building this commit
This commit is contained in:
parent
1cb5ae6bc3
commit
488ca00a41
8
.env
8
.env
@ -41,3 +41,11 @@ LOCK_DSN=flock
|
||||
SENTRY_DSN=
|
||||
###< sentry/sentry-symfony ###
|
||||
REDIS_DSN=redis://redis:6379
|
||||
|
||||
### Altcha
|
||||
ALTCHA_HOST='http://localhost:3333'
|
||||
ALTCHA_BASE_URL=''
|
||||
ALTCHA_DEBUG=false
|
||||
ALTCHA_WORKERS=8
|
||||
ALTCHA_DELAY=100
|
||||
ALTCHA_MOCK_ERROR=false
|
||||
|
@ -75,3 +75,19 @@ services:
|
||||
$securityPattern: '%security_pattern%'
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
App\Altcha\Form\AltchaType:
|
||||
arguments:
|
||||
$altchaHost: "%env(string:ALTCHA_HOST)%"
|
||||
$altchaBaseUrl: "%env(string:ALTCHA_BASE_URL)%"
|
||||
$altchaDebug: "%env(bool:ALTCHA_DEBUG)%"
|
||||
$altchaWorkers: "%env(string:ALTCHA_WORKERS)%"
|
||||
$altchaDelay: "%env(string:ALTCHA_DELAY)%"
|
||||
$altchaMockError: "%env(bool:ALTCHA_MOCK_ERROR)%"
|
||||
tags:
|
||||
- { name: form.type, alias: altcha}
|
||||
|
||||
App\Altcha\AltchaValidator:
|
||||
arguments:
|
||||
$altchaHost: "%env(string:ALTCHA_HOST)%"
|
||||
$altchaBaseUrl: "%env(string:ALTCHA_BASE_URL)%"
|
48
src/Altcha/AltchaTransformer.php
Normal file
48
src/Altcha/AltchaTransformer.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Altcha;
|
||||
|
||||
use App\Altcha\Form\AltchaModel;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
|
||||
class AltchaTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function reverseTransform($value): AltchaModel
|
||||
{
|
||||
if (empty($value)) {
|
||||
return new AltchaModel();
|
||||
}
|
||||
|
||||
$decodedValue = base64_decode($value);
|
||||
$data = json_decode($decodedValue);
|
||||
|
||||
$model = new AltchaModel();
|
||||
|
||||
foreach ($data as $property => $value) {
|
||||
$model->{$property} = $value;
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function transform($value): string
|
||||
{
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$json = json_encode($value);
|
||||
|
||||
if (false === $json) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return base64_encode($json);
|
||||
}
|
||||
}
|
52
src/Altcha/AltchaValidator.php
Normal file
52
src/Altcha/AltchaValidator.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Altcha;
|
||||
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AltchaValidator
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $altchaHost,
|
||||
private readonly string $altchaBaseUrl,
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(FormEvent $formEvent): void
|
||||
{
|
||||
$form = $formEvent->getForm();
|
||||
$data = $form->getData();
|
||||
|
||||
$response = $this->httpClient->request(
|
||||
'POST',
|
||||
$this->altchaHost.$this->altchaBaseUrl.'/verify',
|
||||
[
|
||||
'body' => json_encode($data),
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
if (Response::HTTP_OK !== $response->getStatusCode()) {
|
||||
$form->addError(new FormError($this->translator->trans('altcha.validator.server_validation_error', [], 'form')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $response->getContent();
|
||||
$parsedResponse = json_decode($content);
|
||||
|
||||
if (true !== $parsedResponse->success) {
|
||||
$form->addError(new FormError($this->translator->trans('altcha.validator.server_validation_error', [], 'form')));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
39
src/Altcha/Form/AltchaModel.php
Normal file
39
src/Altcha/Form/AltchaModel.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Altcha\Form;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class AltchaModel
|
||||
{
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
* @Assert\Regex("/^(SHA-1|SHA-256|SHA-512)$/")
|
||||
*/
|
||||
public string $algorithm;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public string $challenge;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public string $salt;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public int $number;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public string $signature;
|
||||
|
||||
/**
|
||||
* @Assert\NotBlank()
|
||||
*/
|
||||
public int $took;
|
||||
}
|
81
src/Altcha/Form/AltchaType.php
Normal file
81
src/Altcha/Form/AltchaType.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Altcha\Form;
|
||||
|
||||
use App\Altcha\AltchaValidator;
|
||||
use App\Altcha\AltchaTransformer;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
||||
class AltchaType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $altchaHost,
|
||||
private readonly string $altchaBaseUrl,
|
||||
private readonly string $altchaDebug,
|
||||
private readonly string $altchaWorkers,
|
||||
private readonly string $altchaDelay,
|
||||
private readonly string $altchaMockError,
|
||||
private readonly AltchaValidator $altchaValidator,
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly TranslatorInterface $translator
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addModelTransformer(new AltchaTransformer());
|
||||
|
||||
$builder->addEventListener(FormEvents::POST_SUBMIT, [$this->altchaValidator, 'validate']);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$translations = [
|
||||
'label' => $this->translator->trans('altcha.widget.label', [], 'form'),
|
||||
'verified' => $this->translator->trans('altcha.widget.verified', [], 'form'),
|
||||
'verifying' => $this->translator->trans('altcha.widget.verifying', [], 'form'),
|
||||
'waitAlert' => $this->translator->trans('altcha.widget.waitalert', [], 'form'),
|
||||
'error' => $this->translator->trans('altcha.widget.error', [], 'form'),
|
||||
'expired' => $this->translator->trans('altcha.widget.expired', [], 'form'),
|
||||
];
|
||||
$view->vars['translations'] = json_encode($translations);
|
||||
$view->vars['challengeJson'] = $this->requestChallenge();
|
||||
$view->vars['debug'] = $this->altchaDebug;
|
||||
$view->vars['workers'] = $this->altchaWorkers;
|
||||
$view->vars['delay'] = $this->altchaDelay;
|
||||
$view->vars['mockError'] = $this->altchaMockError;
|
||||
}
|
||||
|
||||
private function requestChallenge(): string
|
||||
{
|
||||
$resp = $this->httpClient->request('GET', $this->altchaHost.$this->altchaBaseUrl.'/request');
|
||||
if (Response::HTTP_OK === $resp->getStatusCode()) {
|
||||
return $resp->getContent();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->getBlockPrefix();
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'altcha';
|
||||
}
|
||||
}
|
@ -2,12 +2,13 @@
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Altcha\Form\AltchaType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
|
||||
class LoginType extends AbstractType
|
||||
{
|
||||
@ -27,6 +28,10 @@ class LoginType extends AbstractType
|
||||
'translation_domain' => 'form',
|
||||
'label' => 'form.label.remember_me',
|
||||
])
|
||||
->add('altcha', AltchaType::class, [
|
||||
'label' => false,
|
||||
'required' => true,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -9,19 +9,17 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class Client
|
||||
{
|
||||
private HttpClientInterface $client;
|
||||
private const MAX_RETRY = 3;
|
||||
private const SLEEP_TIME = [
|
||||
5,
|
||||
500,
|
||||
5000,
|
||||
];
|
||||
private string $hydraAdminBaseUrl;
|
||||
|
||||
public function __construct(HttpClientInterface $client, string $hydraAdminBaseUrl)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->hydraAdminBaseUrl = $hydraAdminBaseUrl;
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $client,
|
||||
private readonly string $hydraAdminBaseUrl
|
||||
) {
|
||||
}
|
||||
|
||||
public function fetchLoginRequestInfo(string $loginChallenge): ResponseInterface
|
||||
|
18
templates/altcha.html.twig
Normal file
18
templates/altcha.html.twig
Normal file
@ -0,0 +1,18 @@
|
||||
{% block altcha_widget %}
|
||||
<style>
|
||||
.altcha label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<altcha-widget
|
||||
challengejson={{challengeJson}}
|
||||
name={{form.vars.full_name}}
|
||||
strings="{{translations}}"
|
||||
hidelogo
|
||||
hidefooter
|
||||
workers= {{ workers }}
|
||||
delay={{ delay }}
|
||||
{{ debug ? 'debug' : ''}}
|
||||
{{ mockError ? 'mockerror' : ''}}
|
||||
></altcha-widget>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user