Skip to content
Open
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
7 changes: 7 additions & 0 deletions pos-module-captchas-hcaptcha/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.1.0

- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
to them. Requires `captchas ^1.1.0`.
- Other code improvements: internal cleanups with no change to behavior or the public API.

## 1.0.0

Initial release. hCaptcha provider for the
Expand Down
3 changes: 3 additions & 0 deletions pos-module-captchas-hcaptcha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ non-JSON bodies (`modules/captchas/errors.request_failed`). A `success: false` a
`modules/captchas/errors.verification_failed`. `expected_hostname` (handled by the
abstraction) works with the `hostname` field hCaptcha returns.

Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
is set — `result.errors.captcha` holds display-ready text, not translation keys.

> ⚠️ **Billing** — the server-side verify call is a normal, billable platformOS API call;
> each `verify` performs one outbound `siteverify` request.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,21 @@
function r_fail = 'modules/captchas/commands/captcha/verify', provider: 'hcaptcha', secret: '0x0000000000000000000000000000000000000000', token: 'not-a-real-token'
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'garbage_token_fails', object: r_fail
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r_fail.errors

# The message is stored translated, not as a raw key: this provider module sets the error via
# modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
# modules/captchas_hcaptcha. Callers render errors verbatim, so a raw key would leak to users.
# Reuses r_fail — no extra billable call.
assign fail_key = 'modules/captchas/errors.verification_failed'
assign fail_message = r_fail.errors.captcha | first
assign expected_message = fail_key | t
function contract = 'modules/tests/assertions/equal', contract: contract, given: fail_message, expected: expected_message, field_name: 'garbage_token_message_translated'

