Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions pos-module-captchas-hcaptcha/app/views/pages/demo.liquid
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
{% comment %}
Example-app demo for the captchas_hcaptcha provider (NOT part of the distributed module).
GET /demo — renders a form with the hCaptcha widget using hCaptcha's public always-pass
TEST keys. Submitting POSTs to /verify, which verifies the token server-side and shows the
normalized result. Swap the keys for real ones (constants) to see a live challenge.
GET /demo — demonstrates the DEFAULT-KEYS best practice: the site key is resolved from the
CAPTCHA_DEFAULT_SITE_KEY constant, so the widget is rendered with NO site_key argument (and
/verify reads the secret from CAPTCHA_DEFAULT_SECRET). This exercises the captchas
abstraction's helpers/default_site_key and helpers/default_secret. The provider is pinned to
'hcaptcha' here; only the keys come from constants.

hCaptcha publishes public always-pass TEST keys, so set the two constants to those and reload
(see the setup box). Swap in your real keys the same way for a live challenge.
{% endcomment %}
{%- liquid
assign site_key = context.constants.CAPTCHA_DEFAULT_SITE_KEY
assign secret = context.constants.CAPTCHA_DEFAULT_SECRET
assign configured = false
if site_key != blank and secret != blank
assign configured = true
endif
-%}
<h1>Captcha demo — hCaptcha</h1>

<p>
This form uses hCaptcha's public <strong>test</strong> site key, which always passes.
Submitting posts the token to <code>/verify</code>, which calls hCaptcha's siteverify
endpoint server-side and shows the result.
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
{% unless configured %}
<p>
⚠️ This demo resolves the site key and secret from the <code>CAPTCHA_DEFAULT_SITE_KEY</code>
and <code>CAPTCHA_DEFAULT_SECRET</code> constants — set both, then reload. hCaptcha's public
always-pass test keys work out of the box:
</p>
<pre>pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SITE_KEY --value "10000000-ffff-ffff-ffff-000000000001"
pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SECRET --value "0x0000000000000000000000000000000000000000"</pre>
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
Currently set — site key: <code>{{ site_key | default: '(unset)' }}</code>,
secret: <code>{% if secret != blank %}(set){% else %}(unset){% endif %}</code>.
</p>
{% else %}
<p>
The site key comes from <code>CAPTCHA_DEFAULT_SITE_KEY</code>, so the widget below passes no
<code>site_key</code>. Submitting posts the token to <code>/verify</code>, which reads the
secret from <code>CAPTCHA_DEFAULT_SECRET</code> and binds the token to that site key via
<code>expected_sitekey</code> (hCaptcha secrets are account-wide).
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
</p>

{% render 'modules/captchas/widget', provider: 'hcaptcha', site_key: '10000000-ffff-ffff-ffff-000000000001' %}
{% render 'modules/captchas/widget', provider: 'hcaptcha' %}

<p><button type="submit">Submit</button></p>
</form>
<p><button type="submit">Submit</button></p>
</form>
{% endunless %}
16 changes: 10 additions & 6 deletions pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ method: post
---
{% comment %}
Example-app POST handler for the hCaptcha demo (NOT part of the distributed module).
Uses hCaptcha's public TEST secret, and binds the token to the test sitekey via
expected_sitekey — hCaptcha secrets are account-wide, so a real integration should
always pass it:
Verifies with the secret resolved from CAPTCHA_DEFAULT_SECRET — no secret argument passed —
which exercises the abstraction's helpers/default_secret. expected_sitekey is still passed
explicitly (it is a policy param, not a key): hCaptcha secrets are account-wide, so the token
must be bound to the expected site key — here the same CAPTCHA_DEFAULT_SITE_KEY the widget
used. The provider is pinned server-side; a real integration is identical:

function result = 'modules/captchas/commands/captcha/verify',
provider: 'hcaptcha',
secret: context.constants.CAPTCHA_HCAPTCHA_SECRET,
expected_sitekey: context.constants.CAPTCHA_HCAPTCHA_SITE_KEY
expected_sitekey: context.constants.CAPTCHA_DEFAULT_SITE_KEY

