Vellum/internal/auth/bootstrap.go
mithral 9772e55c73 Phase 1: accounts, sessions, per-library grants
Multi-user from the first migration, as planned.

- auth: argon2id password hashing (PHC encoded, parameters travel with the
  hash), opaque session tokens stored only as SHA-256, login rate limiting,
  and account enumeration resistance (unknown user hashes anyway)
- sessions: cookie or Bearer auth, HttpOnly + SameSite=Lax, revocation on
  password change
- grants: library_grants is the single access-control chokepoint. Admins see
  every library; everyone else sees exactly their can_read grants
- bootstrap: VELLUM_ADMIN_USER/PASSWORD create the first admin only on an
  empty database. No default password is ever invented.
- httpapi: mounts modules on a shared mux, session middleware innermost
- httpx: shared JSON helpers, strict body decoding

Gate: two users see different libraries. Tests cover that, plus session
expiry, token-at-rest, grant upsert, and the full login/me/logout flow.
2026-10-09 16:31:24 -04:00

41 lines
1.3 KiB
Go

package auth
import (
"context"
"database/sql"
"log/slog"
)
// EnsureAdminFromEnv creates the first administrator on an empty database.
//
// This runs only when no users exist, so it is idempotent across restarts and
// cannot reset a password or add an admin to an established install. If the
// database is empty and no credentials are configured, it logs a warning
// rather than inventing a default password — a known-weak default on a
// LAN-reachable service is exactly how these servers get compromised.
func EnsureAdminFromEnv(ctx context.Context, db *sql.DB, log *slog.Logger, username, password string) error {
n, err := CountUsers(ctx, db)
if err != nil {
return err
}
if n > 0 {
return nil
}
if username == "" || password == "" {
log.Warn("no user accounts exist and VELLUM_ADMIN_USER / VELLUM_ADMIN_PASSWORD are unset; " +
"the API is reachable but nobody can log in. Set both and restart to create the first admin.")
return nil
}
if len(password) < 8 {
log.Warn("VELLUM_ADMIN_PASSWORD is shorter than 8 characters; refusing to create the admin account")
return nil
}
u, err := CreateUser(ctx, db, username, "Administrator", password, true)
if err != nil {
return err
}
log.Info("created first administrator", "username", u.Username)
return nil
}