Skip to content

Instantly share code, notes, and snippets.

@mikeckennedy
Last active July 5, 2024 19:43
Show Gist options
  • Save mikeckennedy/66d6298106671a3c6215c9262c102d74 to your computer and use it in GitHub Desktop.
Save mikeckennedy/66d6298106671a3c6215c9262c102d74 to your computer and use it in GitHub Desktop.
Python code for server-side Cloudflare validation (paired with client-side Cloudflare code)
# See details about Turnstile at https://blog.cloudflare.com/turnstile-private-captcha-alternative/
from typing import Optional
import pydantic
import requests
cloudflare_secret_key: Optional[str] = None
class SiteVerifyRequest(pydantic.BaseModel):
secret: str
response: str
remoteip: Optional[str]
class SiteVerifyResponse(pydantic.BaseModel):
success: bool
challenge_ts: Optional[str]
hostname: Optional[str]
error_codes: list[str] = pydantic.Field(alias="error-codes", default_factory=list)
action: Optional[str]
cdata: Optional[str]
request_example = {
"secret": "0x5ABAAFAAAn72SdCAP75q6sPP9P6zooFZt",
"response": "???",
"remoteip": "1.2.3.4"
}
success_example = {
"success": True,
"challenge_ts": "2022-02-28T15:14:30.096Z",
"hostname": "example.com",
"error-codes": [],
"action": "login",
"cdata": "session_id-123456789"
}
failure_example = {
"success": False,
"hostname": "",
"error-codes": [
"invalid-input-response"
]
}
def validate(turnstile_response: str, user_ip: Optional[str]) -> SiteVerifyResponse:
if not cloudflare_secret_key:
raise Exception("You must call turnstile.init() with a valid secret key before using this function.")
if not turnstile_response:
model = SiteVerifyResponse(success=False, hostname=None)
model.error_codes.append('Submitted with no cloudflare client response')
return model
url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
model = SiteVerifyRequest(secret=cloudflare_secret_key, response=turnstile_response, remoteip=user_ip)
try:
resp = requests.post(url, data=model.dict())
if resp.status_code != 200:
model = SiteVerifyResponse(success=False, hostname=None)
model.error_codes.extend([
f'Failure status code: {resp.status_code}',
f'Failure details: {resp.text}'])
return model
site_response = SiteVerifyResponse(**resp.json())
return site_response
except Exception as x:
model = SiteVerifyResponse(success=False, hostname=None)
model.error_codes.extend([
f'Failure status code: Unknown',
f'Failure details: {x}'])
return model
def init(secret_key: str):
global cloudflare_secret_key
if not secret_key:
return
cloudflare_secret_key = secret_key.strip()
@eddyizm
Copy link

eddyizm commented Jul 5, 2024

Hi Michael!
I'm guessing this can get pulled into a fastapi endpoint and used with dependency injection? Currently trying to set up a fastapi html page returning the turnstile data and of course, I stumbled upon this snippet.

@mikeckennedy
Copy link
Author

Hey, I would imagine sure. Basically you include the turnsile JS from cloudflare in the HTML and then that gets posted along with the form that it's protecting and you can use this code to check whether the validation passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment