From ca06bc50ed428c5c6632a7998b2429474d6b2bd8 Mon Sep 17 00:00:00 2001
From: lk316 <51822928+lk316@users.noreply.github.com>
Date: Wed, 22 Jul 2026 14:34:24 +0300
Subject: [PATCH] Updated demo apps to be defaults aware
---
.../app/views/pages/demo.liquid | 60 +++++++++----
.../app/views/pages/verify.liquid | 16 ++--
.../app/views/pages/demo.liquid | 61 +++++++++----
.../app/views/pages/verify.liquid | 14 +--
.../app/views/pages/demo.liquid | 40 ++++++---
.../app/views/pages/verify.liquid | 14 ++-
.../app/views/pages/demo.liquid | 60 +++++++++----
.../app/views/pages/verify.liquid | 15 ++--
.../app/views/pages/captcha-demo.liquid | 88 +++++++++++++------
.../app/views/pages/captcha-verify.liquid | 23 ++---
10 files changed, 271 insertions(+), 120 deletions(-)
diff --git a/pos-module-captchas-hcaptcha/app/views/pages/demo.liquid b/pos-module-captchas-hcaptcha/app/views/pages/demo.liquid
index 680bd59..c398f04 100644
--- a/pos-module-captchas-hcaptcha/app/views/pages/demo.liquid
+++ b/pos-module-captchas-hcaptcha/app/views/pages/demo.liquid
@@ -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
+-%}
Captcha demo — hCaptcha
-
- This form uses hCaptcha's public test site key, which always passes.
- Submitting posts the token to /verify, which calls hCaptcha's siteverify
- endpoint server-side and shows the result.
-
-
-
+
+
+{% endunless %}
diff --git a/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid b/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
index d1713b9..4802697 100644
--- a/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
+++ b/pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
@@ -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 %}
✅ Captcha passed ({{ result.provider }})
diff --git a/pos-module-captchas-recaptcha/app/views/pages/demo.liquid b/pos-module-captchas-recaptcha/app/views/pages/demo.liquid
index d54f899..22a8c39 100644
--- a/pos-module-captchas-recaptcha/app/views/pages/demo.liquid
+++ b/pos-module-captchas-recaptcha/app/views/pages/demo.liquid
@@ -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
+-%}
Captcha demo — Google reCAPTCHA v2
-
- This form uses Google's automated-testing site key, which always passes (the widget shows
- a warning banner). Submitting posts the token to /verify, which calls Google's
- siteverify endpoint server-side and shows the result.
-
-
-
+
+
+{% endunless %}
diff --git a/pos-module-captchas-recaptcha/app/views/pages/verify.liquid b/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
index 9f15560..5bab788 100644
--- a/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
+++ b/pos-module-captchas-recaptcha/app/views/pages/verify.liquid
@@ -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 %}
✅ Captcha passed ({{ result.provider }})
diff --git a/pos-module-captchas-recaptcha3/app/views/pages/demo.liquid b/pos-module-captchas-recaptcha3/app/views/pages/demo.liquid
index 3785a91..910faae 100644
--- a/pos-module-captchas-recaptcha3/app/views/pages/demo.liquid
+++ b/pos-module-captchas-recaptcha3/app/views/pages/demo.liquid
@@ -1,14 +1,23 @@
{% 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
+-%}
Captcha demo — Google reCAPTCHA v3
@@ -16,11 +25,14 @@
a score. The verifier here requires a score ≥ 0.5 and action demo_submit.
-{% if site_key == blank %}
-
⚠️ CAPTCHA_RECAPTCHA3_SITE_KEY 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:
-
pos-cli constants set <env> --name CAPTCHA_RECAPTCHA3_SITE_KEY --value "6Le..."
-pos-cli constants set <env> --name CAPTCHA_RECAPTCHA3_SECRET --value "6Le..."
+{% unless configured %}
+
+ ⚠️ CAPTCHA_DEFAULT_SITE_KEY / CAPTCHA_DEFAULT_SECRET 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:
+
+
pos-cli constants set <env> --name CAPTCHA_DEFAULT_SITE_KEY --value "6Le..."
+pos-cli constants set <env> --name CAPTCHA_DEFAULT_SECRET --value "6Le..."
{% else %}
-{% endif %}
+{% endunless %}
diff --git a/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid b/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
index 5b9c405..3af583c 100644
--- a/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
+++ b/pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
@@ -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 %}
✅ Captcha passed ({{ result.provider }})
diff --git a/pos-module-captchas-turnstile/app/views/pages/demo.liquid b/pos-module-captchas-turnstile/app/views/pages/demo.liquid
index 4f8e8a3..9eb519e 100644
--- a/pos-module-captchas-turnstile/app/views/pages/demo.liquid
+++ b/pos-module-captchas-turnstile/app/views/pages/demo.liquid
@@ -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
+-%}
Captcha demo — Cloudflare Turnstile
-
- This form uses Turnstile's public test site key, which always passes.
- Submitting posts the token to /verify, which calls Cloudflare's siteverify
- endpoint server-side and shows the result.
-
-
-
+
+
+{% endunless %}
diff --git a/pos-module-captchas-turnstile/app/views/pages/verify.liquid b/pos-module-captchas-turnstile/app/views/pages/verify.liquid
index 2c7b3d6..47a1436 100644
--- a/pos-module-captchas-turnstile/app/views/pages/verify.liquid
+++ b/pos-module-captchas-turnstile/app/views/pages/verify.liquid
@@ -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 %}
✅ Captcha passed ({{ result.provider }})
diff --git a/pos-module-captchas/app/views/pages/captcha-demo.liquid b/pos-module-captchas/app/views/pages/captcha-demo.liquid
index ab2f374..4701e81 100644
--- a/pos-module-captchas/app/views/pages/captcha-demo.liquid
+++ b/pos-module-captchas/app/views/pages/captcha-demo.liquid
@@ -1,36 +1,70 @@
{% comment %}
Example-app demo for pos-module-captchas (NOT part of the distributed module).
- GET /captcha-demo — renders a form protected by the FAKE test provider that ships with this
- repo's test app (app/modules/captchas_test): its widget injects a deterministic token and its
- verify command passes token 'pass' and fails anything else, without any network call. This
- demonstrates the abstraction only — widget dispatch, verify dispatch, normalized results.
- Real providers are separate modules (captchas_turnstile, captchas_hcaptcha,
- captchas_recaptcha, captchas_recaptcha3), each with its own demo app in its own repo.
-{% endcomment %}
-
Captcha demo — Test provider
+ GET /captcha-demo — demonstrates the DEFAULT-CONSTANT best practice from the module README:
+ the provider, site key, and secret are all resolved from instance constants
+ (CAPTCHA_DEFAULT_PROVIDER, CAPTCHA_DEFAULT_SITE_KEY, CAPTCHA_DEFAULT_SECRET), so the widget
+ below is rendered — and the token verified in /captcha-verify — with NO
+ provider/site_key/secret arguments at the call site. This is what exercises
+ helpers/default_site_key and helpers/default_secret (the default-keys feature).
-
- This form uses the repo's fake test provider (app/modules/captchas_test):
- the widget below injects a token that always verifies, with no network call. Submitting posts
- to /captcha-verify, which dispatches to the provider module server-side and shows
- the normalized result. Install a real provider module (e.g. captchas_turnstile)
- and use its demo app to see a real challenge.
-
+ It runs against the FAKE test provider that ships with this repo's example app
+ (app/modules/captchas_test): its widget injects a deterministic token and its verify command
+ passes token 'pass' and fails anything else, with no network call. Point the constants at the
+ 'test' provider (see the setup box below) to try it — any non-blank site key / secret works,
+ since the fake provider ignores their values. Real providers are separate modules
+ (captchas_turnstile, captchas_hcaptcha, captchas_recaptcha, captchas_recaptcha3), each with
+ its own demo app in its own repo.
+{% endcomment %}
+{%- liquid
+ assign default_provider = context.constants.CAPTCHA_DEFAULT_PROVIDER
+ assign default_site_key = context.constants.CAPTCHA_DEFAULT_SITE_KEY
+ assign default_secret = context.constants.CAPTCHA_DEFAULT_SECRET
+ assign configured = false
+ if default_provider != blank and default_site_key != blank and default_secret != blank
+ assign configured = true
+ endif
+-%}
+
Captcha demo — default constants
-
+
+
-
+
+{% endunless %}
diff --git a/pos-module-captchas/app/views/pages/captcha-verify.liquid b/pos-module-captchas/app/views/pages/captcha-verify.liquid
index 585de7a..28c73ea 100644
--- a/pos-module-captchas/app/views/pages/captcha-verify.liquid
+++ b/pos-module-captchas/app/views/pages/captcha-verify.liquid
@@ -4,20 +4,23 @@ method: post
---
{% comment %}
Example-app POST handler for the captcha demo (NOT part of the distributed module).
- The provider is fixed SERVER-SIDE — never read the provider (and therefore the secret)
- from client input: an attacker who controls `provider` could steer verification to a
- weaker path and bypass the captcha. A real integration looks the same, with a real
- provider module installed and the secret read from a constant:
+ Verifies with NO arguments: the provider resolves from CAPTCHA_DEFAULT_PROVIDER and the secret
+ from CAPTCHA_DEFAULT_SECRET (both server-side constants). This is the module README's
+ best-practice call shape and is what exercises helpers/default_secret.
- function result = 'modules/captchas/commands/captcha/verify',
- provider: 'turnstile',
- secret: context.constants.CAPTCHA_TURNSTILE_SECRET
+ Keeping the provider in a server-side constant — never read from client input — is deliberate:
+ an attacker who controls `provider`, and therefore which secret verifies, could steer
+ verification to a weaker path and bypass the captcha. A real integration is identical; just set
+ the CAPTCHA_DEFAULT_* constants for your provider (see /captcha-demo) and call:
- Here the fake test provider verifies deterministically (token 'pass' passes, anything
- else fails), so any non-blank secret works and no network call is made.
+ function result = 'modules/captchas/commands/captcha/verify'
+
+ With the constants unset, verify fails closed (secret_missing / unsupported_provider) and those
+ errors render below — that is the fail-closed behavior, not a bug. Against the fake test
+ provider, token 'pass' passes and anything else fails, with no network call.
{% endcomment %}
{% liquid
- function result = 'modules/captchas/commands/captcha/verify', provider: 'test', secret: 'demo-secret'
+ function result = 'modules/captchas/commands/captcha/verify'
%}
{% if result.valid %}