# The check above alone would pass if `| t` silently returned the key on both sides, so assert
# the stored value is not the key itself — that is the failure mode this guards against.
assign is_raw_key = false
if fail_message == fail_key
assign is_raw_key = true
endif
function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'garbage_token_message_not_raw_key'
%}
8 changes: 6 additions & 2 deletions pos-module-captchas-hcaptcha/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ method: post
<p>Submitted message: {{ context.params.message }}</p>
{% else %}
<h1>❌ Captcha failed ({{ result.provider }})</h1>
{% comment %}
result.errors holds already-translated messages (the commands translate at set time, via
modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
{% endcomment %}
<ul>
{% for error in result.errors %}
{% for key in error[1] %}
<li>{{ key | t }}</li>
{% for message in error[1] %}
<li>{{ message }}</li>
{% endfor %}
{% endfor %}
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@
@param {object} object - validated object: { secret, token, remote_ip, expected_sitekey, errors }.
expected_sitekey (optional) is forwarded as hCaptcha's `sitekey` param — hCaptcha secrets are
account-wide, so this is what stops a token issued for another sitekey from being redeemed here.
@returns {object} object with success (boolean), response (hash), valid, and errors.
@returns {object} object with success (boolean), response (hash), valid, and errors
(translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = '{}' | parse_json
assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip, sitekey: object.expected_sitekey
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip, "sitekey": object.expected_sitekey }

graphql result = 'modules/captchas_hcaptcha/api_call/send', data: payload, name: 'modules/captchas_hcaptcha/siteverify'

assign call = result.api_call_send
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors

if call.errors != blank
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
endif

assign status = call.response.status
if status != 200
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand All @@ -39,8 +38,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand All @@ -53,8 +52,8 @@
if body.success
assign object.valid = true
else
assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
assign object.errors = updated_errors
assign object.valid = false
endif

Expand Down
4 changes: 2 additions & 2 deletions pos-module-captchas-hcaptcha/pos-module.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas hCaptcha",
"machine_name": "captchas_hcaptcha",
"version": "1.0.0",
"version": "1.1.0",
"dependencies": {
"captchas": "^1.0.0"
"captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
Expand Down
7 changes: 7 additions & 0 deletions pos-module-captchas-recaptcha/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.1.0

- Errors are now recorded with `modules/captchas/helpers/add_error`, so `result.errors` holds
translated messages instead of `modules/captchas/errors.*` keys — drop any `| t` you apply
to them. Requires `captchas ^1.1.0`.
- Other code improvements: internal cleanups with no change to behavior or the public API.

## 1.0.0

Initial release. Google reCAPTCHA v2 (checkbox / invisible) provider for the
Expand Down
3 changes: 3 additions & 0 deletions pos-module-captchas-recaptcha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ responses, and non-JSON bodies (`modules/captchas/errors.request_failed`). A
`expected_hostname` (handled by the abstraction) works with the `hostname` field
reCAPTCHA returns.

Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
is set — `result.errors.captcha` holds display-ready text, not translation keys.

> ⚠️ **Billing** — the server-side verify call is a normal, billable platformOS API call;
> each `verify` performs one outbound `siteverify` request.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,21 @@
function r_fail = 'modules/captchas/commands/captcha/verify', provider: 'recaptcha', secret: 'not-a-real-secret', token: 'dummy-token-for-test-keys'
function contract = 'modules/tests/assertions/not_valid_object', contract: contract, field_name: 'bad_secret_fails', object: r_fail
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r_fail.errors

# The message is stored translated, not as a raw key: this provider module sets the error via
# modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
# modules/captchas_recaptcha. Callers render errors verbatim, so a raw key would leak to users.
# Reuses r_fail — no extra billable call.
assign fail_key = 'modules/captchas/errors.verification_failed'
assign fail_message = r_fail.errors.captcha | first
assign expected_message = fail_key | t
function contract = 'modules/tests/assertions/equal', contract: contract, given: fail_message, expected: expected_message, field_name: 'bad_secret_message_translated'

# The check above alone would pass if `| t` silently returned the key on both sides, so assert
# the stored value is not the key itself — that is the failure mode this guards against.
assign is_raw_key = false
if fail_message == fail_key
assign is_raw_key = true
endif
function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'bad_secret_message_not_raw_key'
%}
8 changes: 6 additions & 2 deletions pos-module-captchas-recaptcha/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ method: post
<p>Submitted message: {{ context.params.message }}</p>
{% else %}
<h1>❌ Captcha failed ({{ result.provider }})</h1>
{% comment %}
result.errors holds already-translated messages (the commands translate at set time, via
modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
{% endcomment %}
<ul>
{% for error in result.errors %}
{% for key in error[1] %}
<li>{{ key | t }}</li>
{% for message in error[1] %}
<li>{{ message }}</li>
{% endfor %}
{% endfor %}
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,28 @@
api_call_send and normalize the result. Part of the captchas provider contract —
dispatched by modules/captchas/commands/captcha/verify/execute.
@param {object} object - validated object: { secret, token, remote_ip, errors }.
@returns {object} object with success (boolean), response (hash), valid, and errors.
@returns {object} object with success (boolean), response (hash), valid, and errors
(translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = '{}' | parse_json
assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }

graphql result = 'modules/captchas_recaptcha/api_call/send', data: payload, name: 'modules/captchas_recaptcha/siteverify'

assign call = result.api_call_send
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors

if call.errors != blank
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
endif

assign status = call.response.status
if status != 200
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand All @@ -37,8 +36,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand All @@ -51,8 +50,8 @@
if body.success
assign object.valid = true
else
assign messages = 'modules/captchas/errors.verification_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.verification_failed'
assign object.errors = updated_errors
assign object.valid = false
endif

Expand Down
4 changes: 2 additions & 2 deletions pos-module-captchas-recaptcha/pos-module.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "pOS Captchas reCAPTCHA v2",
"machine_name": "captchas_recaptcha",
"version": "1.0.0",
"version": "1.1.0",
"dependencies": {
"captchas": "^1.0.0"
"captchas": "^1.1.0"
},
"devDependencies": {
"tests": "^1.3.4"
Expand Down
8 changes: 8 additions & 0 deletions pos-module-captchas-recaptcha3/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.1.0

- Errors are now recorded with `modules/captchas/helpers/add_error` (in both `commands/verify`
and `helpers/score_check`), so `result.errors` holds translated messages instead of
`modules/captchas/errors.*` keys — drop any `| t` you apply to them. Requires
`captchas ^1.1.0`.
- Other code improvements: internal cleanups with no change to behavior or the public API.

## 1.0.0

Initial release. Google reCAPTCHA v3 (invisible, score-based) provider for the
Expand Down
5 changes: 4 additions & 1 deletion pos-module-captchas-recaptcha3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,15 @@ challenge fails client-side, the form submits token-less and verify fails closed
and non-JSON bodies (`modules/captchas/errors.request_failed`), then applies the score/action
gate (`modules/captchas_recaptcha3/helpers/score_check`):

| Outcome | Error key |
| Outcome | Message key |
|---|---|
| provider `success` false/missing | `modules/captchas/errors.verification_failed` |
| action ≠ `expected_action` | `modules/captchas/errors.action_mismatch` |
| score < `min_score` | `modules/captchas/errors.low_score` |

Errors are stored via `modules/captchas/helpers/add_error`, which translates the message as it
is set — `result.errors.captcha` holds display-ready text, not the keys above.

`expected_hostname` (handled by the abstraction) works with the `hostname` field the
response includes.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
function contract = 'modules/tests/assertions/equal', contract: contract, given: r2.valid, expected: false, field_name: 'low_score_fails'
function contract = 'modules/tests/assertions/presence', contract: contract, field_name: 'captcha', object: r2.errors

# The message is stored translated, not as a raw key: score_check sets the error via
# modules/captchas/helpers/add_error, which resolves a modules/captchas/* key from inside
# modules/captchas_recaptcha3. Callers render errors verbatim, so a raw key would leak to users.
assign low_score_key = 'modules/captchas/errors.low_score'
assign low_score_message = r2.errors.captcha | first
assign expected_message = low_score_key | t
function contract = 'modules/tests/assertions/equal', contract: contract, given: low_score_message, expected: expected_message, field_name: 'low_score_message_translated'

# The check above alone would pass if `| t` silently returned the key on both sides, so assert
# the stored value is not the key itself — that is the failure mode this guards against.
assign is_raw_key = false
if low_score_message == low_score_key
assign is_raw_key = true
endif
function contract = 'modules/tests/assertions/equal', contract: contract, given: is_raw_key, expected: false, field_name: 'low_score_message_not_raw_key'

# a custom min_score lowers the bar
function r3 = 'modules/captchas_recaptcha3/helpers/score_check', body: body_low, min_score: 0.2, errors: no_errors
function contract = 'modules/tests/assertions/equal', contract: contract, given: r3.valid, expected: true, field_name: 'custom_min_score'
Expand Down
8 changes: 6 additions & 2 deletions pos-module-captchas-recaptcha3/app/views/pages/verify.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ method: post
<p>Submitted message: {{ context.params.message }}</p>
{% else %}
<h1>❌ Captcha failed ({{ result.provider }})</h1>
{% comment %}
result.errors holds already-translated messages (the commands translate at set time, via
modules/captchas/helpers/add_error), so they render as-is — no `| t` here.
{% endcomment %}
<ul>
{% for error in result.errors %}
{% for key in error[1] %}
<li>{{ key | t }}</li>
{% for message in error[1] %}
<li>{{ message }}</li>
{% endfor %}
{% endfor %}
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@
modules/captchas/commands/captcha/verify/execute.
@param {object} object - validated object: { secret, token, remote_ip, errors, min_score, expected_action }.
min_score defaults to 0.5; expected_action is optional.
@returns {object} object with success (boolean), score (number), action (string), response (hash), valid, and errors.
@returns {object} object with success (boolean), score (number), action (string), response (hash),
valid, and errors (translated messages, via modules/captchas/helpers/add_error).
{% enddoc %}
{% liquid
assign payload = '{}' | parse_json
assign payload = payload | hash_merge: secret: object.secret, token: object.token, remote_ip: object.remote_ip
assign payload = { "secret": object.secret, "token": object.token, "remote_ip": object.remote_ip }

graphql result = 'modules/captchas_recaptcha3/api_call/send', data: payload, name: 'modules/captchas_recaptcha3/siteverify'

assign call = result.api_call_send
function c = 'modules/captchas/helpers/errors_hash', errors: object.errors

if call.errors != blank
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
endif

assign status = call.response.status
if status != 200
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand All @@ -40,8 +39,8 @@
assign raw_body = call.response.body | strip
assign first_char = raw_body | slice: 0, 1
if first_char != '{'
assign messages = 'modules/captchas/errors.request_failed' | split: '|'
assign object.errors = c | hash_merge: captcha: messages
function updated_errors = 'modules/captchas/helpers/add_error', errors: object.errors, field_name: 'captcha', key: 'modules/captchas/errors.request_failed'
assign object.errors = updated_errors
assign object.success = false
assign object.valid = false
return object
Expand Down
Loading
Loading