fix(catalyst-api): make /api/v1/subdomains/check public (no auth required) (#1008)

* deploy: re-bump chart literal :b45a49f → :8ec8c01 (mistake-rollback fix)

PR #1006 rolled back to :b45a49f because the catalyst-api pod was
ImagePullBackOff for ~30s while pulling :8ec8c01. The image was IN
GHCR; the pull just took time. Pod recovered to Running on :8ec8c01,
THEN my rollback kicked in and reverted to :b45a49f — losing the
wizard credentials fix from PR #1004 that the founder needed.

Re-bump forward. :8ec8c01 contains useSubdomainAvailability's
credentials:'include' fix that closes the wizard 401 → false-502.

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

* fix(catalyst-api): make /api/v1/subdomains/check public (no session required)

The wizard's Domain step renders BEFORE the operator authenticates —
PIN issue + verify happen AFTER they pick a subdomain. Requiring a
session cookie on /api/v1/subdomains/check forced 401 on every
anonymous visitor and trapped logged-out operators in a 'check
unavailable' state.

Move the route from rg (session-gated) to r (unauthenticated). Same
model as /auth/pin/issue: read-only public-facing endpoint with no
state change. Information disclosure is negligible — 'is this
subdomain taken?' is what DNS itself answers to anyone with a
resolver.

The handler routes to PDM (managed pool) or DNS (BYO); both are
read-only. PDM has its own rate-limiting middleware on the public
ingress, so anonymous spam is bounded by that.

Closes the wizard 401 the founder hit on otech119 Domain step.

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

---------

Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
e3mrah 2026-05-05 23:59:28 +04:00 committed by GitHub
parent 5e3df8eeb8
commit a1b30ccc28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -216,6 +216,17 @@ func main() {
// or stale magic-link emails degrade gracefully.
r.Post("/api/v1/auth/pin/issue", h.HandlePinIssue)
r.Post("/api/v1/auth/pin/verify", h.HandlePinVerify)
// /api/v1/subdomains/check — public, read-only availability query.
// Same model as a username-availability check on a signup form: an
// anonymous visitor lands on the wizard's Domain step BEFORE they
// authenticate (PIN issue happens AFTER they pick a subdomain), so
// requiring a session cookie here would block the only flow that
// matters. The handler routes the call to PDM (managed pool) or to
// a DNS lookup (BYO) — both are read-only with no state change and
// negligible information disclosure ("is this subdomain taken?"
// is the same answer DNS itself surfaces to anyone).
r.Post("/api/v1/subdomains/check", h.CheckSubdomain)
r.Get("/api/v1/auth/callback", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/login?error=flow_changed", http.StatusFound)
})
@ -466,7 +477,6 @@ func main() {
// confirms the operator-supplied keys can authenticate against the
// chosen region's S3 endpoint via ListBuckets.
rg.Post("/api/v1/credentials/object-storage/validate", h.ValidateObjectStorageCredentials)
rg.Post("/api/v1/subdomains/check", h.CheckSubdomain)
// SSH keypair generator — wizard's "auto-generate" Mode A path
// (issue #160). Returns publicKey + privateKey + fingerprint; the
// handler logs ONLY the fingerprint and never persists either half.