Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-SUMMARY.md
Jakub Zych af3312aa92 docs(09-02): complete backend identity lifecycle plan
- Record the migration, JWT lifecycle, and admin command results
2026-09-24 17:59:31 +02:00

11 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status, plan_head_before, plan_head_after
phase plan subsystem tags requires provides affects actuals tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status plan_head_before plan_head_after
09-backend-admin-authentication-and-schema-pipeline 02 auth
jwt
postgres
gorm
admin
cabana
bcrypt
bonfire
phase provides
09-backend-admin-authentication-and-schema-pipeline backend audience guard, cabana login, and the first backend identity migration
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
09-backend-admin-authentication-and-schema-pipeline
admin-api
phase-10-spa
tokens tasks commits
18249 3 6
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
created modified
lagoon/backend_admin_migrations_test.go
cabana/auth_test.go
cabana/commands.go
cabana/commands_test.go
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
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
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
AUTH-08
id description requirement verification human_judgment
D1 Backend identity tables, system-role seeds, indexes, and rollback match the Winter-shaped contract on PostgreSQL. AUTH-08
kind ref status
integration lagoon/backend_admin_migrations_test.go#TestBackendAdminMigration pass
kind ref status
integration lagoon/backend_admin_migrations_test.go#TestBackendAdminSeed pass
kind ref status
integration lagoon/backend_admin_migrations_test.go#TestBackendAdminRollback pass
kind ref status
integration lagoon/backend_admin_migrations_test.go#TestBackendAdminWinterRow pass
false
id description requirement verification human_judgment
D2 Login, refresh, logout, and me issue and revoke backend-audience tokens, including inactive, deleted, stale, and blacklisted failures. AUTH-08
kind ref status
integration cabana/auth_test.go#TestAdminAuthLifecycle pass
kind ref status
integration cabana/auth_test.go#TestAdminInactive pass
kind ref status
integration cabana/auth_test.go#TestAdminDeleted pass
kind ref status
integration cabana/auth_test.go#TestAdminBlacklist pass
kind ref status
integration plugins/golem15/fonoteka/admin_auth_test.go#TestAdminAuthLifecycleAssembled pass
false
id description requirement verification human_judgment
D3 Repeated logins hit the fixed-window limiter, and auth logs keep outcome and admin id without passwords, hashes, tokens, or the signing secret. AUTH-08
kind ref status
integration cabana/auth_test.go#TestAdminLoginThrottle pass
kind ref status
integration cabana/auth_test.go#TestAdminAuthLogging pass
false
id description requirement verification human_judgment
D4 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. AUTH-08
kind ref status
integration cabana/commands_test.go#TestAdminCreateCommand pass
kind ref status
integration cabana/commands_test.go#TestAdminResetPasswordCommand pass
kind ref status
unit internal/build/build_test.go#TestGenerateMainRegistersCabanaRuntimeCommands pass
kind ref status
unit admin_command_test.go#TestAdminCommandRegistration pass
false
22min 2026-09-24 complete 0ed980e332 5f218977e4

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