Files
summercms/.planning/phases/08-oauth2-1-authorization-server/08-03-PLAN.md
2026-09-23 17:46:38 +02:00

9.1 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
08-oauth2-1-authorization-server 03 execute 3
08-02
wristband/authorize.go
wristband/authorize_test.go
../fonoteka.go/plugins/golem15/fonoteka/plugin.go
../fonoteka.go/plugins/golem15/fonoteka/routes.go
../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go
true
AUTH-05
AUTH-06
AUTH-07
truths artifacts key_links
D-02: Authorize reads query only and validates the client and exact redirect before any redirect response.
D-05: S256, scope/resource policy, ordered RFC3986 errors, and pending-request creation live in wristband.
D-09: Authorize is connector-visible on the raw route surface without house/auth middleware.
D-10: No oauth guard is registered; OAuth access remains on inv_token.
path provides
wristband/authorize.go Ordered validation, S256/resource/scope policy, and pending request creation
path provides
../fonoteka.go/plugins/golem15/fonoteka/routes.go Assembled raw authorize route
from to via pattern
plugin.go wristband.Server.Authorize configured server retained from persistent DCR slice oauth/mcp/authorize
Deliver an assembled connector-visible authorize-request slice that creates durable pending consent state with exact PKCE/redirect/scope/resource behavior.

Purpose: Let a connector start authorization immediately after persistent DCR, leaving only consent and exchange for subsequent slices. Output: Wristband authorize handler, assembled raw route, pending-request persistence, and exact unit/integration tests.

<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/ROADMAP.md @.planning/STATE.md @.planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md @.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md @.planning/phases/08-oauth2-1-authorization-server/08-02-SUMMARY.md Task 1: Specify authorize validation and assembled pending-request behavior in RED wristband/authorize.go, wristband/authorize_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go .planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md .planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md wristband/server.go wristband/stores.go /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthAuthorizeController.php /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/models/OAuthClient.php /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/functional/OAuthAuthorizeTest.php - Unknown/unusable client and unregistered redirect return exact local text/plain 400 with no Location. - Later failures redirect with ordered `error`, `error_description`, `iss`, optional `state` using RFC3986 bytes. - Valid S256 request stores one pending row and redirects to `/connect?request=<opaque>`; exact framework/app RED tests are the only failures. D-02/D-04/D-05/D-18: define compiling authorize seams, deterministic unit cases, and an assembled real-Postgres case. Preserve exact validation order: usable client, exact redirect, `response_type=code`, `code_challenge_method=S256`, verifier syntax/challenge, scope ceiling, resource, pending creation. Build redirects from ordered pairs, never `url.Values.Encode`. Use exact `TestPhase8RedAuthorize`/`PHASE8_RED:authorize` and `TestPhase8RedAuthorizeApp`/`PHASE8_RED:authorize-app` JSON verifier invocations; reject every unexpected failing action/package/test and non-behavior failure. scripts/check-phase8-red.sh go PHASE8_RED:authorize git.golem15.com/golem15/summercms/wristband TestPhase8RedAuthorize -- go test -json ./wristband -run '^TestPhase8RedAuthorize$' -count=1 && scripts/check-phase8-red.sh go PHASE8_RED:authorize-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedAuthorizeApp -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedAuthorizeApp$' -count=1" - Both RED invocations select exactly one named test in one named package and reject compile/setup/panic/no-test/unrelated failures. - Tables assert exact status, Content-Type, body, Location absence/presence, parameter order, `%20` encoding, optional state placement, and no credential/request-handle logging. - The assembled RED test uses the real boot/router/Postgres seams and fails only because authorize is not mounted/implemented. Executable RED evidence completely specifies the connector-visible authorize contract. Task 2: Implement and mount exact authorize request creation wristband/authorize.go, wristband/authorize_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go wristband/authorize_test.go ../fonoteka.go/plugins/golem15/fonoteka/oauth_authorize_test.go ../fonoteka.go/plugins/golem15/fonoteka/plugin.go ../fonoteka.go/plugins/golem15/fonoteka/routes.go /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthAuthorizeController.php /media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/classes/auth/OAuthCodeManager.php ../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml - Valid requests persist a hash-only opaque pending handle with requested/ceiling scopes, exact redirect/resource/client binding, challenge, state, and 600s expiry. - Scope output preserves PHP order and never exceeds the registered ceiling; resource mismatch cannot create pending state. - Assembled authorize route is raw and metadata/DCR remain unchanged. D-02/D-03/D-04/D-05/D-06: implement query-only parsing, exact ordered validation/redirects, constant-time S256 verification seam, scope/resource policy, opaque pending creation, and 600-second expiry using the configured server/backend from 08-02. D-09: mount GET `/oauth/mcp/authorize` raw with no middleware. D-10/D-12: register no oauth guard and add no backend Bearer/resource metadata. Preserve metadata/register behavior byte-for-byte and prove invalid requests create no rows. go test ./wristband -run '^Test(Authorize|OrderedRedirect|PKCE)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestOAuthAuthorizeAssembled$' -count=1) - Unknown client/unregistered redirect have no Location; each later error has exact ordered RFC3986 Location bytes including issuer and optional state. - Missing/plain/malformed S256, excess/unknown scope, wrong resource, query/body ambiguity, and unusable client create zero pending rows. - A valid assembled request creates one durable hash-only pending row with 600s expiry and redirects to the configured `/connect?request=` URL; raw route inspection is clean. - 08-01 metadata and 08-02 DCR exact-byte tests remain green. An unchanged connector can register and start a PKCE-bound authorization request through the assembled production router.

<threat_model>

Trust Boundaries

Boundary Description
Connector → raw authorize Untrusted query data requests a durable consent transaction.
Validated redirect → Location Client-controlled redirect is trusted only after exact allow-list match.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-08-PKCE Spoofing/Elevation authorize mitigate Mandatory S256 syntax/policy and bound pending state.
T-08-OPEN-REDIRECT Spoofing/Disclosure authorize mitigate Exact redirect validation before any Location.
T-08-SCOPE-CEILING Elevation authorize mitigate Requested scopes intersect the registered ceiling before persistence.
T-08-SURFACE Elevation route groups mitigate Assembled route-table test for exact middleware.
T-08-SC Tampering dependencies mitigate No new package.
</threat_model>
- Focused wristband and assembled authorize tests pass in the task feedback budget.

<success_criteria>

  • A registered connector can create a durable PKCE-bound pending authorization request through the real app.
  • All local/redirect error bytes and raw-route boundaries match PHP exactly. </success_criteria>
Create `.planning/phases/08-oauth2-1-authorization-server/08-03-SUMMARY.md` when done.