174 lines
12 KiB
Markdown
174 lines
12 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/jin/.codex/get-shit-done/workflows/execute-plan.md
|
|
@/home/jin/.codex/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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-UI-SPEC.md
|
|
@.planning/phases/08-oauth2-1-authorization-server/08-VALIDATION.md
|
|
@.planning/phases/08-oauth2-1-authorization-server/08-04-SUMMARY.md
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<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.
|
|
- 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 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 go PHASE8_RED:consent-controller git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka/controllers/api TestPhase8RedConsentController -- bash -lc "cd ../fonoteka.go && go test -json ./plugins/golem15/fonoteka/controllers/api -run '^TestPhase8RedConsentController$' -count=1" && scripts/check-phase8-red.sh go PHASE8_RED:consent-app git.golem15.com/golem15/fonoteka/plugins/golem15/fonoteka TestPhase8RedConsentApp -- bash -lc "cd ../fonoteka.go && 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 && 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>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Prove the approved consent and connected-app UI contract read-only</name>
|
|
<files>scripts/check-phase8-ui.mjs</files>
|
|
<read_first>
|
|
.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
|
|
</read_first>
|
|
<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>node scripts/check-phase8-ui.mjs --contract-self-test</automated>
|
|
</verify>
|
|
<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>
|
|
|
|
<threat_model>
|
|
## 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. |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- Focused consent/app tests pass under 30 seconds where possible.
|
|
- 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>
|
|
- 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.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/08-oauth2-1-authorization-server/08-05-SUMMARY.md` when done.
|
|
</output>
|