Files
MindSift/internal/config/config.go
T
sandrodachandClaude Opus 5.5 d6c70d7a8e feat(legal): imprint & privacy notices, "Über MindSift"
- LEGAL_IMPRINT_/LEGAL_PRIVACY_ URL and Markdown (inline or *_FILE);
  only http(s) URLs accepted
- /auth/status exposes legal info; public GET /legal/{imprint|privacy}
- Links on login/setup screen and in the settings footer: URL opens a new
  tab, Markdown a reading modal; hidden when nothing is configured
- "Über MindSift" modal (version, license, repository) in settings and help
- Docs and compose/env examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:10:27 +01:00

150 lines
4.3 KiB
Go

package config
import (
"log/slog"
"os"
"path/filepath"
"strings"
)
type Config struct {
Port string
DataDir string
DatabasePath string
JWTSecret string
BaseURL string
AllowRegistration bool
LogLevel string
Debug bool
DemoMode bool
// Optional SMTP environment variables (can also be overridden/configured in DB system_settings)
SMTPHost string
SMTPPort string
SMTPUser string
SMTPPass string
SMTPFrom string
SMTPTLS bool
// SMTPSkipVerify accepts self-signed or mismatching certificates (internal mail servers)
SMTPSkipVerify bool
// Legal notices (Impressum, Datenschutz): an external URL and/or Markdown text, shown even before login
Legal LegalTexts
}
type LegalTexts struct {
ImprintURL, ImprintContent string
PrivacyURL, PrivacyContent string
}
// legalContent reads Markdown from NAME or, for longer texts, from the file named in NAME_FILE.
func legalContent(name string) string {
if v := strings.TrimSpace(os.Getenv(name)); v != "" {
return v
}
if path := strings.TrimSpace(os.Getenv(name + "_FILE")); path != "" {
data, err := os.ReadFile(path)
if err != nil {
slog.Warn("Rechtstext konnte nicht gelesen werden", "variable", name+"_FILE", "error", err)
return ""
}
return strings.TrimSpace(string(data))
}
return ""
}
// legalURL accepts only http(s) links, so a misconfiguration cannot inject javascript: URLs.
func legalURL(name string) string {
v := strings.TrimSpace(os.Getenv(name))
if v == "" {
return ""
}
if !strings.HasPrefix(v, "https://") && !strings.HasPrefix(v, "http://") {
slog.Warn("Rechtstext-URL ignoriert: nur http(s) erlaubt", "variable", name)
return ""
}
return v
}
func Load() *Config {
port := getEnv("PORT", "8080")
dataDir := getEnv("DATA_DIR", "./data")
dbURL := getEnv("DATABASE_URL", "")
dbPath := filepath.Join(dataDir, "mindsift.db")
if strings.HasPrefix(dbURL, "sqlite://") {
dbPath = strings.TrimPrefix(dbURL, "sqlite://")
} else if dbURL != "" && !strings.HasPrefix(dbURL, "postgres") {
dbPath = dbURL
}
jwtSecret := getEnv("JWT_SECRET", "mindsift-default-selfhosted-jwt-secret-key-2026")
baseURL := getEnv("BASE_URL", "http://localhost:"+port)
allowReg := strings.ToLower(getEnv("ALLOW_REGISTRATION", "false")) == "true"
debugVal := strings.ToLower(getEnv("DEBUG", "false"))
debug := debugVal == "true" || debugVal == "1" || debugVal == "yes" || debugVal == "on"
logLevel := strings.ToLower(getEnv("LOG_LEVEL", "info"))
if debug {
logLevel = "debug"
}
demoVal := strings.ToLower(getEnv("MINDA_DEMO", getEnv("MINDSIFT_DEMO", getEnv("DEMO_MODE", "false"))))
demoMode := demoVal == "true" || demoVal == "1" || demoVal == "yes" || demoVal == "on"
smtpHost := getEnv("SMTP_HOST", "")
smtpPort := getEnv("SMTP_PORT", "587")
smtpUser := getEnv("SMTP_USER", "")
smtpPass := getEnv("SMTP_PASS", "")
smtpFrom := getEnv("SMTP_FROM", "")
smtpTLS := strings.ToLower(getEnv("SMTP_TLS", "true")) != "false"
smtpSkipVerify := strings.ToLower(getEnv("SMTP_SKIP_VERIFY", "false")) == "true"
return &Config{
Port: port,
DataDir: dataDir,
DatabasePath: dbPath,
JWTSecret: jwtSecret,
BaseURL: baseURL,
AllowRegistration: allowReg,
LogLevel: logLevel,
Debug: debug,
DemoMode: demoMode,
SMTPHost: smtpHost,
SMTPPort: smtpPort,
SMTPUser: smtpUser,
SMTPPass: smtpPass,
SMTPFrom: smtpFrom,
SMTPTLS: smtpTLS,
SMTPSkipVerify: smtpSkipVerify,
Legal: LegalTexts{
ImprintURL: legalURL("LEGAL_IMPRINT_URL"),
ImprintContent: legalContent("LEGAL_IMPRINT_CONTENT"),
PrivacyURL: legalURL("LEGAL_PRIVACY_URL"),
PrivacyContent: legalContent("LEGAL_PRIVACY_CONTENT"),
},
}
}
// SlogLevel parses Config.LogLevel into a standard slog.Level.
func (c *Config) SlogLevel() slog.Level {
switch strings.ToLower(strings.TrimSpace(c.LogLevel)) {
case "debug":
return slog.LevelDebug
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
func getEnv(key, fallback string) string {
if val, ok := os.LookupEnv(key); ok && strings.TrimSpace(val) != "" {
return strings.TrimSpace(val)
}
return fallback
}