diff --git a/.env b/.env index db284e4..59bd81f 100644 --- a/.env +++ b/.env @@ -31,4 +31,9 @@ DATABASE_URL="mysql://pflaenzli:develop@127.0.0.1:3306/pflaenzli?serverVersion=m # MAILER_DSN=smtp://localhost ###< symfony/mailer ### -DEFAULT_URI='http://localhost:8080/' \ No newline at end of file +DEFAULT_URI='http://localhost:8080/' + +###> Ffiendlycaptcha ### +# CAPTCHA_SECRET= +# CAPTCHA_SITEKEY= +###< Ffiendlycaptcha ### diff --git a/assets/app.js b/assets/app.js index 1f37ba5..a680695 100644 --- a/assets/app.js +++ b/assets/app.js @@ -19,11 +19,23 @@ import '@fortawesome/fontawesome-free/js/regular' import '@fortawesome/fontawesome-free/js/brands' // Friendly captcha -import "friendly-challenge/widget"; +import { WidgetInstance } from 'friendly-challenge'; + +function doneCallback(solution) { + $('#registration_form_captcha_solution').val(solution); +} + +const element = document.querySelector('#captcha'); +const options = { + doneCallback: doneCallback, + sitekey: 'FCMVL79DP1G5K1K0', +} +const widget = new WidgetInstance(element, options); +widget.start() // Dsiplay Filename when uploading -document.querySelector('.custom-file-input').addEventListener('change',function(e){ - var fileName = document.getElementById("offering_form_photo").files[0].name; +document.querySelector('.custom-file-input').addEventListener('change', function (e) { + var fileName = document.getElementById('offering_form_photo').files[0].name; var nextSibling = e.target.nextElementSibling nextSibling.innerText = fileName }) diff --git a/config/services.yaml b/config/services.yaml index c346b29..36ac6cd 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,6 +4,8 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration parameters: + captcha.secret: '%env(CAPTCHA_SECRET)%' + captcha.sitekey: '%env(CAPTCHA_SITEKEY)%' services: # default configuration for services in *this* file diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index c228033..2f71767 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -6,6 +6,7 @@ use App\Entity\User; use App\Form\RegistrationFormType; use App\Security\AppAuthenticator; use App\Security\EmailVerifier; +use App\Service\CaptchaVerifier; use App\Repository\UserRepository; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -26,36 +27,41 @@ class RegistrationController extends AbstractController } #[Route('/register', name: 'app_register')] - public function register(Request $request, UserPasswordHasherInterface $passwordEncoder): Response + public function register(Request $request, UserPasswordHasherInterface $passwordEncoder, CaptchaVerifier $captchaVerifier): Response { $user = new User(); $form = $this->createForm(RegistrationFormType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $user->setUrlId(uniqid()); - // encode the plain password - $user->setPassword( - $passwordEncoder->hashPassword( - $user, - $form->get('plainPassword')->getData() - ) - ); + if ($captchaVerifier->isVerified($form->get('captcha_solution')->getData(), $this->getParameter('captcha.secret'), $this->getParameter('captcha.sitekey')) == true) { + $user->setUrlId(uniqid()); + // encode the plain password + $user->setPassword( + $passwordEncoder->hashPassword( + $user, + $form->get('plainPassword')->getData() + ) + ); - $entityManager = $this->getDoctrine()->getManager(); - $entityManager->persist($user); - $entityManager->flush(); + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($user); + $entityManager->flush(); - // generate a signed url and email it to the user - $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, - (new TemplatedEmail()) - ->from(new Address('no-reply@pflaenz.li', 'Pflänzli no-reply')) - ->to($user->getEmail()) - ->subject('Please Confirm your Email') - ->htmlTemplate('registration/confirmation_email.html.twig') - ); + // generate a signed url and email it to the user + $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, + (new TemplatedEmail()) + ->from(new Address('no-reply@pflaenz.li', 'Pflänzli no-reply')) + ->to($user->getEmail()) + ->subject('Please Confirm your Email') + ->htmlTemplate('registration/confirmation_email.html.twig') + ); - return $this->render('registration/created.html.twig'); + return $this->render('registration/created.html.twig'); + } + else { + $this->addFlash('error', 'CAPTCHA failed'); + } } return $this->render('registration/register.html.twig', [ diff --git a/src/Form/RegistrationFormType.php b/src/Form/RegistrationFormType.php index 0c80292..1a50b0f 100644 --- a/src/Form/RegistrationFormType.php +++ b/src/Form/RegistrationFormType.php @@ -6,12 +6,16 @@ use App\Entity\User; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\EmailType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\NotNull; class RegistrationFormType extends AbstractType { @@ -20,19 +24,14 @@ class RegistrationFormType extends AbstractType $builder ->add('email', EmailType::class) ->add('username') - ->add('zipcode') - ->add('agreeTerms', CheckboxType::class, [ - 'mapped' => false, - 'constraints' => [ - new IsTrue([ - 'message' => 'You should agree to our terms.', - ]), - ], + ->add('zipcode', NumberType::class, [ + 'label' => 'ZIP' ]) ->add('plainPassword', PasswordType::class, [ // instead of being set onto the object directly, // this is read and encoded in the controller 'mapped' => false, + 'label' => 'Password', 'constraints' => [ new NotBlank([ 'message' => 'Please enter a password', @@ -45,6 +44,28 @@ class RegistrationFormType extends AbstractType ]), ], ]) + ->add('agreeTerms', CheckboxType::class, [ + 'mapped' => false, + 'constraints' => [ + new IsTrue([ + 'message' => 'You need to agree to our terms.', + ]), + ], + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'Register', + 'attr' => [ + 'class' => 'btn-lg btn-primary', + ], + ]) + ->add('captcha_solution', HiddenType::class, [ + 'mapped' => false, + 'constraints' => [ + new NotNull([ + 'message' => 'Please wait for the CAPTCHA to complete', + ]), + ], + ]) ; } diff --git a/src/Service/CaptchaVerifier.php b/src/Service/CaptchaVerifier.php new file mode 100644 index 0000000..f5153ba --- /dev/null +++ b/src/Service/CaptchaVerifier.php @@ -0,0 +1,33 @@ + $solution, + 'secret'=> $secret, + 'sitekey'=> $sitekey, + ); + + $options = array( + 'http' => array( + 'method' => 'POST', + 'content' => json_encode( $data ), + 'header'=> "Content-Type: application/json\r\n" . + "Accept: application/json\r\n" + ) + ); + + $context = stream_context_create( $options ); + $result = file_get_contents( $url, false, $context ); + $response = json_decode( $result ); + + $isVerified = $response->success; + + return $isVerified; + } +} \ No newline at end of file diff --git a/templates/registration/register.html.twig b/templates/registration/register.html.twig index 4fb4aab..bc3882d 100644 --- a/templates/registration/register.html.twig +++ b/templates/registration/register.html.twig @@ -1,32 +1,27 @@ {% extends 'base.html.twig' %} -{% block title %}Register{% endblock %} +{% block title %}Register +{% endblock %} {% block body %} - {% for flashError in app.flashes('verify_email_error') %} -