Web UI: optional HTTP basic auth via UI_USER/UI_PASS
Both must be set together and require LISTEN_UI; constant-time compares, challenge on every endpoint. Startup warns when LISTEN_UI binds a non-loopback address without auth. Bind address and port were already covered by LISTEN_UI itself (host:port).
This commit is contained in:
@@ -20,6 +20,8 @@ type Config struct {
|
||||
ListenOllama string `env:"LISTEN_OLLAMA"`
|
||||
ListenComfy string `env:"LISTEN_COMFY"`
|
||||
ListenUI string `env:"LISTEN_UI"`
|
||||
UIUser string `env:"UI_USER"`
|
||||
UIPass string `env:"UI_PASS"`
|
||||
OllamaURL string `env:"OLLAMA_URL"`
|
||||
ComfyURL string `env:"COMFY_URL"`
|
||||
UnloadTimeout time.Duration `env:"UNLOAD_TIMEOUT"`
|
||||
@@ -198,6 +200,8 @@ func Load(getenv func(string) string) (Config, error) {
|
||||
{"LISTEN_OLLAMA", &cfg.ListenOllama},
|
||||
{"LISTEN_COMFY", &cfg.ListenComfy},
|
||||
{"LISTEN_UI", &cfg.ListenUI},
|
||||
{"UI_USER", &cfg.UIUser},
|
||||
{"UI_PASS", &cfg.UIPass},
|
||||
{"OLLAMA_URL", &cfg.OllamaURL},
|
||||
{"COMFY_URL", &cfg.ComfyURL},
|
||||
{"WARM_MODEL", &cfg.WarmModel},
|
||||
@@ -320,6 +324,12 @@ func Load(getenv func(string) string) (Config, error) {
|
||||
default:
|
||||
return cfg, fmt.Errorf("LOG_FORMAT: must be \"text\" or \"json\"")
|
||||
}
|
||||
if (cfg.UIUser == "") != (cfg.UIPass == "") {
|
||||
return cfg, fmt.Errorf("UI_USER and UI_PASS must be set together (both empty = no auth)")
|
||||
}
|
||||
if cfg.UIUser != "" && cfg.ListenUI == "" {
|
||||
return cfg, fmt.Errorf("UI_USER/UI_PASS have no effect without LISTEN_UI")
|
||||
}
|
||||
if cfg.ComfyCmd != "" && cfg.ComfyURL == "" {
|
||||
return cfg, fmt.Errorf("COMFY_CMD requires COMFY_URL to be set (the proxy needs somewhere to forward)")
|
||||
}
|
||||
|
||||
@@ -97,6 +97,30 @@ func TestComfyCmdRequiresURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIAuthPairing(t *testing.T) {
|
||||
env := func(set map[string]string) func(string) string {
|
||||
return func(k string) string { return set[k] }
|
||||
}
|
||||
// Only one of UI_USER/UI_PASS: error.
|
||||
_, err := Load(env(map[string]string{"OLLAMA_URL": "http://x", "LISTEN_UI": "127.0.0.1:7860", "UI_USER": "admin"}))
|
||||
if err == nil || !strings.Contains(err.Error(), "UI_USER and UI_PASS must be set together") {
|
||||
t.Fatalf("err = %v, want pairing error", err)
|
||||
}
|
||||
// Auth without LISTEN_UI: error.
|
||||
_, err = Load(env(map[string]string{"OLLAMA_URL": "http://x", "UI_USER": "admin", "UI_PASS": "x"}))
|
||||
if err == nil || !strings.Contains(err.Error(), "no effect without LISTEN_UI") {
|
||||
t.Fatalf("err = %v, want LISTEN_UI error", err)
|
||||
}
|
||||
// Both with LISTEN_UI: loads.
|
||||
cfg, err := Load(env(map[string]string{"OLLAMA_URL": "http://x", "LISTEN_UI": "127.0.0.1:7860", "UI_USER": "admin", "UI_PASS": "x"}))
|
||||
if err != nil {
|
||||
t.Fatalf("auth pair with LISTEN_UI must load: %v", err)
|
||||
}
|
||||
if cfg.UIUser != "admin" || cfg.UIPass != "x" {
|
||||
t.Errorf("got %q/%q", cfg.UIUser, cfg.UIPass)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEnvFile(t *testing.T) {
|
||||
input := `# comment
|
||||
OLLAMA_URL=http://host:11435
|
||||
|
||||
@@ -25,7 +25,9 @@ func sampleEntries(logFile string) []sampleEntry {
|
||||
return []sampleEntry{
|
||||
{"LISTEN_OLLAMA", ":11434", "Listen address for Ollama-compatible clients (gpu-turnstile poses as Ollama here)", false},
|
||||
{"LISTEN_COMFY", ":8188", "Listen address for ComfyUI clients (gpu-turnstile poses as ComfyUI here)", false},
|
||||
{"LISTEN_UI", "127.0.0.1:7860", "Web UI listen address: live status like --monitor, with update/reload buttons (default: empty = disabled; keep it on localhost — there is no auth)", false},
|
||||
{"LISTEN_UI", "127.0.0.1:7860", "Web UI listen address (host:port): live status like --monitor, with update/reload buttons (default: empty = disabled; bind 127.0.0.1 unless you set auth)", false},
|
||||
{"UI_USER", "", "HTTP basic auth for the web UI (UI_USER and UI_PASS must be set together; both empty = no auth)", false},
|
||||
{"UI_PASS", "", "See UI_USER", false},
|
||||
{"OLLAMA_URL", "http://127.0.0.1:11434", "Ollama upstream URL; setting it enables the Ollama consumer (default: empty = disabled)", false},
|
||||
{"COMFY_URL", "http://127.0.0.1:8188", "ComfyUI upstream URL; setting it enables the ComfyUI consumer (default: empty = disabled)", false},
|
||||
{"WARM_MODEL", "", "Optional model to reload after an image job (default: empty = none)", false},
|
||||
|
||||
Reference in New Issue
Block a user