Files
summercms/.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-PLAN.md
2026-09-24 16:45:45 +02:00

14 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, estimate, must_haves
phase plan type wave depends_on files_modified autonomous requirements estimate must_haves
09-backend-admin-authentication-and-schema-pipeline 02 execute 2
09-01
lagoon/backend_admin_migrations.go
lagoon/backend_admin_migrations_test.go
cabana/contracts.go
cabana/auth.go
cabana/http.go
cabana/commands.go
cabana/auth_test.go
cabana/commands_test.go
internal/build/build.go
internal/build/build_test.go
../fonoteka.go/config/admin.yaml
../fonoteka.go/main.go
../fonoteka.go/plugins/golem15/fonoteka/admin_auth_test.go
true
AUTH-08
tokens raw_tokens tasks confidence
43000 43000 3 low
truths artifacts key_links prohibitions
The D-01 backend_users/backend_user_roles schema matches the locked Winter-shaped columns, seeds developer and publisher as system roles idempotently, and migrates up/down against real PostgreSQL without AutoMigrate.
Backend login, refresh, logout, and me use D-02 backend-audience JWTs, sliding refresh, and a PostgreSQL jti blacklist; inactive, soft-deleted, unknown, blacklisted, and stale principals fail closed.
D-04 `admin:create` and `admin:reset-password` operate through the generated app command surface, hash with the existing bcrypt helper, validate the selected role, and never echo a password or token.
Backend login is throttled with the existing fixed-window limiter, and successful, failed, and denied auth events are logged with outcome/admin ID only—never credentials or bearer tokens.
path provides
lagoon/backend_admin_migrations_test.go Real-PostgreSQL migration, seed, and rollback evidence
path provides
cabana/commands.go admin:create and admin:reset-password runtime commands
path provides
cabana/auth_test.go Complete backend token lifecycle and safe logging coverage
path provides
../fonoteka.go/main.go Generated binary command registration for cabana runtime commands
from to via pattern
internal/build/build.go cabana/commands.go generated app main appends cabana.RuntimeCommands cabana.RuntimeCommands
from to via pattern
cabana/auth.go bouncer/blacklist.go backend refresh/logout use the PostgreSQL blacklist implementation PostgresBlacklist
from to via pattern
cabana/auth.go lagoon/backend_admin_migrations.go backend provider loads active non-deleted users and roles backend_users
[FLAGGED-UNVERIFIED] First-admin provisioning must not occur at boot, through a web setup wizard, or from environment-seeded credentials.
[FLAGGED-UNVERIFIED] Admin authentication must not introduce a cookie session store or merge with the frontend user model.
[FLAGGED-UNVERIFIED] Authentication logs must not contain login passwords, JWTs, signing secrets, or password hashes.

Phase Goal

As a backend administrator, I want to authenticate separately and manage resources described by Winter-shaped schemas, so that the administration surface stays permission-gated and reusable without coupling it to frontend users.

Complete the backend identity lifecycle and operator provisioning surface established by the tracer.

Purpose: AUTH-08 requires a durable, independently operated admin identity—not just a test fixture capable of driving one route. Output: Exact migrations with real-PostgreSQL proof, complete auth lifecycle, safe throttling/audit logging, and the two D-04 commands in generated binaries.

<execution_context> @/home/jin/.codex/gsd-core/workflows/execute-plan.md @/home/jin/.codex/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-RESEARCH.md @.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-01-SUMMARY.md @lagoon/migrations.go @lagoon/commands.go @bouncer/blacklist.go @bouncer/password.go @internal/build/build.go @../fonoteka.go/main.go Use the 09-01 `cabana.BackendUser`, `cabana.BackendUserRole`, audience-aware bouncer functions, and activated registry. Preserve `lagoon.Migrate` ordering and generated-main construction while adding cabana commands as framework runtime commands.

