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

14 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 09 execute 8
08-07
08-08
../fonoteka.go/parity/oauth_flow_test.go
../fonoteka.go/parity/capture_clients.mjs
../fonoteka.go/parity/capture-rules.yaml
../fonoteka.go/parity/fixtures/mcp/mcp-lifecycle.yaml
../fonoteka.go/parity/manifest.yaml
../fonoteka.go/parity/check_corpus.go
scripts/check-phase8.sh
true
AUTH-05
AUTH-06
AUTH-07
truths artifacts key_links
D-13: All four raw OAuth routes and five JWT OAuth management routes replay their recorded PHP contracts against Go and count as ported only after passing.
D-16: A clean recorded lifecycle proves DCR, authorize, consent, token, refresh, replay kill, list, revoke, post-revoke failure, deny, confidential Basic auth, and scope ceiling.
D-14: The unchanged real fonoteka-mcp process completes discovery, DCR, PKCE, JWT consent, token bootstrap, an MCP tool call, and refresh against the Go backend.
D-15: Live vendor connects remain excluded; automated DCR-plus-PKCE evidence is the Phase 8 acceptance boundary.
path provides
../fonoteka.go/parity/oauth_flow_test.go Projected existing flows and full lifecycle replay
path provides
../fonoteka.go/parity/fixtures/mcp/mcp-lifecycle.yaml Secret-scrubbed PHP lifecycle source-of-truth fixture
path provides
scripts/check-phase8.sh Two-repository, parity, secret, Postgres, real-MCP phase gate
from to via pattern
oauth_flow_test.go newConfiguredTarget replay through the assembled Go app and real Postgres newConfiguredTarget
from to via pattern
scripts/check-phase8.sh /media/nvme/dev/golem15/fonoteka/fonoteka-mcp three configured URLs and scripted MCP SDK lifecycle FONOTEKA_(API_URL|MCP_PUBLIC_URL|MCP_AUTH_SERVER)
Prove the completed server through recorded PHP parity and the unchanged real MCP client rather than only implementation-local tests.

Purpose: Turn exact route bytes, lifecycle security semantics, protected-resource discovery ownership, and actual SDK compatibility into one repeatable acceptance gate. Output: Lifecycle fixture/capture policy, projected replay tests, nine ported manifest entries, and scripts/check-phase8.sh.

Phase Goal

As an unchanged MCP client, I want to complete the full OAuth lifecycle against Go exactly as I did against PHP, so that discovery, tool use, refresh, replay defense, and revocation are proven together.

<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-RESEARCH.md @.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md @.planning/phases/08-oauth2-1-authorization-server/08-07-SUMMARY.md @.planning/phases/08-oauth2-1-authorization-server/08-08-SUMMARY.md Existing parity seams: - `newConfiguredTarget(t, db)` boots the same assembled handler used by the app. - `tide.LoadFlow`, `tide.OpenStore`, and `tide.ReplayFlow` execute captured request/response sequences with private variables. - `parity/manifest.yaml` status becomes `ported` only when the selected replay subtest passes.

Unchanged MCP inputs:

  • FONOTEKA_API_URL points to the Go app personal-token API.
  • FONOTEKA_MCP_PUBLIC_URL points to the MCP resource server.
  • FONOTEKA_MCP_AUTH_SERVER points to the Go authorization server.
