Skip to content

EU Captcha configuration

EU Captcha is Myra's own GDPR-compliant captcha. It protects the registration form against automated sign-ups (epic AGF-2969). The configuration is database-backed (never environment variables) and has three parts:

Setting Where it is set Exposure
Enable flag (eu_captcha_enabled) Global settings (PUT /admin/v1/settings/eu_captcha_enabled) platform-admin
Public sitekey (eu_captcha_sitekey) Global settings (PUT /admin/v1/settings/eu_captcha_sitekey) platform-admin; the sitekey is public (safe to embed on the registration page)
Server-only secret this endpoint (PUT /admin/v1/eu-captcha/secret) platform-admin; write-only — never returned to anyone, never sent to the browser

The enable flag and the public sitekey are ordinary global settings (see the Global settings API). The secret is a credential, so it is handled separately and never travels on the generic settings surface.

Until all three are set — and the registration widget and server-side verify flow ship (AGF-2986 / AGF-2987) — the feature is a no-op: registration behaves exactly as it does today. It ships dark; turning it on is a deliberate go-live act.

Base URL: https://<your-gateway-host>/admin/v1

Every endpoint requires the platform admin role (permission SETTINGS_MANAGE); a lower role is 403, an unauthenticated caller 401. Access is re-gated server-side — the client is never the authorization boundary.

How the secret is protected

  • Stored encrypted at rest. The secret is encrypted with the deployment's application master key before it is written to the database; only the ciphertext is ever persisted.
  • Never returned. No endpoint — not this one, not the generic settings list — ever returns the secret. The generic GET /admin/v1/settings lists every settings row verbatim, so a platform admin will see an opaque ciphertext blob for eu_captcha_secret there; the plaintext is never exposed.
  • Not a generic setting. eu_captcha_secret is deliberately not an editable global-settings key, so PUT /admin/v1/settings/eu_captcha_secret is an unknown key → 404. A plaintext credential can never be written through, or echoed by, the generic settings surface.
  • Audited without the value. Setting the secret writes an audit event recording only that it was set (secret_set: true) — never the value or its ciphertext.

GET /admin/v1/eu-captcha/secret

Reports only whether a secret is configured — never the value.

Response 200

{ "secret_set": true }

secret_set is false when no secret has been stored.

PUT /admin/v1/eu-captcha/secret

Sets (or replaces) the server-only secret. The value is validated, encrypted, and stored; the response never echoes it.

Accepted body

{ "secret": "<the EU Captcha secret>" }

The secret must be an opaque token: a string, non-empty, at most 4096 bytes, containing no whitespace and no control characters (Base64 characters such as + / = are allowed). There is no "clear" operation — to turn the feature off, set eu_captcha_enabled to false; a stored secret can only be overwritten, not deleted, so a stale ciphertext row may remain after the feature is disabled (harmless — it is never read while the feature is off).

Response 200

{ "ok": true, "secret_set": true }

Rejected (fail closed — nothing is persisted)

  • Not a platform admin → 403.
  • Missing secret, null, or a non-string / empty / over-4096-byte value, or any value containing whitespace or a control character → 400.
  • The application master key is unavailable (so the secret cannot be encrypted), or encryption fails → 500. Nothing is written.

Fail-closed behaviour (contract for the verify flow)

The server-side read helper exposes a public shape to the registration page containing only { enabled, sitekey } — never the secret. "Enabled" is best-effort: it is true only when the flag is on and a sitekey is set and a secret row is present. A present-but-undecryptable secret (for example after a master-key rotation) keeps the feature enabled so the verify flow (AGF-2987) rejects sign-ups, rather than silently disabling the captcha and letting them through. The verify flow is therefore the authoritative gate: it must reject any request whose captcha response cannot be verified, and must treat a missing/undecryptable secret as "reject all".