Set CAPTCHA_DEFAULT_SECRET (hCaptcha's test secret 0x0000... works). With it unset, verify
fails closed with secret_missing and the error renders below.
{% endcomment %}
{% liquid
function result = 'modules/captchas/commands/captcha/verify', provider: 'hcaptcha', secret: '0x0000000000000000000000000000000000000000', expected_sitekey: '10000000-ffff-ffff-ffff-000000000001'
function result = 'modules/captchas/commands/captcha/verify', provider: 'hcaptcha', expected_sitekey: context.constants.CAPTCHA_DEFAULT_SITE_KEY
%}
{% if result.valid %}
<h1>✅ Captcha passed ({{ result.provider }})</h1>
Expand Down
61 changes: 45 additions & 16 deletions pos-module-captchas-recaptcha/app/views/pages/demo.liquid
Original file line number Diff line number Diff line change
@@ -1,25 +1,54 @@
{% comment %}
Example-app demo for the captchas_recaptcha provider (NOT part of the distributed module).
GET /demo — renders a form with the reCAPTCHA v2 widget using Google's public always-pass
automated-testing keys (the widget shows a warning banner). Submitting POSTs to /verify,
which verifies the token server-side and shows the normalized result.
GET /demo — demonstrates the DEFAULT-KEYS best practice: the site key is resolved from the
CAPTCHA_DEFAULT_SITE_KEY constant, so the widget is rendered with NO site_key argument (and
/verify reads the secret from CAPTCHA_DEFAULT_SECRET). This exercises the captchas
abstraction's helpers/default_site_key and helpers/default_secret. The provider is pinned to
'recaptcha' here; only the keys come from constants.

Google publishes public always-pass automated-testing keys (the widget shows a warning
banner), so set the two constants to those and reload (see the setup box). Swap in your real
keys the same way for a live challenge.
{% endcomment %}
{%- liquid
assign site_key = context.constants.CAPTCHA_DEFAULT_SITE_KEY
assign secret = context.constants.CAPTCHA_DEFAULT_SECRET
assign configured = false
if site_key != blank and secret != blank
assign configured = true
endif
-%}
<h1>Captcha demo — Google reCAPTCHA v2</h1>

<p>
This form uses Google's automated-testing site key, which always passes (the widget shows
a warning banner). Submitting posts the token to <code>/verify</code>, which calls Google's
siteverify endpoint server-side and shows the result.
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
{% unless configured %}
<p>
⚠️ This demo resolves the site key and secret from the <code>CAPTCHA_DEFAULT_SITE_KEY</code>
and <code>CAPTCHA_DEFAULT_SECRET</code> constants — set both, then reload. Google's public
automated-testing keys work out of the box (the widget shows a warning banner):
</p>
<pre>pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SITE_KEY --value "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SECRET --value "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"</pre>
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
Currently set — site key: <code>{{ site_key | default: '(unset)' }}</code>,
secret: <code>{% if secret != blank %}(set){% else %}(unset){% endif %}</code>.
</p>
{% else %}
<p>
The site key comes from <code>CAPTCHA_DEFAULT_SITE_KEY</code>, so the widget below passes no
<code>site_key</code>. Submitting posts the token to <code>/verify</code>, which likewise
reads the secret from <code>CAPTCHA_DEFAULT_SECRET</code> — Google's siteverify is called
server-side and the normalized result shown.
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
</p>