Artifacts this phase produces

  • Complete cabana.BackendUser/BackendUserRole GORM contracts and backend UserProvider
  • cabana.RuntimeCommands, cabana.AdminCreateCommand, and cabana.AdminResetPasswordCommand
  • Login/refresh/logout/me handlers with PostgreSQL revocation and safe auth-event logging
  • Real-PostgreSQL migration/rollback tests and assembled auth lifecycle tests
Task 1: Finish exact backend identity migrations and persistence D-01 intentionally matches cutover tables already named by Winter; changing columns later requires coordinated import/schema work, but the locked decision must not be re-gated. lagoon/backend_admin_migrations.go, lagoon/backend_admin_migrations_test.go, cabana/contracts.go lagoon/backend_admin_migrations.go, lagoon/migrations.go, lagoon/migrations_test.go, cabana/contracts.go, .planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-CONTEXT.md - Test 1: fresh PostgreSQL migration creates the exact D-01 user/role columns, indexes, role relationship, timestamps and soft-delete behavior. - Test 2: repeated migration leaves exactly one developer and one publisher system role with stable codes; rollback removes the framework admin tables without affecting plugin histories. - Test 3: copied Winter-shaped rows load through the GORM contracts without a schema transform. Complete the framework migration with explicit gormigrate Up/Down SQL and integrate it before plugin sets, alongside the existing framework-owned attachment migration. Model only D-01's backend users and roles; seed developer/publisher idempotently; preserve nullable role and last-login fields; and give the admin blacklist its own framework-owned table while reusing `bouncer.PostgresBlacklist`. Do not use AutoMigrate or add backend user groups/preferences/access-log tables. Prove exact columns, index/unique behavior, system-role idempotency, cutover-shaped row loading, and rollback on Testcontainers PostgreSQL. go test ./lagoon -run '^TestBackendAdmin(Migration|Seed|Rollback|WinterRow)' -count=1 The command exits non-zero, reports no matching test, PostgreSQL is skipped, a locked column/index/role seed is missing, migration is non-idempotent, rollback damages another history, or AutoMigrate appears in production code. The migration is an executable PostgreSQL contract for D-01 and preserves the existing per-framework/per-plugin migration ordering. Backend identity persistence and system-role seeds round-trip on real PostgreSQL. Task 2: Complete backend JWT lifecycle, throttle, and safe auth events cabana/auth.go, cabana/http.go, cabana/auth_test.go, ../fonoteka.go/config/admin.yaml, ../fonoteka.go/plugins/golem15/fonoteka/admin_auth_test.go cabana/auth.go, cabana/http.go, bouncer/refresh.go, bouncer/blacklist.go, bouncer/password.go, surf/limiter.go, ../fonoteka.go/plugins/golem15/fonoteka/admin_tracer_test.go, ../fonoteka.go/config/golem15.user.yaml - Test 1: login by backend login or normalized email returns a backend-audience access token; refresh rotates and blacklists the previous jti; logout revokes it; me returns only safe backend profile/role data. - Test 2: unknown, wrong-password, inactive, soft-deleted, stale, and blacklisted identities share opaque failures without timing-dependent record disclosure. - Test 3: the existing fixed-window limiter rejects repeated login attempts, while captured logs record outcomes without password, bearer token, hash, or secret substrings. Expand the tracer login into all D-09 auth routes using D-02's bouncer lifecycle and D-10 envelopes. Configure access/refresh TTL, grace, bcrypt cost, and a fail-loud backend secret under `admin.*`; attach the existing surf fixed-window limiter to login; update last_login only after successful password verification; require activated/non-deleted users on every token load; rotate/blacklist transactionally; and serialize a safe me DTO. Emit structured successful/failed/authorization-denied events containing outcome, stable admin ID when known, and normalized request metadata only. Reuse the shared test config helpers for the test-only secret so unrelated assembled-route tests remain honest. go test ./cabana -run '^TestAdmin(AuthLifecycle|LoginThrottle|AuthLogging|Inactive|Deleted|Blacklist)' -count=1 && (cd ../fonoteka.go && go test ./plugins/golem15/fonoteka -run '^TestAdminAuthLifecycleAssembled$' -count=1) Either command exits non-zero, reports no matching test, refresh/logout leaves an old token usable, inactive/deleted users authenticate, throttling does not reject the limit case, error bodies reveal account existence, or captured logs contain a credential/token/secret. Login, refresh, logout, and me are fully assembled and every sensitive auth failure is both opaque to clients and redacted in logs. The backend guard has a durable, revocable, separately configured session lifecycle. Task 3: Provision and reset admins through generated framework commands The command names and flags in D-04 are operator-facing contracts carried into generated app binaries. cabana/commands.go, cabana/commands_test.go, internal/build/build.go, internal/build/build_test.go, ../fonoteka.go/main.go lagoon/commands.go, bonfire/command.go, internal/build/build.go, internal/build/build_test.go, ../fonoteka.go/main.go, cabana/contracts.go - Test 1: admin:create with email/password and optional login/superuser/role creates one activated bcrypt-backed admin, defaults login deterministically when omitted, and rejects unknown/ambiguous roles. - Test 2: admin:reset-password accepts login or email, updates the bcrypt hash, invalidates earlier tokens, and reveals no password/hash. - Test 3: a generated app main registers both commands exactly once and compiles. Implement D-04 as cabana runtime bonfire commands that open/publish the database through existing lagoon helpers, validate flags and role code, use `bouncer.HashPassword`, and atomically create or update the backend row. On reset, advance the token-valid-after cutoff so existing admin JWTs stop working. Update the app-main generator to append cabana runtime commands and regenerate the tracked Fonoteka main through the established build path; do not hand-edit a command into only this app. Command output may identify the affected login/email but must never print a supplied password, stored hash, JWT, or signing secret. go test ./cabana -run '^TestAdmin(Create|ResetPassword)Command' -count=1 && go test ./internal/build -run '^Test.*RuntimeCommands' -count=1 && (cd ../fonoteka.go && go test . -run '^Test.*AdminCommandRegistration' -count=1) Any command exits non-zero, any package reports no matching test, generated main lacks or duplicates cabana commands, role validation is bypassed, old tokens survive reset, or command output contains password/hash/token material. The operator can create the first admin and reset a password entirely through the generated binary using the exact D-04 command/flag surface. Backend administrator provisioning is command-only, deterministic, bcrypt-backed, and token-revoking.

