Meteo-Concept / hcaptcha-bundle

A Symfony 4+ bundle to bring hCaptcha into your forms
Other
22 stars 10 forks source link

Support for custom Authenticator? #13

Closed TaromaruYuki closed 2 years ago

TaromaruYuki commented 2 years ago

Works perfectly, use it for my signup form. But I can't use it in my login form, as it uses a custom Symfony authenticator. There aren't any docs outside of the readme, which doesn't help.

If there isn't any support, is it possible to check and verify the captcha myself?

Thank you!

lgeorget commented 2 years ago

What problem are you facing with your Symfony authenticator? This bundle only integrates with the Form component, it adds a custom FieldType whose data is not mapped and with a default constraint that checks the captcha. It should be irrelevant in what context you use it, as long as you have a Form or a FormExtension in which you can add the field.

The verification is done by this class: https://github.com/Meteo-Concept/hcaptcha-bundle/blob/v3/Service/HCaptchaVerifier.php. You can import that class as a service and use the method verify to verify a captcha.

I reckon that the bundle could use more documentation, although the only intended usage is really what's shown in the readme.

TaromaruYuki commented 2 years ago

When authenticating, the authenticator does not see it as a required param so it can be skipped. Which makes sense. So, I have to verify it myself.

I have attempted to make a verifier instance, but the client param is wrong. How would passing Symfony's HttpClient work then? The only example I can find is the VerifierTest but that uses HTTP mock.

lgeorget commented 2 years ago

You shouldn't need to instantiate it yourself and should use dependency injection instead, like any service.

Let's assume your class is called CustomAuthenticator, you can use :

<?php

use MeteoConcept\HCaptchaBundle\Form\HCaptchaResponse;
use MeteoConcept\HCaptchaBundle\Service\HCaptchaVerifier;
use Symfony\Component\HttpFoundation\RequestStack;

class CustomAuthenticator
{
    private HCaptchaVerifier $verifier;

    private RequestStack $requestStack;

    private string $siteKey;

    public function __construct(RequestStack $requestStack, HCaptchaVerifier $verifier, string $siteKey) {
        $this->requestStack = $requestStack;
        $this->verifier = $verifier;
        $this->siteKey = $siteKey;
    }

   //...

   public function verifyLogin() {
       //..
        $masterRequest = $this->requestStack->getMainRequest();
        $response = $masterRequest->get("h-captcha-response");
        $remoteIp = $masterRequest->getClientIp();
        $captcha = new HCaptchaResponse($response, $remoteIp, $this->siteKey);

        if ($this->verifier->verify($captcha)) {
           // ...
        }
   }
}

You also need to pass the site key to your CustomAuthenticator class via a service definition. It's a bit cumbersome because normally the work of getting the captcha from the request is done by the Form DataTransformer. Alternatively you could define a service that extends the HCaptchaVerifier to do all the work.

lgeorget commented 2 years ago

If you have access to the form itself in the authenticator, it's easier because then, the captcha field value is already an instance of HCaptchaResponse so you can verify it directly.

TaromaruYuki commented 2 years ago

Ah yeah, I've been doing vanilla PHP for a while. I'm just so used to making everything myself.

I did get it working, roughly used what was posted.

public function authenticate(Request $request): Passport
{
    ...

    $h_res = $request->request->get('h-captcha-response');
    $ip = $request->getClientIp();
    $captcha = new HCaptchaResponse($h_res, $ip, $_ENV['HCAPTCHA_SITE_KEY']);

    if ($h_res === "") {
        throw new CustomUserMessageAuthenticationException(
            "Capcha was not filled out. Please fill the capcha to login."
        );
    }

    if($this->verifier->verify($captcha)) {
        ...
    } else {
        throw new CustomUserMessageAuthenticationException("Captcha could not verified. Please try again.");
    }
}