{% render 'modules/captchas/widget', provider: 'recaptcha', site_key: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' %}
{% render 'modules/captchas/widget', provider: 'recaptcha' %}

<p><button type="submit">Submit</button></p>
</form>
<p><button type="submit">Submit</button></p>
</form>
{% endunless %}
14 changes: 8 additions & 6 deletions pos-module-captchas-recaptcha/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ method: post
---
{% comment %}
Example-app POST handler for the reCAPTCHA v2 demo (NOT part of the distributed module).
Uses Google's public automated-testing secret. A real integration is identical, with
the secret read from a constant:
Verifies with the secret resolved from CAPTCHA_DEFAULT_SECRET — no secret argument passed —
which exercises the abstraction's helpers/default_secret. The provider is pinned server-side
(never read from client input); a real integration is identical:

function result = 'modules/captchas/commands/captcha/verify',
provider: 'recaptcha',
secret: context.constants.CAPTCHA_RECAPTCHA_SECRET
function result = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha'

Set CAPTCHA_DEFAULT_SECRET (Google's automated-testing secret 6LeIxAcT...WifJWe works). With
it unset, verify fails closed with secret_missing and the error renders below.
{% endcomment %}
{% liquid
function result = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha', secret: '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
function result = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha'
%}
{% if result.valid %}
<h1>✅ Captcha passed ({{ result.provider }})</h1>
Expand Down
40 changes: 26 additions & 14 deletions pos-module-captchas-recaptcha3/app/views/pages/demo.liquid
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
{% comment %}
Example-app demo for the captchas_recaptcha3 provider (NOT part of the distributed module).
GET /demo — reCAPTCHA v3 has NO public test keys, so this reads real keys from the
CAPTCHA_RECAPTCHA3_SITE_KEY / CAPTCHA_RECAPTCHA3_SECRET constants and shows setup
instructions when they are unset. v3 is invisible: submitting runs the challenge, injects
the token, and POSTs to /verify, which thresholds the returned score.
GET /demo — demonstrates the DEFAULT-KEYS best practice: the site key is resolved from the
CAPTCHA_DEFAULT_SITE_KEY constant, so the widget is rendered with NO site_key argument (and
/verify reads the secret from CAPTCHA_DEFAULT_SECRET). This exercises the captchas
abstraction's helpers/default_site_key and helpers/default_secret. The provider is pinned to
'recaptcha3', and the action/score policy is still passed explicitly (not part of the keys
feature). reCAPTCHA v3 has NO public test keys, so this shows setup instructions when the
constants are unset. v3 is invisible: submitting runs the challenge, injects the token, and
POSTs to /verify, which thresholds the returned score.
{% endcomment %}
{% liquid
assign site_key = context.constants.CAPTCHA_RECAPTCHA3_SITE_KEY
{%- liquid
assign site_key = context.constants.CAPTCHA_DEFAULT_SITE_KEY
assign secret = context.constants.CAPTCHA_DEFAULT_SECRET
assign widget_options = '{"action": "demo_submit"}' | parse_json
%}
assign configured = false
if site_key != blank and secret != blank
assign configured = true
endif
-%}
<h1>Captcha demo — Google reCAPTCHA v3</h1>

<p>
reCAPTCHA v3 is <strong>invisible and score-based</strong>: no checkbox, every submit gets
a score. The verifier here requires a score ≥ 0.5 and action <code>demo_submit</code>.
</p>

{% if site_key == blank %}
<p>⚠️ <code>CAPTCHA_RECAPTCHA3_SITE_KEY</code> is not set. v3 has no public test keys —
register a v3 site in the reCAPTCHA admin console (tied to this domain) and set both constants:</p>
<pre>pos-cli constants set &lt;env&gt; --name CAPTCHA_RECAPTCHA3_SITE_KEY --value "6Le..."
pos-cli constants set &lt;env&gt; --name CAPTCHA_RECAPTCHA3_SECRET --value "6Le..."</pre>
{% unless configured %}
<p>
⚠️ <code>CAPTCHA_DEFAULT_SITE_KEY</code> / <code>CAPTCHA_DEFAULT_SECRET</code> are not both
set. v3 has no public test keys — register a v3 site in the reCAPTCHA admin console (tied to
this domain) and set both constants, then reload:
</p>
<pre>pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SITE_KEY --value "6Le..."
pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SECRET --value "6Le..."</pre>
{% else %}
<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
Expand All @@ -29,8 +41,8 @@ pos-cli constants set &lt;env&gt; --name CAPTCHA_RECAPTCHA3_SECRET --value "6Le.
<textarea id="message" name="message" rows="4" cols="40"></textarea>
</p>

{% render 'modules/captchas/widget', provider: 'recaptcha3', site_key: site_key, options: widget_options %}
{% render 'modules/captchas/widget', provider: 'recaptcha3', options: widget_options %}

<p><button type="submit">Submit</button></p>
</form>
{% endif %}
{% endunless %}
14 changes: 11 additions & 3 deletions pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ method: post
---
{% comment %}
Example-app POST handler for the reCAPTCHA v3 demo (NOT part of the distributed module).
Reads the real secret from a constant (v3 has no public test keys) and pairs the widget's
action with expected_action.
Verifies with the secret resolved from CAPTCHA_DEFAULT_SECRET — no secret argument passed —
which exercises the abstraction's helpers/default_secret. min_score and expected_action are
still passed explicitly (v3 score policy, not keys) and expected_action pairs with the
widget's action. The provider is pinned server-side; a real integration is identical:

function result = 'modules/captchas/commands/captcha/verify',
provider: 'recaptcha3', min_score: 0.5, expected_action: 'demo_submit'

Set CAPTCHA_DEFAULT_SECRET to your v3 secret (v3 has no public test keys). With it unset,
verify fails closed with secret_missing and the error renders below.
{% endcomment %}
{% liquid
function result = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha3', secret: context.constants.CAPTCHA_RECAPTCHA3_SECRET, min_score: 0.5, expected_action: 'demo_submit'
function result = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha3', min_score: 0.5, expected_action: 'demo_submit'
%}
{% if result.valid %}
<h1>✅ Captcha passed ({{ result.provider }})</h1>
Expand Down
60 changes: 44 additions & 16 deletions pos-module-captchas-turnstile/app/views/pages/demo.liquid
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
{% comment %}
Example-app demo for the captchas_turnstile provider (NOT part of the distributed module).
GET /demo — renders a form with the Turnstile widget using Cloudflare's public always-pass
TEST keys. Submitting POSTs to /verify, which verifies the token server-side and shows the
normalized result. Swap the keys for real ones (constants) to see a live challenge.
GET /demo — demonstrates the DEFAULT-KEYS best practice: the site key is resolved from the
CAPTCHA_DEFAULT_SITE_KEY constant, so the widget is rendered with NO site_key argument (and
/verify reads the secret from CAPTCHA_DEFAULT_SECRET). This exercises the captchas
abstraction's helpers/default_site_key and helpers/default_secret. The provider is pinned to
'turnstile' here; only the keys come from constants.

Cloudflare publishes public always-pass TEST keys, so set the two constants to those and
reload (see the setup box). Swap in your real keys the same way for a live challenge.
{% endcomment %}
{%- liquid
assign site_key = context.constants.CAPTCHA_DEFAULT_SITE_KEY
assign secret = context.constants.CAPTCHA_DEFAULT_SECRET
assign configured = false
if site_key != blank and secret != blank
assign configured = true
endif
-%}
<h1>Captcha demo — Cloudflare Turnstile</h1>

<p>
This form uses Turnstile's public <strong>test</strong> site key, which always passes.
Submitting posts the token to <code>/verify</code>, which calls Cloudflare's siteverify
endpoint server-side and shows the result.
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
{% unless configured %}
<p>
⚠️ This demo resolves the site key and secret from the <code>CAPTCHA_DEFAULT_SITE_KEY</code>
and <code>CAPTCHA_DEFAULT_SECRET</code> constants — set both, then reload. Cloudflare's
public always-pass test keys work out of the box:
</p>
<pre>pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SITE_KEY --value "1x00000000000000000000AA"
pos-cli constants set &lt;env&gt; --name CAPTCHA_DEFAULT_SECRET --value "1x0000000000000000000000000000000AA"</pre>
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
Currently set — site key: <code>{{ site_key | default: '(unset)' }}</code>,
secret: <code>{% if secret != blank %}(set){% else %}(unset){% endif %}</code>.
</p>
{% else %}
<p>
The site key comes from <code>CAPTCHA_DEFAULT_SITE_KEY</code>, so the widget below passes no
<code>site_key</code>. Submitting posts the token to <code>/verify</code>, which likewise
reads the secret from <code>CAPTCHA_DEFAULT_SECRET</code> — Cloudflare's siteverify is
called server-side and the normalized result shown.
</p>

<form action="/verify" method="post" accept-charset="UTF-8">
<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
<p>
<label for="message">Message</label><br>
<textarea id="message" name="message" rows="4" cols="40"></textarea>
</p>

{% render 'modules/captchas/widget', provider: 'turnstile', site_key: '1x00000000000000000000AA' %}
{% render 'modules/captchas/widget', provider: 'turnstile' %}

<p><button type="submit">Submit</button></p>
</form>
<p><button type="submit">Submit</button></p>
</form>
{% endunless %}
15 changes: 9 additions & 6 deletions pos-module-captchas-turnstile/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ method: post
---
{% comment %}
Example-app POST handler for the Turnstile demo (NOT part of the distributed module).
Uses Cloudflare's public always-pass TEST secret. A real integration is identical, with
the secret read from a constant:
Verifies with the secret resolved from CAPTCHA_DEFAULT_SECRET — no secret argument passed —
which exercises the abstraction's helpers/default_secret. The provider is pinned server-side
(never read from client input); a real integration is identical:

function result = 'modules/captchas/commands/captcha/verify',
provider: 'turnstile',
secret: context.constants.CAPTCHA_TURNSTILE_SECRET
function result = 'modules/captchas/commands/captcha/verify', provider: 'turnstile'

Set CAPTCHA_DEFAULT_SECRET (Cloudflare's always-pass test secret 1x0000...AA works). With it
unset, verify fails closed with secret_missing and the error renders below — that is the
fail-closed behavior, not a bug.
{% endcomment %}
{% liquid
function result = 'modules/captchas/commands/captcha/verify', provider: 'turnstile', secret: '1x0000000000000000000000000000000AA'
function result = 'modules/captchas/commands/captcha/verify', provider: 'turnstile'
%}
{% if result.valid %}
<h1>✅ Captcha passed ({{ result.provider }})</h1>
Expand Down
Loading
Loading