Task 1: Specify recorded and real-client OAuth acceptance before changing fixtures ../fonoteka.go/parity/oauth_flow_test.go, scripts/check-phase8.sh .planning/phases/08-oauth2-1-authorization-server/08-CONTEXT.md .planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md ../fonoteka.go/parity/nuxt_flow_test.go ../fonoteka.go/parity/parity_test.go ../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml ../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml ../fonoteka.go/parity/manifest.yaml ../fonoteka.go/parity/capture_clients.mjs scripts/check-phase3.sh /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts - Existing broad flows are projected to named OAuth/MCP prerequisite steps and fail if an expected step disappears; unrelated later-phase calls are not replayed. - The clean lifecycle flow is required and every terminal security action is asserted before routes can be marked ported. - The gate starts real Postgres, Go app, and unchanged Node MCP; it verifies MCP-owned protected-resource metadata and Bearer hint separately from backend-owned metadata and Basic invalid-client challenge. D-12: distinguish backend Basic/no-challenge responses from MCP RFC 9728 behavior. D-13: project `mcp-oauth` and `mcp-tools` by stable named step IDs. D-14: declare real Postgres/app/MCP stages with all three environment variables. D-15: keep live vendor connects excluded. D-16: require the full clean lifecycle. D-18: create compiling failing parity tests and a fail-closed gate skeleton before changing fixtures/status. Use `PHASE8_RED:parity-gate` and the shared RED verifier so shell/Go syntax, missing tests, setup failures, and unrelated failures cannot satisfy RED. Plan 08-10 alone adds the security-review validation stage after the review exists. Do not edit or patch MCP or Nuxt. scripts/check-phase8-red.sh parity-gate bash -lc "cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows' -count=1" - `oauth_flow_test.go` names projections for `mcp-oauth`, `mcp-tools`, and the complete `mcp-lifecycle`, and missing expected steps fail. - `scripts/check-phase8.sh` uses `set -euo pipefail`, fail-closed dependency/Docker checks, cleanup traps, and all three MCP environment variables. - The gate distinguishes MCP RFC 9728 metadata/rich Bearer challenge from backend exact Basic invalid-client challenge and unchanged token-surface 401. - Tests/gate fail because the lifecycle fixture/status/evidence is not yet complete, not because of shell or Go syntax errors. The acceptance harness demands the exact recorded and real-client lifecycle before any route can be claimed ported. Task 2: Record, scrub, replay, and promote the complete OAuth lifecycle ../fonoteka.go/parity/capture_clients.mjs, ../fonoteka.go/parity/capture-rules.yaml, ../fonoteka.go/parity/fixtures/mcp/mcp-lifecycle.yaml, ../fonoteka.go/parity/oauth_flow_test.go, ../fonoteka.go/parity/manifest.yaml, ../fonoteka.go/parity/check_corpus.go ../fonoteka.go/parity/oauth_flow_test.go ../fonoteka.go/parity/capture_clients.mjs ../fonoteka.go/parity/capture-rules.yaml ../fonoteka.go/parity/php_parity.sh ../fonoteka.go/parity/fixtures/mcp/mcp-oauth.yaml ../fonoteka.go/parity/fixtures/mcp/mcp-tools.yaml ../fonoteka.go/parity/manifest.yaml tide/flow.go tide/replay.go - Lifecycle records DCR → authorize → consent → token → refresh → spent-token replay → list → revoke → refresh failure → deny, plus confidential Basic and ceiling/invalid-scope cases. - Every request id, code, verifier, client secret, access token, and refresh token is represented only by a typed placeholder in committed fixtures; private vars are mode 0600. - Nine manifest entries become ported only after their exact route replay passes; pending never increments passing. D-16: extend the existing capture script/rules and use the Phase 2 isolated-PHP process to record the locked lifecycle. Issue the confidential client through `fonoteka:oauth-client`; exercise scope ceiling truncation and invalid-scope redirect with `client_secret_basic`. Capture all secret-bearing values with explicit pkce/token/credential categories into the private store, confirm both vars files are 0600, and commit only symbolic variable references. Add full and projected replays through `newConfiguredTarget` with real Postgres. After each of the four raw and five JWT route subtests passes, change only those manifest entries to `status: ported`; keep honest corpus accounting. cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows|TestParityCorpus|TestParityContract' -count=1 - `mcp-lifecycle.yaml` contains the locked sequence and placeholder references, not recoverable credential values. - `go run ./parity/check_corpus.go --manifest parity/manifest.yaml --fixtures parity/fixtures --check-secrets` exits 0. - All nine OAuth manifest entries are `ported`; replay reports them passing with zero failing and does not count any pending route as passing. - Existing `mcp-oauth`/`mcp-tools` projections fail if a required named step is removed and ignore only explicitly enumerated later-phase steps. The Go app passes the PHP-recorded OAuth route and lifecycle contracts without committing live secrets. Task 3: Complete the real unchanged-MCP phase gate scripts/check-phase8.sh scripts/check-phase8.sh scripts/check-phase3.sh ../fonoteka.go/parity/oauth_flow_test.go /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/package.json /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/http.ts /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/config.ts /media/nvme/dev/golem15/fonoteka/fonoteka-mcp/src/install.ts D-14: finish the executable pre-security gate using the existing gate family and installed Node MCP dependencies. Add `--core-smoke` for focused syntax/dependency/service lifecycle feedback and `--pre-security` for the complete wave-boundary gate. Allocate loopback ports, start disposable Postgres and the assembled Go app, start unchanged MCP with all three URLs, and drive SDK discovery/DCR/PKCE/login/consent/token, `/me`, tool, refresh, replay, and revoke. Verify RFC 9728 ownership separately from exact backend Basic/no-challenge responses. Run both modules' vet/test/race, parity/corpus/secret checks, `scripts/check-phase8-ui.mjs`, and unchanged Nuxt/MCP path diffs. Do not require `08-SECURITY-REVIEW.md` in either mode; Plan 08-10 adds the final fail-closed review stage. Preserve cleanup and never print secrets. scripts/check-phase8.sh --core-smoke - The gate starts the real unchanged MCP checkout and completes metadata, DCR, PKCE, JWT consent, token, `/me`, one MCP tool call, and refresh against the Go backend. - The gate proves spent refresh replay and connected-app revoke kill the lineage and later access/refresh attempts fail. - Both repositories pass `go vet ./...`, `go test ./...`, and `go test -race ./...`; corpus and secret scans exit 0. - `git -C /media/nvme/dev/golem15/fonoteka/fonoteka-mcp status --short` and the Nuxt equivalent show no Phase 8 diff. The actual connector stack, including RFC 9728 resource-server behavior, runs unchanged through the complete Go authorization lifecycle.

