Follow-up to PR #8 (JWT revocation + refresh), addressing the two
in-scope follow-ups called out in the completion-sequence summary on
PR #11:
1. swagger.yaml pre-dated /api/v1/auth/refresh and /api/v1/auth/logout
- client generators could not pick them up.
2. Those handlers were covered by unit tests on the WalletAuth layer
and by the e2e-full Playwright spec, but had no HTTP-level unit
tests - regressions at the mux/handler seam (wrong method,
missing walletAuth, unregistered route) were invisible to
go test ./backend/api/rest.
Changes:
backend/api/rest/swagger.yaml:
- New POST /api/v1/auth/refresh entry under the Auth tag.
Uses bearerAuth, returns the existing WalletAuthResponse on 200,
401 via components/responses/Unauthorized, 503 when the auth
storage or the jwt_revocations table from migration 0016 is
missing. Description calls out that legacy tokens without a jti
cannot be refreshed.
- New POST /api/v1/auth/logout entry. Same auth requirement;
returns {status: ok} on 200; 401 via Unauthorized; 503 when
migration 0016 has not run. Description names the jwt_revocations
table explicitly so ops can correlate 503s with the migration.
- Both slot in alphabetically between /auth/wallet and /auth/register
so the tag block stays ordered.
backend/api/rest/auth_refresh_internal_test.go (new, 8 tests):
- TestHandleAuthRefreshRejectsGet - GET returns 405 method_not_allowed.
- TestHandleAuthRefreshReturns503WhenWalletAuthUnconfigured -
walletAuth nil, POST with a Bearer header returns 503 rather
than panicking (guards against a regression where someone calls
s.walletAuth.RefreshJWT without the nil-check).
- TestHandleAuthLogoutRejectsGet - symmetric 405 on GET.
- TestHandleAuthLogoutReturns503WhenWalletAuthUnconfigured -
symmetric 503 on nil walletAuth.
- TestAuthRefreshRouteRegistered - exercises SetupRoutes and
confirms POST /api/v1/auth/refresh and /api/v1/auth/logout are
registered (i.e. not 404). Catches regressions where a future
refactor drops the mux.HandleFunc entries for either endpoint.
- TestAuthRefreshRequiresBearerToken +
TestAuthLogoutRequiresBearerToken - sanity-check that a POST
with no Authorization header resolves to 401 or 503 (never 200
or 500).
- decodeErrorBody helper extracts ErrorDetail from writeError's
{"error":{"code":...,"message":...}} envelope, so asserts
on body["code"] match the actual wire format (not the looser
{"error":"..."} shape).
- newServerNoWalletAuth builds a rest.Server with JWT_SECRET set
to a 32-byte string of 'a' so NewServer's fail-fast check from
PR #3 is happy; nil db pool is fine because the tests do not
exercise any DB path.
Verification:
cd backend && go vet ./... clean
cd backend && go test ./api/rest/ pass (17 tests; 7 new)
cd backend && go test ./... pass
Out of scope: the live credential rotation in the third follow-up
bullet requires infra access (database + SSH + deploy pipeline) and
belongs to the operator.