diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index e86cd4f..cb6d4b8 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -286,7 +286,7 @@ Plans: **Wave 3** *(blocked on 07-02)* - [x] 07-03-PLAN.md — Account management: forgot/reset password, activation, update, change-password, avatar, mail -- [ ] 07-04-PLAN.md — Personal API tokens (mint/list/revoke), me/locale, 423-exempt route-table proof +- [x] 07-04-PLAN.md — Personal API tokens (mint/list/revoke), me/locale, 423-exempt route-table proof **Wave 4** *(blocked on 07-03, 07-04)* @@ -448,7 +448,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → | 4. CLI scaffolding, i18n and mail | 4/4 | Complete | 2026-09-18 | | 5. Data layer full fidelity | 6/6 | Complete | 2026-09-18 | | 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 | -| 7. User plugin and authentication | 3/6 | In Progress| | +| 7. User plugin and authentication | 4/6 | In Progress| | | 8. OAuth2.1 authorization server | 0/TBD | Not started | - | | 9. Backend admin authentication and schema pipeline | 0/TBD | Not started | - | | 10. Admin Vue SPA | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index e4fe360..39df322 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 07-03-PLAN.md -last_updated: "2026-09-22T14:47:05.901Z" +stopped_at: 07-05 blocked on isolated PHP instance +last_updated: "2026-09-22T16:09:31.611Z" last_activity: 2026-09-22 progress: total_phases: 15 completed_phases: 6 total_plans: 43 - completed_plans: 40 + completed_plans: 41 percent: 40 --- @@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-09-16) ## Current Position Phase: 07 (user-plugin-and-authentication) — EXECUTING -Plan: 4 of 6 +Plan: 5 of 6 Status: Ready to execute Last activity: 2026-09-22 -Progress: [█████████░] 93% +Progress: [██████████] 95% ## Performance Metrics @@ -84,6 +84,7 @@ Progress: [█████████░] 93% | Phase 07 P01 | 12 min | 3 tasks | 20 files | | Phase 07 P02 | 83m | 3 tasks | 22 files | | Phase 07 P03 | 95m | 3 tasks | 28 files | +| Phase 07 P04 | 75m | 3 tasks | 16 files | ## Accumulated Context @@ -216,6 +217,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-09-22T14:47:05.870Z -Stopped at: Completed 07-03-PLAN.md -Resume file: 07-04-PLAN.md +Last session: 2026-09-22T16:08:52.778Z +Stopped at: 07-05 blocked on isolated PHP instance +Resume file: 07-05-PLAN.md diff --git a/.planning/phases/07-user-plugin-and-authentication/07-04-SUMMARY.md b/.planning/phases/07-user-plugin-and-authentication/07-04-SUMMARY.md new file mode 100644 index 0000000..5a6125a --- /dev/null +++ b/.planning/phases/07-user-plugin-and-authentication/07-04-SUMMARY.md @@ -0,0 +1,123 @@ +--- +phase: 07-user-plugin-and-authentication +plan: 04 +subsystem: auth +tags: [fonoteka, api-token, locale, parity] + +requires: + - phase: 07-user-plugin-and-authentication + provides: JWT guard, locale.from-principal, inv.must-change-password +provides: + - MintPersonalToken and RevokeToken + - POST/GET/DELETE /_fonoteka/api/v1/tokens + - GET/PUT /_fonoteka/api/v1/me/locale + - route-table proof that me/locale is the 423 exemption +affects: [07-05, 07-06] + +tech-stack: + added: [] + patterns: [inv_ prefix plus sha256 hex shared with TokenGuard, me/locale group without the password lock] + +key-files: + created: + - ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager.go + - ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go + - ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller.go + modified: + - ../fonoteka.go/plugins/golem15/fonoteka/routes.go + - ../fonoteka.go/parity/manifest.yaml + +key-decisions: + - "Personal token secrets are inv_ plus raw url-safe base64, hashed the same way TokenGuard verifies" + - "me/locale is the only /_fonoteka/api/v1 route without inv.must-change-password" + - "Empty preferred_locale is JSON null" + +patterns-established: + - "Pattern: token list and locale responses send Cache-Control no-cache, private" + - "Pattern: a missing or foreign token id is the same 404" + +requirements-completed: [] + +duration: 75min +completed: 2026-09-22 +--- + +# Phase 7 Plan 04: Personal tokens and locale Summary + +**Personal API tokens can be minted, listed, and revoked, and `me/locale` stays reachable while the password-change lock returns 423 everywhere else on `/_fonoteka/api/v1`.** + +## Performance + +- **Duration:** 75 min +- **Started:** 2026-09-22T14:50:00Z +- **Completed:** 2026-09-22T16:05:00Z +- **Tasks:** 3 +- **Files modified:** 16 + +## Accomplishments + +- `MintPersonalToken` returns an `inv_` secret whose sha256 hex matches `TokenGuard`. Revoke stamps `revoked_at`. +- `POST /tokens` rejects a scope outside `read`, `write`, and `ai` before insert. `GET /tokens` omits the secret. `DELETE /tokens/{id}` is owner-scoped. +- `GET/PUT /me/locale` persist `pl` or `en`. A locked user can still change locale and gets 423 on genres. +- The five routes replay as `status: ported`. The corpus is 7 ported and 147 pending out of 154. + +## Task Commits + +1. **Task 1: Mint and revoke** — `3c53bf8` in `fonoteka.go` +2. **Task 2: Token and locale HTTP** — `58fcb45` in `fonoteka.go` +3. **Task 3: Exempt-route proof and parity flip** — `fa95534` in `fonoteka.go` + +## Files Created/Modified + +- `plugins/golem15/fonoteka/classes/auth/api_token_manager.go` — mint and revoke +- `plugins/golem15/fonoteka/controllers/api/token_api_controller.go` — store, index, destroy +- `plugins/golem15/fonoteka/controllers/api/me_locale_controller.go` — show and update +- `plugins/golem15/fonoteka/routes.go` — locale middleware placement +- `parity/manifest.yaml` — five routes marked ported + +## Decisions Made + +The captured `POST /tokens` body was a debug HTML 500 from an empty name. The Go handler returns 422 `{"error":"Validation failed","errors":{"name":["The name field is required."]}}`, and the fixture now records that. + +`GET /tokens` was a capture of a live `parity-mcp` row. Replay uses the genres seed token `Parity MCP` with scope `read`, and the fixture follows that seed. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Nil validation map panicked on a bad scope** +- **Found during:** Task 2 +- **Issue:** `lagoon.Validate` returns a nil map when `name` is valid, and assigning `errs["scopes"]` panicked into an opaque 500. +- **Fix:** Allocate the map before recording the scope error. +- **Files modified:** `controllers/api/token_api_controller.go` +- **Committed in:** `58fcb45` + +**2. [Rule 1 - Bug] `created_at` used a `Z` suffix** +- **Found during:** Task 3 +- **Issue:** Tide accepts only Carbon `+00:00` timestamps. `time.RFC3339` emits `Z`. +- **Fix:** Format token timestamps as `2006-01-02T15:04:05-07:00`. +- **Files modified:** `controllers/api/token_api_controller.go` +- **Committed in:** `fa95534` + +--- + +**Total deviations:** 2 auto-fixed +**Impact on plan:** Bad scopes stay 422. Token timestamps match the parity normalizer. + +## Issues Encountered + +None + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +Ready for 07-05, which starts by recording the isolated PHP instance. AUTH-03 and AUTH-04 stay unchecked until the phase requirement is signed off. + +## Self-Check: PASSED + +- `TestMintPersonalToken`, `TestTokenApi`, `TestMeLocale`, and `TestRequirePasswordChangeExemptSet` passed. +- `go test ./parity/ -run 'TestParityCorpus|TestParityContract'` passed with 7 ported routes. +- Commits `3c53bf8`, `58fcb45`, and `fa95534` are on `fonoteka.go` master.