feat: add Cap CAPTCHA - #12
Merged
Merged
Conversation
There was a problem hiding this comment.
Review completed against the latest diff
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
taobojlen
force-pushed
the
feat/cap-captcha
branch
from
July 24, 2026 17:03
0b6b6c4 to
ba99194
Compare
The README and example.env used 'Authorization: Bot $CAP_ADMIN_KEY' to
create a site key, but Cap's Bot scheme authenticates against
Valkey-stored API keys, not the ADMIN_KEY env var — so that curl always
401s. The correct flow: POST /auth/login with the admin_key to obtain a
session token + hash, then POST /server/keys with a Bearer token
(base64 of {token,hash}). Verified live against tiago2/cap:latest.
Also notes that the production 'cap' service has no host port mapping,
so the curl must run via a temporary ports override or 'docker compose
exec' on the compose network (http://cap:3000).
The final whole-branch review found a production-breaking defect: the
hosting compose hardcoded CAP_INSTANCE_URL=http://cap:3000, but that
value is rendered into the browser's <cap-widget data-cap-api-endpoint>.
http://cap:3000 is Docker-network-internal (unresolvable from a browser)
and would be blocked as mixed content on an https://APP_DOMAIN page.
With Cap 'enabled' the widget would never solve, fail-closing every
register/login/reset POST for real users.
Fix: make the compose use ${CAP_INSTANCE_URL} (not the hardcoded
internal URL), document that it MUST be a browser-reachable HTTPS URL,
and add it to example.env with guidance. The cap service stays
internal-only; the self-hoster puts it behind their own ingress
(e.g. a Caddy route) and points CAP_INSTANCE_URL at the public URL.
Add a cap.{APP_DOMAIN} site block reverse-proxying to the internal cap:3000
service, so the browser-rendered <cap-widget> can reach Cap over HTTPS (the
existing internal-only http://cap:3000 is unresolvable + mixed-content blocked
on https pages).
Default CAP_INSTANCE_URL to https://cap. so the stack works
out-of-the-box once a site key is set, while remaining overridable in .env
for self-hosters who want a path-based or different-domain setup.
cap.{APP_DOMAIN} baked the 'cap.' prefix in, so there was no way to serve
Cap on a different subdomain (e.g. cap.shroud.email when
APP_DOMAIN=app.shroud.email). Introduce CAP_DOMAIN as the single knob:
- Caddyfile: cap.{} -> {$CAP_DOMAIN:disabled.localhost}. The
:disabled.localhost default-token keeps the file valid when CAP_DOMAIN is
unset (a bare {$CAP_DOMAIN} block is invalid Caddyfile and would break the
whole stack for self-hosters who don't use Cap); Caddy issues no cert and
routes no real traffic to the inert block.
- compose: default CAP_DOMAIN to cap. on both web and caddy,
and default CAP_INSTANCE_URL to https://. Nested-default
resolution keeps web + caddy in sync; explicit overrides still win.
- example.env: document CAP_DOMAIN and the now-derived CAP_INSTANCE_URL.
taobojlen
force-pushed
the
feat/cap-captcha
branch
from
July 24, 2026 17:05
ba99194 to
f7970a7
Compare
- Caddyfile.bunny: mirror the cap block from Caddyfile so self-hosters using
Bunny DNS-01 get the Cap route too (with dns bunny issuer, matching the
APP_DOMAIN block).
- docker-compose: stop defaulting CAP_DOMAIN to cap.${APP_DOMAIN}. It was
the only thing making the CAP_INSTANCE_URL default complex
(${CAP_INSTANCE_URL:-https://${CAP_DOMAIN:-cap.${APP_DOMAIN}}}). CAP_DOMAIN
is now a required knob to enable Cap; when unset, Caddy leaves the route
inert via the :disabled.localhost fallback and the app stays disabled.
CAP_INSTANCE_URL simplifies to
${CAP_INSTANCE_URL:-https://${CAP_DOMAIN}}.
- caddy service keeps CAP_DOMAIN=${CAP_DOMAIN} (no default) so Caddy sees
the operator's value or falls back.
- example.env: document CAP_DOMAIN as required, note both TLS paths.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.