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.
@.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.