fix(08): revise oauth plans after checker feedback

This commit is contained in:
Jakub Zych
2026-09-23 17:46:38 +02:00
parent 3c6a505c5f
commit e23cbac240
13 changed files with 444 additions and 197 deletions

View File

@@ -58,28 +58,58 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
<task type="auto" tdd="true">
<name>Task 1: Specify assembled consent and route behavior in executable RED</name>
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go</files>
<read_first>
.planning/phases/08-oauth2-1-authorization-server/08-UI-SPEC.md
.planning/phases/08-oauth2-1-authorization-server/08-PATTERNS.md
wristband/authorize.go
wristband/token.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go
../fonoteka.go/plugins/golem15/fonoteka/routes.go
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthConsentController.php
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/tests/security/OAuthConsentScopeCeilingTest.php
</read_first>
<behavior>
- Show/allow/deny cover exact 200/404/422 payloads, host-only redirect, canonical scopes, active collection, ownership, and ordered redirects.
- Tests compile and fail only through `PHASE8_RED:consent`.
- Controller and assembled tests compile and fail only through their exact consent RED sentinels.
</behavior>
<action>D-18: add controller and assembled PKCE flow tests against 08-04 interfaces. Use `PHASE8_RED:consent` only for absent controller/route behavior and reject syntax/setup/missing-test failures via the RED verifier. Cover T-08-CROSS-USER, SCOPE-CEILING, REQUEST-LEAK, and SURFACE, including duplicate action single-use.</action>
<action>D-18: add controller and assembled PKCE flow tests against 08-04 interfaces. Use exact `TestPhase8RedConsentController`/`PHASE8_RED:consent-controller` and `TestPhase8RedConsentApp`/`PHASE8_RED:consent-app` package/test/sentinel triples with `go test -json`; reject every unexpected failing action/package/test, compile/setup/panic/no-test case, and missing/duplicate sentinel. Cover T-08-CROSS-USER, SCOPE-CEILING, REQUEST-LEAK, and SURFACE, including duplicate action single-use.</action>
<verify>
<automated>scripts/check-phase8-red.sh consent bash -lc "cd ../fonoteka.go &amp;&amp; go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1"</automated>
<automated>scripts/check-phase8-red.sh go PHASE8_RED:consent-controller git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api TestPhase8RedConsentController -- bash -lc "cd ../fonoteka.go &amp;&amp; go test -json ./plugins/golem15/fonoteka/controllers/api -run '^TestPhase8RedConsentController$' -count=1" &amp;&amp; scripts/check-phase8-red.sh go PHASE8_RED:consent-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedConsentApp -- bash -lc "cd ../fonoteka.go &amp;&amp; go test -json ./plugins/golem15/fonoteka -run '^TestPhase8RedConsentApp$' -count=1"</automated>
</verify>
<acceptance_criteria>
- Each RED invocation observes exactly one selected test failure and its package failure with the exact sentinel; any other failure action is rejected.
- Tests assert exact show 200, missing/stale/used/foreign 404, empty-intersection 422, allow/deny redirect bytes, ownership, active collection, ordered scopes, and duplicate-action single use.
- Assembled route inspection proves consent paths are JWT+locale+must-change-password only while authorize/token remain raw.
</acceptance_criteria>
<done>Consent tests compile, execute, and fail only on the intended missing behavior.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Implement owner-bound JWT consent and raw route wiring</name>
<files>../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/plugin.go, ../fonoteka.go/plugins/golem15/fonoteka/routes.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go</files>
<read_first>
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go
../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go
../fonoteka.go/plugins/golem15/fonoteka/classes/active_collection.go
../fonoteka.go/plugins/golem15/fonoteka/routes.go
/media/nvme/dev/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api/OAuthConsentController.php
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/stores/fonoteka.ts
</read_first>
<behavior>
- Show returns sanitized client, host-only redirect, ordered mintable scopes, active collection name, and ISO expiry.
- Allow grants submitted ∩ requested ∩ ceiling ∩ mintable; deny consumes pending state; both return nonblank ordered redirect_to.
</behavior>
<action>D-08: implement show/allow/deny in the JWT+locale+must-change-password group using `bouncer.User`, `ResolveActiveCollection`, `lagoon.Validate`, and wristband operations; never accept collection IDs or extra scopes. Collapse missing/stale/used/foreign handles to exact 404 and empty/no-longer-grantable intersection to exact 422. D-09: mount authorize/token in raw routes and only token receives its throttle. D-10 and D-12: add no oauth guard, house middleware, backend Bearer challenge, or RFC 9728 document.</action>
<verify>
<automated>cd ../fonoteka.go &amp;&amp; go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1</automated>
<automated>(cd ../fonoteka.go &amp;&amp; go test ./plugins/golem15/fonoteka/controllers/api ./plugins/golem15/fonoteka -run '^TestOAuth(Consent|Deny|CodeExchange|Surface)' -count=1)</automated>
</verify>
<acceptance_criteria>
- Show returns only sanitized client name, host-only redirect, ordered mintable scopes, active collection name, acting user, and ISO expiry; no secret/handle beyond the submitted opaque request id leaks.
- Allow grants exactly submitted ∩ requested ∩ client ceiling ∩ mintable and server-derived collection IDs; deny grants none; both consume once and return one nonblank ordered `redirect_to`.
- Missing/stale/used/foreign handles have identical exact 404 bytes, empty/no-longer-grantable scope has exact 422, and no state mutation survives a failing transaction.
- Route tests prove JWT/raw/personal groups and unchanged Basic/token-surface headers remain isolated.
</acceptance_criteria>
<done>The unchanged consent client can inspect, allow, or deny one owner-bound request and complete exact PKCE code exchange.</done>
</task>
@@ -94,11 +124,16 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/components/fonoteka/ConnectedAppsManager.vue
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/stores/fonoteka.ts
</read_first>
<action>Create a read-only harness outside the Nuxt checkout using its already-installed Playwright runtime. First run `pnpm verify:oauth-return-path` and `pnpm verify:oauth-i18n`. Then boot the unchanged app and intercept API responses to prove: invalid/missing/repeated-first-invalid handles send no oauth/request call; logged-out entry preserves only a validated localized return path; 200, 404, network/error, empty-scope, allow-pending, deny-pending, and one-redirect outcomes render correctly; connected-app error/empty/manual-count/populated/cancel/revoke-pending/success/failure/identical-404 states render correctly. Assert keyboard order, visible 2px focus, dialog trap/Escape/restore, native disabled semantics, checkbox/revoke targets at least 44px, narrow/mobile stacking and no horizontal overflow, and both English/Polish strings with no raw keys. Snapshot the OAuth-related Nuxt paths before/after and fail on any diff; do not write fixtures, snapshots, generated files, or source inside Nuxt.</action>
<action>Create a read-only harness outside the Nuxt checkout using its already-installed Playwright runtime. Define focused scenario selectors for invalid/missing/repeated-first-invalid handles; safe localized login return; consent 200/404/network/empty-scope/pending/one-redirect states; connected-app error/empty/manual-count/populated/cancel/revoke pending/success/failure/identical-404 states; keyboard/focus/dialog/disabled/44px/mobile/en-pl assertions. Add `--contract-self-test` that validates scenario completeness, source-path hashes, intercept definitions, and no-write guards without booting browsers/services; reserve the actual return-path/i18n/browser run for 08-10's final checkpoint. Never write inside Nuxt.</action>
<verify>
<automated>cd /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app &amp;&amp; pnpm verify:oauth-return-path &amp;&amp; pnpm verify:oauth-i18n &amp;&amp; cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go &amp;&amp; node scripts/check-phase8-ui.mjs --focused</automated>
<automated>node scripts/check-phase8-ui.mjs --contract-self-test</automated>
</verify>
<done>The full UI-SPEC state/accessibility/return-path/i18n matrix passes against unchanged Nuxt files, and the harness is callable from the final gate.</done>
<acceptance_criteria>
- Self-test enumerates every UI-SPEC consent/connected-app state, keyboard/focus/dialog/44px/mobile assertion, English/Polish check, and invalid-handle no-request case exactly once.
- Source-hash/no-write guards cover the listed Nuxt component/store/i18n/return-path files and fail on any before/after change.
- Harness exposes one final-gate mode that runs existing `verify:oauth-return-path`, `verify:oauth-i18n`, and the real Playwright matrix; Plan 08-10 is its only full execution site.
</acceptance_criteria>
<done>The harness encodes and self-validates the full UI-SPEC matrix without changing Nuxt; Plan 08-10's sole final gate executes it.</done>
</task>
</tasks>
@@ -124,7 +159,7 @@ Output: JWT consent controllers/routes, assembled flow tests, and an external re
<verification>
- Focused consent/app tests pass under 30 seconds where possible.
- UI harness runs at the wave boundary and is invoked again by the final gate.
- UI harness contract self-test stays under 30 seconds; the complete browser matrix runs only in 08-10's final blocking checkpoint.
</verification>
<success_criteria>