Skip to content

Add ALTCHA challenge provider (Fixes #72)#76

Open
sean-e-dietrich wants to merge 1 commit into
2.xfrom
72-altcha-provider
Open

Add ALTCHA challenge provider (Fixes #72)#76
sean-e-dietrich wants to merge 1 commit into
2.xfrom
72-altcha-provider

Conversation

@sean-e-dietrich
Copy link
Copy Markdown
Contributor

Summary

  • New Kanopi\Firewall\Challenge\AltchaChallengeProvider — stateless ALTCHA v2 provider. Server pre-computes {algorithm, challenge, maxnumber, salt, signature} and embeds it in the widget via challengejson=; salt carries ?expires=<unix>; signature is TokenManager::sign(challenge). Verify decodes the base64 altcha payload, re-hashes salt + number, checks the HMAC, and rejects expired salts.
  • Registered as 'altcha' in ChallengeProviderFactory::BUILTINS — same shape as the existing math builtin.
  • example/demo/ grows a fourth route (/secure-altcha) via path-based dispatch in index.php to a second config (config.altcha.yml). Only one challenge.provider is configurable per Firewall instance, so the demo runs two instances in sequence based on which path is being requested. Distinct submit path / cookie / header per provider so the two pass tokens coexist.
  • Docs: README "Built-in providers" subsection now covers both math and altcha; example/config.notes.yml lists altcha alongside math.

Test plan

  • tests/Unit/Challenge/AltchaChallengeProviderTest.php — 13 cases: render shape + escaping, accepts solved payload, rejects tampered challenge / wrong number / bad signature / expired salt / no-expiry salt / wrong algorithm / non-base64 / non-JSON / empty payload.
  • ChallengeProviderFactoryTest::testResolvesAltchaShortName — short-name resolution.
  • Full suite still passes (the pre-existing 6 Redis-extension errors are unrelated to this branch).
  • PHPStan and PHPCS clean on touched files.
  • Manual end-to-end: GET /secure-altcha → 200 with widget; POST garbage to /_firewall/challenge-altcha → 400 invalid_solution; POST solved payload → 200 + fw_challenge_altcha_pass cookie; reload with that cookie → success page.

Notes for the reviewer

  • script type="module" on the widget bundle is required — the official ALTCHA altcha.min.js distribution ships as an ES module; a classic <script> tag fails with Unexpected token 'export'.
  • The widget bundle is loaded from cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js. If a self-hosted bundle is preferred down the line, that string is a class constant and can be extracted to config without API changes.
  • Both providers' REDIRECT_FIELD / TTL_FIELD constants are named identically (redirect_to / ttl) so Firewall::handleChallengeSubmission() doesn't need to know which provider is wired up — it reads a shared field name regardless.

🤖 Generated with Claude Code

Second built-in ChallengeProviderInterface alongside math: embeds the
ALTCHA v2 widget with a pre-computed challenge so the server stays
stateless, brute-forces SHA-256(salt + N) == challenge in-browser, and
verifies with an HMAC signature on the challenge value via the existing
TokenManager. The salt embeds an ?expires=<unix> query string so a
precomputed solution can't be replayed indefinitely.

Demo gets a /secure-altcha route via path-based dispatch in index.php
(two configs since only one challenge.provider is configurable per
Firewall instance), with distinct challenge submit path, cookie name,
and header name so the math and altcha pass tokens coexist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants