--- phase: 08-oauth2-1-authorization-server plan: 03 type: execute wave: 3 depends_on: [08-02] files_modified: - 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 autonomous: true requirements: [AUTH-05, AUTH-06, AUTH-07] must_haves: truths: - "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." artifacts: - path: "wristband/authorize.go" provides: "Ordered validation, S256/resource/scope policy, and pending request creation" - path: "../fonoteka.go/plugins/golem15/fonoteka/routes.go" provides: "Assembled raw authorize route" key_links: - from: "plugin.go" to: "wristband.Server.Authorize" via: "configured server retained from persistent DCR slice" pattern: "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. @/home/jin/.codex/get-shit-done/workflows/execute-plan.md @/home/jin/.codex/get-shit-done/templates/summary.md @.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. ## 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. | - Focused wristband and assembled authorize tests pass in the task feedback budget. - 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. Create `.planning/phases/08-oauth2-1-authorization-server/08-03-SUMMARY.md` when done.