---
phase: 08-oauth2-1-authorization-server
plan: 05
type: execute
wave: 5
depends_on: [08-04]
files_modified:
- ../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
- scripts/check-phase8-ui.mjs
autonomous: true
requirements: [AUTH-05, AUTH-06, AUTH-07]
must_haves:
truths:
- "D-08: JWT consent returns exact unchanged-Nuxt payloads and derives scopes and collection ids server-side."
- "D-09: Authorize/token remain raw while consent routes remain in the JWT group."
- "Invalid handles make no request, login uses the closed return-path allow-list, and English/Polish consent copy resolves."
- "Consent state, keyboard/focus, 44px target, and mobile stacking matrices are proven without changing Nuxt source."
artifacts:
- path: "../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller.go"
provides: "Owner-bound consent show/allow/deny API"
- path: "scripts/check-phase8-ui.mjs"
provides: "Read-only browser harness for the approved UI-SPEC"
key_links:
- from: "scripts/check-phase8-ui.mjs"
to: "/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app"
via: "existing Playwright dependency, mocked backend responses, and no source writes"
pattern: "playwright"
---
Wire browser consent and prove the complete approved UI contract against the unchanged Nuxt checkout.
Purpose: Separate browser-facing API/state compatibility from protocol internals and make preservation objectively executable.
Output: JWT consent controllers/routes, assembled flow tests, and an external read-only browser/UI gate.
@/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-UI-SPEC.md
@.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
@.planning/phases/08-oauth2-1-authorization-server/08-04-SUMMARY.md
Task 1: Specify assembled consent and route behavior in executable RED
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/oauth_consent_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/oauth_connect_test.go
- 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`.
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.
scripts/check-phase8-red.sh consent bash -lc "cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1"
Consent tests compile, execute, and fail only on the intended missing behavior.
Task 2: Implement owner-bound JWT consent and raw route wiring
../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
- 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.
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.
cd ../fonoteka.go && go test ./plugins/golem15/fonoteka/... -run 'TestOAuth(Authorize|Consent|Deny|CodeExchange|Surface)' -count=1
The unchanged consent client can inspect, allow, or deny one owner-bound request and complete exact PKCE code exchange.
Task 3: Prove the approved consent and connected-app UI contract read-only
scripts/check-phase8-ui.mjs
.planning/phases/08-oauth2-1-authorization-server/08-UI-SPEC.md
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/pages/connect.vue
/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/app/components/fonoteka/ConsentScopePicker.vue
/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
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.
cd /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app && pnpm verify:oauth-return-path && pnpm verify:oauth-i18n && cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && node scripts/check-phase8-ui.mjs --focused
The full UI-SPEC state/accessibility/return-path/i18n matrix passes against unchanged Nuxt files, and the harness is callable from the final gate.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Browser JWT principal → consent API | Authenticated input crosses ownership/scope/tenant boundaries. |
| Backend data → unchanged Nuxt | Untrusted names and protocol state select rendered UI states. |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-08-SCOPE-CEILING | Elevation | consent | mitigate | Four-way scope intersection and server-derived collection. |
| T-08-CROSS-USER | Elevation | consent | mitigate | Principal-bound lookup/consume and indistinguishable 404. |
| T-08-REQUEST-LEAK | Information Disclosure | browser/errors | mitigate | Opaque handle, no-referrer behavior, no request on invalid handle. |
| T-08-SURFACE | Elevation | route groups | mitigate | Raw/JWT isolation and browser contract harness. |
| T-08-SC | Tampering | Playwright reuse | mitigate | Reuse installed locked dependency; no package install. |
- 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.
- Consent API matches every unchanged client state selector.
- Return-path, no-invalid-request, i18n, state, accessibility, and responsive matrices have runnable evidence.
- Nuxt source remains unchanged.