Loading
chore(auth): /v2/auth/token handler
Summary
Step 4 of the S08 authentication stub plan: the /v2/auth/token handler.
- Spec: S08 Authentication (stub) —
/v2/auth/tokensection. - Plan: docs/plans/2026-05-12-authentication-stub.md — Step 4.
- Depends on: Step 2 (Validator interface and registry seam, !266 (merged)) — merged.
- Siblings: Step 3 (dispatch middleware, !300 (merged), merged); Step 5 (bootstrap validator + main.go wiring, not yet opened).
What it does
GET /v2/auth/token, Basic credential matches →200. JSON envelope per spec:token,access_token,expires_in: 86400,issued_at(RFC3339). Username on the Basic credential is ignored (spec-mandated).- Missing / empty / malformed / mismatched credential →
401.WWW-Authenticate: Basic realm=\"artifact-registry\", empty body. Spec collapses the four cases into one response (no information leak between them). - Non-GET →
405.Allow: GETheader + S01 envelope (code: \"method_not_allowed\") viainternal/transport.WriteError. Plan-territory decision, not a spec restatement — the spec documentsGETonly; the plan picks 405 by analogy with S01's wrong-method response. - Validator-surfaced
context.Canceled/context.DeadlineExceeded→ skip response. Mirrors the dispatch middleware's cancellation handling — writing a 401 to a closed connection is a stale write that pollutes credential-failure metrics with transport cancellations.
Code reuse
The handler calls the package-private extractCredential(r, SchemeBasic) shipped in Step 3 (alongside the dispatch middleware). Same parsing semantics, same secret.Secret boundary, no duplicated Basic-header parser. No new exported surface beyond auth.TokenHandler.
Spec coverage
Spec: docs/specs/S08-authentication.md
Acceptance criteria
| # | Criterion | Tests |
|---|---|---|
| AC-1 | Dispatch table coverage | Owned by Step 3 (internal/auth/middleware_test.go). Not in scope for Step 4. |
| AC-2 | /v2/auth/token handler — 200 JSON shape on match; 401 + Basic realm=\"artifact-registry\" on missing/empty/malformed/mismatched |
TestTokenHandler_PositiveGETReturnsBootstrapToken, TestTokenHandler_PositiveIgnoresUsername, TestTokenHandler_Negative401 |
| AC-3 | Constant-time comparison in the credential check | Owned by Step 5 (internal/auth/bootstrap/bootstrap_test.go, timing-variance test). Not in scope for Step 4 (the authtest.Fake uses plain ==). |
| AC-4 | Build-tag fence — default build cannot link the bootstrap validator | Owned by Step 5 (scripts/check-no-devstubs.sh, lint:no-devstubs-in-prod CI job). Not in scope for Step 4. |
| AC-5 | Configuration loading — env unset / file missing / file empty / both set / neither set | Owned by Step 1 (internal/secret/resolver_test.go). Not in scope for Step 4. |
Error cases
| # | Condition | Tests |
|---|---|---|
| E-1 | Missing/empty/malformed/mismatched credential on a non-OCI prefix: 401 + challenge + empty body | TestTokenHandler_Negative401 (5 subtests: missing, empty, malformed-scheme, malformed-base64, mismatched) |
| E-2 | Unknown path prefix: 404 + S01 envelope | Owned by Step 3 (TestMiddleware_FallbackNotFound). Not in scope for Step 4 — the handler is only reachable via dispatch. |
Plan-territory additions (not in spec)
| # | Behavior | Tests |
|---|---|---|
| P-1 | Non-GET on /v2/auth/token → 405 + Allow: GET + S01 envelope |
TestTokenHandler_MethodNotAllowed |
| P-2 | Validator-surfaced ctx cancellation skips response (mirrors middleware) | TestTokenHandler_CancellationSkipsResponse |
Security considerations
| # | Concern | Tests |
|---|---|---|
| S-1 | Bootstrap-token validator gated behind development_stubs build tag (the spec's entire security argument) |
Owned by Step 5 (build-tag fence test + CI). Gating is enforced upstream of TokenHandler. |
Test plan
- CI pipeline green
-
go test ./internal/auth/...passes locally (12 new tests inTestTokenHandler_*) - Full suite green locally (456 tests across 15 packages at branch HEAD)
- Reviewer confirms 405-on-non-GET decision matches the rationale in the handler godoc (analogy with S01's wrong-method response)
- Reviewer confirms cancellation skip-response branch matches middleware semantics
Edited by Moaz Khalifa