<threat_model>

Trust Boundaries

Boundary Description
Operator CLI → backend tables Sensitive password and role inputs create or mutate privileged identities.
Login/refresh/logout → token state Untrusted credentials and bearer tokens cross into hashing, rotation, and revocation logic.
Auth outcomes → logs Security telemetry must preserve evidence without copying secrets.

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-09-03 Information Disclosure cabana auth handlers and structured logging high mitigate Opaque login errors plus explicit log-capture tests that reject passwords, hashes, JWTs, and secrets while retaining outcome/admin ID.
T-09-04 Elevation admin:create/reset-password commands medium mitigate Validate exact role codes, hash inside the transaction, invalidate tokens on reset, and test generated command registration/output redaction.
T-09-SC Tampering Go module dependency set high mitigate Reuse existing bouncer/bonfire/lagoon/surf packages; no package installs are authorized, and any discovered dependency need halts for legitimacy review.
</threat_model>
- Real PostgreSQL migration up/down and seed idempotency tests pass. - The assembled backend auth lifecycle covers every failure class and safe logging. - Generated application command registration, create, and reset flows pass without exposing secrets.

<success_criteria>

  • Backend identity is durable, Winter-shaped, separately signed, refreshable, and revocable.
  • An operator can provision/reset admins without a web bootstrap path.
  • Authentication throttling and logging are executable security controls. </success_criteria>
Create `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-SUMMARY.md` when done.