docs(09-02): complete backend identity lifecycle plan
- Record the migration, JWT lifecycle, and admin command results
This commit is contained in:
@@ -375,7 +375,7 @@ Plans:
|
||||
4. The relation-manager schema supports search/link/unlink/manage-or-view lists for Collections' editors tab, replacing the `partial` field entirely.
|
||||
5. Admin CRUD endpoints expose `listExtendQuery`/`formExtendQuery`/`formBeforeCreate`/`formBeforeUpdate`/`relationExtendManageQuery` hooks, bulk delete runs each record's lifecycle hooks, and the Settings model binds to a settings screen through the same schema pipeline.
|
||||
|
||||
**Plans**: 1/12 plans executed
|
||||
**Plans**: 2/12 plans executed
|
||||
**Research flag:** yes
|
||||
|
||||
Plans:
|
||||
@@ -384,7 +384,7 @@ Plans:
|
||||
- [x] 09-01-PLAN.md — Prove the architecture with one production end-to-end Genre list
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
- [ ] 09-02-PLAN.md — Complete backend identity lifecycle and operator provisioning
|
||||
- [x] 09-02-PLAN.md — Complete backend identity lifecycle and operator provisioning
|
||||
|
||||
**Wave 3** *(blocked on Wave 2 completion)*
|
||||
- [ ] 09-03-PLAN.md — Compile the typed form-schema contract and Winter scaffolding
|
||||
@@ -529,7 +529,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
|
||||
| 6. HTTP routing, auth groups and rate limiting | 14/14 | Complete | 2026-09-21 |
|
||||
| 7. User plugin and authentication | 8/8 | Complete | 2026-09-23 |
|
||||
| 8. OAuth2.1 authorization server | 10/10 | Complete | 2026-09-23 |
|
||||
| 9. Backend admin authentication and schema pipeline | 1/12 | In Progress| |
|
||||
| 9. Backend admin authentication and schema pipeline | 2/12 | In Progress| |
|
||||
| 10. Admin Vue SPA | 0/TBD | Not started | - |
|
||||
| 11. Jobs, realtime and search infrastructure | 0/TBD | Not started | - |
|
||||
| 12. Płytarium API — Collections and Albums | 0/TBD | Not started | - |
|
||||
|
||||
@@ -4,16 +4,16 @@ milestone: v1.0
|
||||
current_phase: 09
|
||||
current_phase_name: Backend admin authentication and schema pipeline
|
||||
status: executing
|
||||
stopped_at: Completed 09-01-PLAN.md
|
||||
last_updated: "2026-09-24T15:22:53.884Z"
|
||||
stopped_at: Completed 09-02-PLAN.md
|
||||
last_updated: "2026-09-24T15:59:20.547Z"
|
||||
last_activity: 2026-09-24
|
||||
last_activity_desc: Phase 09 execution started
|
||||
state_head: 18b2e851063f3a50b7a13c87413ac4ae3ece9998
|
||||
state_head: 5f218977e4cc61b103a3be4e459fe5aa6962006f
|
||||
progress:
|
||||
total_phases: 15
|
||||
completed_phases: 8
|
||||
total_plans: 67
|
||||
completed_plans: 56
|
||||
completed_plans: 57
|
||||
milestone_name: milestone
|
||||
---
|
||||
|
||||
@@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-09-16)
|
||||
## Current Position
|
||||
|
||||
Phase: 09 (Backend admin authentication and schema pipeline) — EXECUTING
|
||||
Plan: 2 of 12
|
||||
Plan: 3 of 12
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-09-24 — Phase 09 execution started
|
||||
|
||||
@@ -110,6 +110,7 @@ Progress: [██████████] 100%
|
||||
| Plan | Duration | Tasks | Files |
|
||||
|------|----------|-------|-------|
|
||||
| Phase 09 P01 | 26min | 2 tasks | 26 files |
|
||||
| Phase 09 P02 | 22 min | 3 tasks | 14 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -272,6 +273,9 @@ Recent decisions affecting current work:
|
||||
- [Phase 09]: Frontend verification accepts PHP tokens that omit aud and rejects any other explicit audience
|
||||
- [Phase 09]: Backend JWTs require aud=backend, use admin.jwt.secret, and omit the PHP user prv hash
|
||||
- [Phase 09]: Cabana mounts from BuildRouter only when a plugin registers admin controllers; an empty admin.jwt.secret fails that assembly
|
||||
- [Phase 09]: Admin jti rows live in backend_jwt_blacklist and cabana does not republish the frontend BlacklistStore — Refresh and logout must not revoke frontend tokens or be revoked by them.
|
||||
- [Phase 09]: backend_user_roles.code is indexed and not unique so Winter rows can repeat a code — admin:create rejects zero or many matches instead of a unique constraint the cutover table does not have.
|
||||
- [Phase 09]: tokens_valid_after is a nullable additive column used to revoke admin JWTs on password reset — The guard already honors Principal.TokensValidAfter and Winter's required columns stay unchanged.
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -294,6 +298,6 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-09-24T15:22:53.442Z
|
||||
Stopped at: Completed 09-01-PLAN.md
|
||||
Last session: 2026-09-24T15:59:20.134Z
|
||||
Stopped at: Completed 09-02-PLAN.md
|
||||
Resume file: None
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
---
|
||||
phase: 09-backend-admin-authentication-and-schema-pipeline
|
||||
plan: 02
|
||||
subsystem: auth
|
||||
tags: [jwt, postgres, gorm, admin, cabana, bcrypt, bonfire]
|
||||
|
||||
requires:
|
||||
- phase: 09-backend-admin-authentication-and-schema-pipeline
|
||||
provides: backend audience guard, cabana login, and the first backend identity migration
|
||||
provides:
|
||||
- Idempotent Winter-shaped backend_users and backend_user_roles migrations plus a separate admin jti table
|
||||
- Login, refresh, logout, and me with sliding refresh, opaque failures, throttle, and redacted auth logs
|
||||
- admin:create and admin:reset-password on the generated binary
|
||||
affects: [09-backend-admin-authentication-and-schema-pipeline, admin-api, phase-10-spa]
|
||||
|
||||
actuals:
|
||||
tokens: 18249
|
||||
tasks: 3
|
||||
commits: 6
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Admin revocation uses backend_jwt_blacklist through bouncer.PostgresBlacklist and does not replace the frontend blacklist"
|
||||
- "Role code is indexed, not unique, so a copied Winter row can repeat a code and admin:create rejects the ambiguous match"
|
||||
- "Password reset advances tokens_valid_after so existing backend JWTs fail closed"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- lagoon/backend_admin_migrations_test.go
|
||||
- cabana/auth_test.go
|
||||
- cabana/commands.go
|
||||
- cabana/commands_test.go
|
||||
modified:
|
||||
- lagoon/backend_admin_migrations.go
|
||||
- cabana/auth.go
|
||||
- cabana/http.go
|
||||
- cabana/contracts.go
|
||||
- internal/build/build.go
|
||||
- ../fonoteka.go/main.go
|
||||
- ../fonoteka.go/config/admin.yaml
|
||||
|
||||
key-decisions:
|
||||
- "Admin jti rows live in backend_jwt_blacklist, not the frontend jwt_blacklist, and cabana does not republish BlacklistStore"
|
||||
- "backend_user_roles.code stays nullable and non-unique, matching Winter, while name stays unique for the idempotent seed"
|
||||
- "tokens_valid_after is an extra nullable column so reset can revoke tokens without changing Winter's required columns"
|
||||
- "Login throttle defaults to 5 attempts per minute on the existing fixed-window limiter"
|
||||
|
||||
patterns-established:
|
||||
- "Pattern: login always runs bcrypt, then rejects unknown, inactive, and bad-password with one body"
|
||||
- "Pattern: operator provisioning is cabana.RuntimeCommands appended by the app-main generator"
|
||||
|
||||
requirements-completed: [AUTH-08]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: Backend identity tables, system-role seeds, indexes, and rollback match the Winter-shaped contract on PostgreSQL.
|
||||
requirement: AUTH-08
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: lagoon/backend_admin_migrations_test.go#TestBackendAdminMigration
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: lagoon/backend_admin_migrations_test.go#TestBackendAdminSeed
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: lagoon/backend_admin_migrations_test.go#TestBackendAdminRollback
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: lagoon/backend_admin_migrations_test.go#TestBackendAdminWinterRow
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: Login, refresh, logout, and me issue and revoke backend-audience tokens, including inactive, deleted, stale, and blacklisted failures.
|
||||
requirement: AUTH-08
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminAuthLifecycle
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminInactive
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminDeleted
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminBlacklist
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: plugins/golem15/fonoteka/admin_auth_test.go#TestAdminAuthLifecycleAssembled
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: Repeated logins hit the fixed-window limiter, and auth logs keep outcome and admin id without passwords, hashes, tokens, or the signing secret.
|
||||
requirement: AUTH-08
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminLoginThrottle
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: cabana/auth_test.go#TestAdminAuthLogging
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: admin:create and admin:reset-password provision bcrypt admins, reject unknown or ambiguous roles, revoke old tokens, and are registered once in the generated binary.
|
||||
requirement: AUTH-08
|
||||
verification:
|
||||
- kind: integration
|
||||
ref: cabana/commands_test.go#TestAdminCreateCommand
|
||||
status: pass
|
||||
- kind: integration
|
||||
ref: cabana/commands_test.go#TestAdminResetPasswordCommand
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: internal/build/build_test.go#TestGenerateMainRegistersCabanaRuntimeCommands
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: admin_command_test.go#TestAdminCommandRegistration
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 22min
|
||||
completed: 2026-09-24
|
||||
status: complete
|
||||
plan_head_before: 0ed980e332239a32432f011686e0b2e6b1bd3573
|
||||
plan_head_after: 5f218977e4cc61b103a3be4e459fe5aa6962006f
|
||||
---
|
||||
|
||||
# Phase 9 Plan 02: Backend identity lifecycle Summary
|
||||
|
||||
**Backend admins now have a Winter-shaped PostgreSQL identity, a revocable backend-audience JWT lifecycle, and command-only provisioning on the generated binary.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 22 min
|
||||
- **Started:** 2026-09-24T15:35:45Z
|
||||
- **Completed:** 2026-09-24T15:57:36Z
|
||||
- **Tasks:** 3
|
||||
- **Files modified:** 14
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Framework migrations create `backend_users`, `backend_user_roles`, and `backend_jwt_blacklist`, seed developer and publisher idempotently, and roll back without touching plugin history.
|
||||
- `POST /_admin/api/v1/auth/login`, `/refresh`, `/logout`, and `GET /me` use backend-audience JWTs, sliding refresh, opaque failures, a 5-per-minute login limiter, and logs that keep outcome and admin id only.
|
||||
- `admin:create` and `admin:reset-password` hash with bcrypt, validate role codes, revoke older tokens, and are appended once by the app-main generator.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically. SummerCMS `commits: 6` is `git rev-list --count` from the plan ledger. Fonoteka commits are in the sibling repository.
|
||||
|
||||
1. **Task 1: Exact backend identity migrations (RED)** - `448faa4` (test)
|
||||
2. **Task 1: Exact backend identity migrations (GREEN)** - `06a7292` (feat)
|
||||
3. **Task 2: Backend JWT lifecycle (RED)** - `0953308` (test, summercms.go) and `6349952` (test, fonoteka.go)
|
||||
4. **Task 2: Backend JWT lifecycle (GREEN)** - `9740c3d` (feat, summercms.go) and `029f908` (feat, fonoteka.go)
|
||||
5. **Task 3: Admin commands (RED)** - `d27f442` (test, summercms.go) and `9522c65` (test, fonoteka.go)
|
||||
6. **Task 3: Admin commands (GREEN)** - `5f21897` (feat, summercms.go) and `e4d773d` (feat, fonoteka.go)
|
||||
|
||||
**Plan metadata:** pending docs commit
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `lagoon/backend_admin_migrations.go` - re-runnable identity DDL, system-role seed, and admin blacklist table
|
||||
- `lagoon/backend_admin_migrations_test.go` - real PostgreSQL column, seed, rollback, and Winter-row tests
|
||||
- `cabana/contracts.go` - GORM `BackendUser` and `BackendUserRole`, including the reset cutoff
|
||||
- `cabana/auth.go` - login, refresh, logout, me, safe logging, and the admin blacklist
|
||||
- `cabana/http.go` - mounts the auth routes and the login throttle
|
||||
- `cabana/commands.go` - `admin:create` and `admin:reset-password`
|
||||
- `internal/build/build.go` - generated main appends `cabana.RuntimeCommands`
|
||||
- `fonoteka.go` `main.go` - regenerated command registration
|
||||
- `fonoteka.go` `config/admin.yaml` - TTL, bcrypt cost, and login throttle defaults with an empty secret
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Admin revocation uses its own `backend_jwt_blacklist` table. Cabana does not publish that store over the frontend `jwt_blacklist`.
|
||||
- `backend_user_roles.code` is indexed and not unique, so a copied Winter row can repeat a code. `admin:create --role` rejects zero or many matches.
|
||||
- `tokens_valid_after` is nullable and additive. Reset sets it one second ahead so existing backend JWTs fail the guard without changing Winter's required columns.
|
||||
- Login throttle defaults to 5 attempts per minute through `throttle:N,M` on the existing fixed-window limiter.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 3 - Blocking] Lagoon tests no longer import cabana**
|
||||
- **Found during:** Task 3 (admin commands)
|
||||
- **Issue:** `cabana` must call `lagoon.OpenFromApp`, but `lagoon` tests imported `cabana.BackendUser`, which is an import cycle once that edge exists.
|
||||
- **Fix:** The Winter-row test loads a local GORM struct with the same column tags. Production `cabana.BackendUser` is unchanged.
|
||||
- **Files modified:** `lagoon/backend_admin_migrations_test.go`
|
||||
- **Verification:** `TestBackendAdminWinterRow` passed
|
||||
- **Committed in:** `5f21897`
|
||||
|
||||
**2. [Rule 3 - Blocking] Regenerated main also restored `route:list`**
|
||||
- **Found during:** Task 3 (admin commands)
|
||||
- **Issue:** `internal/build/build.go` already emitted `surf.RouteListCommand`, but the tracked Fonoteka `main.go` had drifted and omitted it.
|
||||
- **Fix:** Regeneration followed the generator, so the tracked main gained that one existing line as well as `cabana.RuntimeCommands`.
|
||||
- **Files modified:** `fonoteka.go/main.go`
|
||||
- **Verification:** `TestAdminCommandRegistration` passed and `go test .` compiled the main package
|
||||
- **Committed in:** `e4d773d`
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 2 auto-fixed (2 blocking)
|
||||
**Impact on plan:** Both were required to keep the command path compiling and the generated binary equal to the generator. No new dependency and no production secret.
|
||||
|
||||
## TDD Gate Compliance
|
||||
|
||||
| Gate | Commit | Result |
|
||||
|------|--------|--------|
|
||||
| RED task 1 | `448faa4` test(09-02) | `TestBackendAdminMigration` failed because `tokens_valid_after` and `backend_jwt_blacklist` were missing |
|
||||
| GREEN task 1 | `06a7292` feat(09-02) | migration, seed, rollback, and Winter-row tests passed on PostgreSQL |
|
||||
| RED task 2 | `0953308` / `6349952` test(09-02) | login left `last_login` null; logout was 404 |
|
||||
| GREEN task 2 | `9740c3d` / `029f908` feat(09-02) | lifecycle, throttle, logging, and assembled tests passed |
|
||||
| RED task 3 | `d27f442` / `9522c65` test(09-02) | `admin:create` was not registered and generated main lacked `cabana.RuntimeCommands` |
|
||||
| GREEN task 3 | `5f21897` / `e4d773d` feat(09-02) | create, reset, generator, and registration tests passed |
|
||||
|
||||
`gsd_run check tdd-red-evidence` returned `RED_EVIDENCE_OK` for the migration, lifecycle, and create-command RED runs. The task 3 RED commit includes a nil `RuntimeCommands` stub so the Go tests compiled before the implementation replaced it.
|
||||
|
||||
## Authentication Gates
|
||||
|
||||
None.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
Production boots that register admin controllers must set `SUMMER_ADMIN__JWT__SECRET`. `config/admin.yaml` still ships that key empty. Login throttle, refresh TTL, grace, and bcrypt cost have non-secret defaults.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Ready for 09-03. Identity, revocation, and operator provisioning are in place. `AUTH-08` stays shared with 09-11 and 09-12, so it is not marked complete in REQUIREMENTS.md.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: lagoon/backend_admin_migrations.go, lagoon/backend_admin_migrations_test.go, cabana/commands.go, cabana/auth.go, cabana/http.go, cabana/contracts.go, internal/build/build.go
|
||||
- FOUND: fonoteka.go main.go, config/admin.yaml, plugins/golem15/fonoteka/admin_auth_test.go, admin_command_test.go
|
||||
- FOUND commits: 448faa4, 06a7292, 0953308, 6349952, 9740c3d, 029f908, d27f442, 9522c65, 5f21897, e4d773d
|
||||
|
||||
---
|
||||
*Phase: 09-backend-admin-authentication-and-schema-pipeline*
|
||||
*Completed: 2026-09-24*
|
||||
Reference in New Issue
Block a user