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.
41 lines
1.3 KiB
Go
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
|
|
}
|