Files
summercms/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-10-PLAN.md
2026-09-20 16:14:21 +02:00

7.6 KiB

phase: 06-http-routing-auth-groups-and-rate-limiting plan: 10 type: execute wave: 6 depends_on: ["06-06"] files_modified: - fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go - fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_test.go autonomous: true gap_closure: true requirements: [HTTP-05, HTTP-06] must_haves: truths: - "InvScope without a resolved user returns byte-exact 401 body {"error":"Invalid token"} with no trailing newline" - "InvScope with a token missing the requested scope returns byte-exact 403 body {"error":"Missing required scope: "} with no trailing newline" - "Tests compare raw recorder bytes directly and cannot hide whitespace with TrimSpace" - "A correctly scoped token still reaches the next handler unchanged" artifacts: - path: fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go provides: "InvScope 401/403 responses delegated to framework wire.WriteJSON" - path: fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_test.go provides: "exact raw-byte assertions for both TokenScope denial branches" key_links: - from: fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go to: summercms.go/wire/response.go via: "both denial branches call wire.WriteJSON, the shared PHP-compatible no-newline writer" pattern: "wire.WriteJSON" Restore PHP byte parity for personal-token scope denial by using the shared no-newline JSON writer and making the 401/403 tests compare exact raw bytes.

Purpose: the guard registry and response-convention contracts are only satisfied when TokenScope responses are byte-identical; json.Encoder.Encode adds a byte PHP does not send. Output: InvScope delegated to wire.WriteJSON and exact-byte denial tests that cannot mask the regression.

<execution_context> @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-CONTEXT.md @.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-VERIFICATION.md @.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-REVIEW.md @.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-01-SUMMARY.md @.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-03-SUMMARY.md Task 1: Emit and assert exact no-newline InvScope denial bodies fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go, fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_test.go - No bouncer.User context -> 401, Content-Type application/json, raw body bytes exactly `{"error":"Invalid token"}`. - Resolved user with an ApiToken lacking `write` -> 403, Content-Type application/json, raw body bytes exactly `{"error":"Missing required scope: write"}`. - Both bodies have final byte `}` and contain no `\n` or `\r`; tests perform no trimming or whitespace normalization. - Resolved user plus a token containing the requested scope -> next handler runs and returns its original 204 response. fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go (current local json.Encoder writer) fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_test.go (assertErrorBody currently hides the newline with strings.TrimSpace) fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_coverage_test.go (wrong-credential fail-closed path that reuses the assertion helper) summercms.go/wire/response.go (`wire.WriteJSON` exact behavior: SetEscapeHTML(false), trailing newline removed) /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/middleware/TokenScope.php (source-of-truth 401/403 payloads) .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-CONTEXT.md (D-08 exact TokenScope bodies) .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-VERIFICATION.md (fourth authoritative gap) .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-REVIEW.md (WR-11) In `token_scope.go`, remove the local `writeJSON` helper and its `encoding/json` import. Import `git.golem15.com/golem15/summercms/wire` and call `wire.WriteJSON` in both denial branches with the existing statuses and exact `map[string]string` payloads. Do not change user/credential lookup, fail-closed wrong-credential behavior, scope text, status codes, or middleware ordering (D-08).
In `token_scope_test.go`, remove `strings.TrimSpace` and compare `rec.Body.Bytes()` or `rec.Body.String()` directly to the expected literal. Keep JSON decoding only as a secondary shape/type assertion after the exact-byte comparison; it must not replace or normalize the wire assertion. Add explicit checks that neither denial body ends in newline/carriage-return. Ensure the named no-user 401 and missing-scope 403 subtests each make their own exact expected-body assertion. Keep the read-scope success case and the wrong-credential 403 coverage green.
cd /media/nvme/dev/golem15/summercms.io/summercms/fonoteka.go && go test ./plugins/golem15/fonoteka/middleware -run 'TestInvScope' -count=1 -race -short && go vet ./plugins/golem15/fonoteka/middleware && go test ./plugins/golem15/fonoteka/... -count=1 -short - `token_scope.go` imports and calls `wire.WriteJSON`; it contains no `json.NewEncoder` or local response writer. - The 401 raw body equals exactly `{"error":"Invalid token"}` and the 403 raw body equals exactly `{"error":"Missing required scope: write"}`. - `token_scope_test.go` contains no `TrimSpace`, `Trim`, or normalized-body comparison on either denial path. - Both denial tests retain exact status and Content-Type assertions, while the valid-scope next-handler and wrong-credential fail-closed tests pass. InvScope's 401 and 403 are byte-identical to PHP TokenScope, with exact tests that fail on any trailing newline.

<threat_model>

Trust Boundaries

Boundary Description
personal token context -> HTTP denial Authentication/scope state crosses into a public wire response whose status and bytes are part of the PHP compatibility contract

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-06-27 Tampering InvScope 401/403 serialization mitigate Use the established wire.WriteJSON implementation and raw-byte assertions for both branches; forbid trimming in the regression tests
T-06-SC Tampering package supply chain accept No install or manifest change; wire is an existing framework package already used by the phase
</threat_model>
Run exact InvScope tests under `-race`, then the full fonoteka plugin suite. Grep the production and test files to prove `json.NewEncoder` and TrimSpace are absent from the TokenScope path.

<success_criteria>

  • Personal-token 401 and 403 bodies contain no trailing newline and match PHP bytes exactly.
  • Tests compare raw bytes before optional JSON shape checks.
  • Scope authorization and fail-closed credential behavior are unchanged.
  • The fonoteka middleware and plugin suites pass. </success_criteria>
Create `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-10-SUMMARY.md` when done.