diff --git a/internal/config/config.go b/internal/config/config.go index f1f4bcd2..e796c1c3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,10 +19,11 @@ type Config struct { } type ServerConfig struct { - Port int `mapstructure:"port"` - Host string `mapstructure:"host"` - AuthTokens []string `mapstructure:"auth_tokens"` - WSAllowedOrigin string `mapstructure:"ws_allowed_origin"` + Port int `mapstructure:"port"` + Host string `mapstructure:"host"` + AuthTokens []string `mapstructure:"auth_tokens"` + WSAllowedOrigin string `mapstructure:"ws_allowed_origin"` + MaxHistoryMessages int `mapstructure:"max_history_messages"` } type DatabaseConfig struct { @@ -96,6 +97,7 @@ func Load() (*Config, error) { v.SetDefault("server.port", 8080) v.SetDefault("server.host", "0.0.0.0") v.SetDefault("server.auth_tokens", []string{}) + v.SetDefault("server.max_history_messages", 0) v.SetDefault("database.path", "./data/llm_proxy.db") v.SetDefault("database.max_connections", 10) @@ -142,6 +144,7 @@ func Load() (*Config, error) { v.BindEnv("encryption_key", "LLM_PROXY__ENCRYPTION_KEY") v.BindEnv("server.port", "LLM_PROXY__SERVER__PORT") v.BindEnv("server.host", "LLM_PROXY__SERVER__HOST") + v.BindEnv("server.max_history_messages", "LLM_PROXY__SERVER__MAX_HISTORY_MESSAGES") v.BindEnv("providers.ollama.enabled", "LLM_PROXY__PROVIDERS__OLLAMA__ENABLED") v.BindEnv("providers.ollama.base_url", "LLM_PROXY__PROVIDERS__OLLAMA__BASE_URL") v.BindEnv("providers.ollama.models", "LLM_PROXY__PROVIDERS__OLLAMA__MODELS") @@ -174,6 +177,9 @@ func Load() (*Config, error) { cfg.Server.Host = host } + if maxHistory := os.Getenv("LLM_PROXY__SERVER__MAX_HISTORY_MESSAGES"); maxHistory != "" { + fmt.Sscanf(maxHistory, "%d", &cfg.Server.MaxHistoryMessages) + } // Ollama overrides if enabled := os.Getenv("LLM_PROXY__PROVIDERS__OLLAMA__ENABLED"); enabled != "" { diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index e370e94d..8ee94cc2 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -4,6 +4,8 @@ import ( "log" "net/http" "strings" + "sync" + "time" "gophergate/internal/db" "gophergate/internal/models" @@ -11,6 +13,15 @@ import ( "github.com/gin-gonic/gin" ) +type tokenCacheEntry struct { + clientID string + expiredAt time.Time +} + +var ( + tokenCache sync.Map // map[string]tokenCacheEntry +) + func AuthMiddleware(database *db.DB, requireAuth bool) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") @@ -52,11 +63,39 @@ func AuthMiddleware(database *db.DB, requireAuth bool) gin.HandlerFunc { return } - // Try to resolve client from database with a read-only SELECT + // Try to resolve client from cache first var clientID string - err := database.Get(&clientID, "SELECT client_id FROM client_tokens WHERE token = ? AND is_active = 1", token) + var dbErr error + now := time.Now() + if cached, ok := tokenCache.Load(token); ok { + entry := cached.(tokenCacheEntry) + if now.Before(entry.expiredAt) { + clientID = entry.clientID + } + } - if err == nil { + // If cache miss (or expired), query database + if clientID == "" { + var fetchedID string + dbErr = database.Get(&fetchedID, "SELECT client_id FROM client_tokens WHERE token = ? AND is_active = 1", token) + if dbErr == nil { + clientID = fetchedID + // Cache valid token for 10 seconds + tokenCache.Store(token, tokenCacheEntry{ + clientID: clientID, + expiredAt: now.Add(10 * time.Second), + }) + } else { + // If error (invalid/inactive token), cache negative result for 2 seconds + // to avoid hammering SQLite on repeated invalid requests + tokenCache.Store(token, tokenCacheEntry{ + clientID: "", + expiredAt: now.Add(2 * time.Second), + }) + } + } + + if clientID != "" { c.Set("auth", models.AuthInfo{ Token: token, ClientID: clientID, @@ -76,7 +115,11 @@ func AuthMiddleware(database *db.DB, requireAuth bool) gin.HandlerFunc { if len(token) > 8 { maskedToken = token[:3] + "••••" + token[len(token)-4:] } - log.Printf("Token not found, inactive or error in DB: %s (err: %v)", maskedToken, err) + if dbErr != nil { + log.Printf("Token not found, inactive or error in DB: %s (err: %v)", maskedToken, dbErr) + } else { + log.Printf("Token not found or inactive: %s", maskedToken) + } c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": gin.H{ "message": "Invalid or inactive client token.",