Source code for piccolo_api.shared.auth.captcha

import inspect
import typing as t
from dataclasses import dataclass

import httpx

Response = t.Optional[str]
Validator = t.Union[
    t.Callable[[str], Response],
    t.Callable[[str], t.Awaitable[Response]],
]


[docs] @dataclass class Captcha: """ Used to create CAPTCHAs for adding bot protection to endpoints. This is a generic class for implementing your own CAPTCHA. Out of the box support is provided for :func:`hcaptcha <hcaptcha>` and :func:`recaptcha_v2 <recaptcha_v2>`, so you should only need to use this class directly if doing something custom. :param form_html: Any HTML which needs inserting into the form to make the CAPTCHA work. :param validator: A callback (either an async or normal function), which is passed the CAPTCHA token, and is used to verify with the CAPTCHA provider's API that the token is valid. To indicate that validation has failed, return a string containing an error message which will be shown to the user. """ form_html: str token_field: str validator: Validator async def validate(self, token: str) -> t.Optional[str]: if self.validator: if inspect.iscoroutinefunction(self.validator): return await self.validator(token) # type: ignore elif inspect.isfunction(self.validator): return self.validator(token) return None
@dataclass class TestCredentials: site_key: str secret_key: str token: str # These can be used to test hCaptcha # From here: https://docs.hcaptcha.com/#integration-testing-test-keys HCAPTCHA_TEST_CREDENTIALS = TestCredentials( site_key="10000000-ffff-ffff-ffff-000000000001", secret_key="0x0000000000000000000000000000000000000000", token="10000000-aaaa-bbbb-cccc-000000000001", )
[docs] def hcaptcha(site_key: str, secret_key: str) -> Captcha: """ Can be used along with Piccolo endpoints to incorporate `hCaptcha <https://www.hcaptcha.com/>`_. :param site_key: Provided by hCaptcha. :param secret_key: Provided by hCaptcha. """ async def validator(token: str) -> t.Optional[str]: if not token: return "Unable to find CAPTCHA token." async with httpx.AsyncClient() as client: response = await client.post( "https://hcaptcha.com/siteverify", data={ "secret": secret_key, "response": token, }, ) data = response.json() if not data.get("success", None) is True: return "CAPTCHA failed." return None return Captcha( form_html=f""" <div class="h-captcha" data-sitekey="{site_key}"></div> <script src="https://js.hcaptcha.com/1/api.js" async defer></script> """, token_field="h-captcha-response", validator=validator, )
# These can be used to test reCAPTCHA # From here: https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do # noqa: E501 RECAPTCHA_V2_TEST_CREDENTIALS = TestCredentials( site_key="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", secret_key="6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe", token="abc123", # Any token will pass )
[docs] def recaptcha_v2(site_key: str, secret_key: str) -> Captcha: """ Can be used along with Piccolo endpoints to incorporate `reCAPTCHA <https://developers.google.com/recaptcha>`_. :param site_key: Provided by reCAPTCHA. :param secret_key: Provided by reCAPTCHA. """ async def validator(token: str) -> t.Optional[str]: if not token: return "Unable to find CAPTCHA token." async with httpx.AsyncClient() as client: response = await client.post( "https://www.google.com/recaptcha/api/siteverify", data={ "secret": secret_key, "response": token, }, ) data = response.json() if not data.get("success", None) is True: return "CAPTCHA failed." return None return Captcha( form_html=f""" <div class="g-recaptcha" data-sitekey="{site_key}"></div> <script src="https://www.google.com/recaptcha/api.js" async defer></script> """, # noqa: E501 token_field="g-recaptcha-response", validator=validator, )