Replace token-based admin auth with JWT session authentication
CI / test (push) Failing after 12m45s
CI / test (push) Failing after 12m45s
- Add AdminUser model (bcrypt hashed passwords) and admin_users table - Add AdminJWTService for HS256 JWT sessions (24h TTL) - Add AdminSessionAuth middleware for /api/v1/admin/* routes - Add admin handlers: login, logout, me, change-password, users CRUD - Keys and model management routes now require admin JWT session - Remove ADMIN_TOKEN, add ADMIN_USERNAME, ADMIN_PASSWORD env vars - Update frontend: username/password login, admin_session storage, AdminUsers CRUD view
This commit is contained in:
@@ -33,7 +33,10 @@ type Config struct {
|
||||
RateLimitStorage string // "memory" or "redis"
|
||||
|
||||
// Auth
|
||||
AdminToken string
|
||||
AdminUsername string
|
||||
AdminPassword string
|
||||
JWTSecret []byte
|
||||
AdminSessionTTL int // seconds
|
||||
|
||||
// Logging
|
||||
LogLevel string
|
||||
@@ -58,10 +61,19 @@ func Load() *Config {
|
||||
ModelSwapCooldown: intEnv("MODEL_SWAP_COOLDOWN", 2),
|
||||
RateLimitPerMinute: intEnv("RATE_LIMIT_PER_MINUTE", 60),
|
||||
RateLimitStorage: getEnv("RATE_LIMIT_STORAGE", "memory"),
|
||||
AdminToken: getEnv("ADMIN_TOKEN", "changeme"),
|
||||
AdminUsername: getEnv("ADMIN_USERNAME", "admin"),
|
||||
AdminPassword: getEnv("ADMIN_PASSWORD", ""),
|
||||
AdminSessionTTL: intEnv("ADMIN_SESSION_TTL", 86400),
|
||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||
LogFormat: getEnv("LOG_FORMAT", "json"),
|
||||
}
|
||||
|
||||
secretKey := getEnv("LLAMALINK_SECRET_KEY", "change-me-in-production")
|
||||
if secretKey == "change-me-in-production" {
|
||||
slog.Warn("LLAMALINK_SECRET_KEY is using the default value — set a secure random string in production")
|
||||
}
|
||||
c.JWTSecret = []byte(secretKey)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ func TestLoad(t *testing.T) {
|
||||
func TestLoadEnvOverride(t *testing.T) {
|
||||
t.Setenv("LLAMALINK_PORT", "9000")
|
||||
t.Setenv("LLAMALINK_ENV", "production")
|
||||
t.Setenv("ADMIN_TOKEN", "secret-token")
|
||||
t.Setenv("ADMIN_USERNAME", "admin")
|
||||
t.Setenv("ADMIN_PASSWORD", "secret-password")
|
||||
|
||||
cfg := Load()
|
||||
|
||||
@@ -44,8 +45,12 @@ func TestLoadEnvOverride(t *testing.T) {
|
||||
t.Errorf("expected env production, got %s", cfg.LlamalinkEnv)
|
||||
}
|
||||
|
||||
if cfg.AdminToken != "secret-token" {
|
||||
t.Errorf("expected AdminToken secret-token, got %s", cfg.AdminToken)
|
||||
if cfg.AdminUsername != "admin" {
|
||||
t.Errorf("expected AdminUsername admin, got %s", cfg.AdminUsername)
|
||||
}
|
||||
|
||||
if cfg.AdminPassword != "secret-password" {
|
||||
t.Errorf("expected AdminPassword secret-password, got %s", cfg.AdminPassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user