Quick 260927-q23: Admin refresh enforces tokens_valid_after and is_activated (CR-01) Summary
Admin POST {prefix}/api/v1/auth/refresh now loads the subject through the backend guard's own provider (lazyBackendUsers) and applies the guard's checks (subjectPrincipal, issuedBeforeCutoff) via the new bouncer.RefreshAudienceFor before it mints a token. A refresh refused for its subject over cookie transport expires summer_admin. So summer admin:reset-password, deactivation and soft-deletion now end a session the SPA keeps refreshing.
The pre-reset cookie subtest failed the same way: the refresh returned status 200 with a cookie body. GREEN: all five subtests pass on Postgres. go vet ./... and go test ./... in summercms.go were green at both commits.
Task 2: removal check
With RefreshAudienceFor temporarily passing nil instead of its check:
Restored with git checkout -- bouncer/refresh.go. git diff --quiet -- bouncer/refresh.go held, and the tests were green again.
Task 3: gates
git show --stat of be4a923 and a13a121 shows no go.mod or go.sum change. ../fonoteka.go has no working-tree change.
bash scripts/check-phase10.sh --go: the third run exited 0 (phase10 go passed, with only the two accepted parity failures TestMigrateSeedsCanonicalGenres and TestSchemaMatchesPHPSnapshot). The first two runs failed on one different fonoteka golem15/user test each: user/classes TestCodes the first time, user TestForgotPassword the second. Each passed when re-run on its own (TestForgotPassword three times). A full standalone fonoteka.go go test -json ./... ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... showed only the two parity failures. Neither test calls refresh or the JWT guard: forgot-password and code issuance are unauthenticated. TestCodes asserts time.Since(issuedAt) <= 2s (codes_test.go:37). Load average was 10 to 14 on 12 cores, and another project's testcontainers Postgres was running at the same time. So these look like load flakes and are not caused by this change. See Deferred Issues.
bash scripts/check-phase10.sh --evidence: exit 0 (table parse, security, postgres, openapi. The admin OpenAPI document and fonoteka docs/openapi.json did not drift).
10-SECURITY-REVIEW.md T-10-05 row: 9 cells. It names RefreshAudienceFor, TestAdminRefreshRevocation, TestRefreshAudienceForSubject and WR-07, and no longer says "valid until logout or expiry".
Both docs are modified and uncommitted.
Deviations from Plan
None in the code. Test-structure choice: each TestAdminRefreshRevocation subtest builds its own adminHandler, so the per-app login throttle (5 per minute) cannot trip across the six logins in the test.
Deferred Issues
fonoteka.go plugins/golem15/user/classes TestCodes and plugins/golem15/user TestForgotPassword each failed once, only under the full parallel check-phase10.sh --go run on a loaded machine. They pass alone and passed on the third gate run. Likely cause: timing sensitivity under load (for example the 2 s time.Since bound in codes_test.go:37). Out of scope here, and there is no code path shared with this change.
Known Stubs
None.
Threat Flags
None. No new endpoint or trust boundary. The refresh endpoint now does one extra indexed primary-key lookup, and only for tokens that passed every signature, audience, window and blacklist check (T-q23-04).
FOUND: RefreshAudienceFor(r.Context(), s.users in cabana/auth.go, users: users in cabana/http.go, subjectPrincipal(r.Context(), g.users in bouncer/jwt.go, issuerURL, nil) twice in bouncer/refresh.go