feat: add token caching middleware and max history messages config
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
2026-07-21 17:30:22 +00:00
parent 9980123f97
commit 0decc63e8c
2 changed files with 57 additions and 8 deletions
+10 -4
View File
@@ -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 != "" {
+47 -4
View File
@@ -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.",