<threat_model>

Trust Boundaries

Boundary Description
PHP capture → committed fixtures Live credentials must become typed placeholders before entering git.
Scripted SDK → Go backend/MCP External client parsing and redirects exercise public network-facing contracts.
Gate process → child services Secrets and cleanup state cross shell/Node/Go process boundaries.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-08-PKCE Spoofing/Elevation real SDK flow mitigate Actual SDK S256 authorize/exchange plus wrong-verifier rejection in gate.
T-08-CODE-REPLAY Spoofing lifecycle replay mitigate Recorded and real repeated code/spent state fail.
T-08-REFRESH-REPLAY Spoofing/Elevation lifecycle replay/gate mitigate Spent replay kills lineage; post-replay DB/API evidence required.
T-08-OPEN-REDIRECT Spoofing/Disclosure recorded authorize cases mitigate PHP fixture and Go replay assert local errors versus trusted ordered redirects.
T-08-SECRET-TIMING Information Disclosure confidential Basic flow mitigate Actual confidential flow uses the constant-time implementation; final source audit in 08-06.
T-08-SCOPE-CEILING Elevation confidential lifecycle mitigate Recorded ceiling truncation and invalid-scope redirect.
T-08-CROSS-USER Elevation consent/list/revoke replay mitigate JWT ownership cases and indistinguishable 404 replay.
T-08-REQUEST-LEAK Information Disclosure fixtures/logs mitigate Capture categories, 0600 stores, placeholder-only fixtures, check-secrets and quiet gate.
T-08-DCR-FLOOD Denial of Service register route mitigate Recorded native errors plus focused rate/body/cap tests run by gate.
T-08-SURFACE Elevation MCP/backend boundary mitigate Gate asserts correct RFC 9728 ownership and exact backend challenges.
T-08-SC Tampering reused Node dependencies mitigate No install; use checked-in lockfile/node_modules and dependency preflight.
</threat_model>
- `cd ../fonoteka.go && go test ./parity -run 'TestOAuthFlows|TestParityCorpus|TestParityContract' -count=1` - `scripts/check-phase8.sh --pre-security` at the Wave 8 boundary; this mode proves the complete lifecycle but intentionally does not require the not-yet-created security review.

<success_criteria>

  • Nine OAuth routes and the full lifecycle replay pass against Go with no leaked fixture secret.
  • The real unchanged MCP discovers, authorizes, initializes, executes a tool, refreshes, and observes replay/revoke failure.
  • Resource-server and authorization-server header ownership is proven exactly, not conflated. </success_criteria>
Create `.planning/phases/08-oauth2-1-authorization-server/08-09-SUMMARY.md` when done.