feat: add token caching middleware and max history messages config
This commit is contained in:
@@ -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 != "" {
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Reference in New Issue
Block a user