Skip to content

Add captchas module: generic captcha abstraction with Turnstile, hCaptcha, and reCAPTCHA v2/v3#15

Open
lk316 wants to merge 19 commits into
masterfrom
pos-module-captchas
Open

Add captchas module: generic captcha abstraction with Turnstile, hCaptcha, and reCAPTCHA v2/v3#15
lk316 wants to merge 19 commits into
masterfrom
pos-module-captchas

Conversation

@lk316

@lk316 lk316 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

All 4 providers in a single module as they are not complicated which means we can avoid the module cross-dependency dance.

pos-module-captchas

New module: a generic, swappable captcha abstraction. It does two jobs behind one uniform interface:

  1. Render: render 'modules/captchas/widget', provider: ..., site_key: ... emits the provider's loader script + widget element (script deduped per page, options passed through, all attribute output escaped).
  2. Verify: function r = 'modules/captchas/commands/captcha/verify', secret: ... runs build > check > execute, POSTs the token to the provider's siteverify endpoint via api_call_send, and returns a normalized { valid, success, response, errors }.

Providers

  • Cloudflare Turnstile: JSON siteverify body.
  • hCaptcha: form-urlencoded; supports expected_sitekey to bind tokens to one sitekey (hCaptcha secrets are account-wide).
  • reCAPTCHA v2: form-urlencoded; size: invisible documented as requiring caller JS.
  • reCAPTCHA v3: separate provider; score-based with min_score (default 0.5) and optional expected_action; invisible widget auto-injects the token on submit.

Design notes

  • Keys are caller-supplied per call (from constants); the module stores none, so multiple keys per provider per instance work out of the box.
  • Fails closed everywhere: missing token/secret, unsupported provider, non-200 or non-JSON provider response, etc. all reject without ever passing unverified. Optional expected_hostname allow-list as defense-in-depth.
  • Self-contained (no core dependency). Provider default via CAPTCHA_DEFAULT_PROVIDER constant.
  • The siteverify call is a normal billable API call (not exempt); documented in the README.
  • en/pl translations; errors are returned as translation keys.

Testing

  • 8 Liquid unit tests (build/check phases, fail-closed paths, hostname allow-list, helpers, translations); all passing on staging via pos-cli test run.
  • Runnable demo app: /captcha-demo?provider=... > /captcha-verify, using each provider's public always-pass test keys (v3 needs real keys via constants).
  • Verified end-to-end on staging for Turnstile, hCaptcha, and reCAPTCHA v2; pos-cli check clean.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant