--- phase: 09-backend-admin-authentication-and-schema-pipeline plan: 02 type: execute wave: 2 depends_on: [09-01] files_modified: - 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 autonomous: true requirements: [AUTH-08] estimate: tokens: 43000 raw_tokens: 43000 tasks: 3 confidence: low must_haves: truths: - "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." artifacts: - path: "lagoon/backend_admin_migrations_test.go" provides: "Real-PostgreSQL migration, seed, and rollback evidence" - path: "cabana/commands.go" provides: "admin:create and admin:reset-password runtime commands" - path: "cabana/auth_test.go" provides: "Complete backend token lifecycle and safe logging coverage" - path: "../fonoteka.go/main.go" provides: "Generated binary command registration for cabana runtime commands" key_links: - from: "internal/build/build.go" to: "cabana/commands.go" via: "generated app main appends cabana.RuntimeCommands" pattern: "cabana.RuntimeCommands" - from: "cabana/auth.go" to: "bouncer/blacklist.go" via: "backend refresh/logout use the PostgreSQL blacklist implementation" pattern: "PostgresBlacklist" - from: "cabana/auth.go" to: "lagoon/backend_admin_migrations.go" via: "backend provider loads active non-deleted users and roles" pattern: "backend_users" prohibitions: - "[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. @/home/jin/.codex/gsd-core/workflows/execute-plan.md @/home/jin/.codex/gsd-core/templates/summary.md @.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. ## 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. | - 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. - 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. Create `.planning/phases/09-backend-admin-authentication-and-schema-pipeline/09-02-SUMMARY.md` when done.