fix: Phase 1 - security & stability patches
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

- AuthMiddleware now requires auth on /v1/* routes (returns 401)
- WebSocket origin check configurable via WSAllowedOrigin
- Removed debug fmt.Printf leaks (config, ollama, server)
- Registry access protected by sync.RWMutex (race condition fix)
- Session cleanup goroutine runs every 15 min
- RevokeSession returns error instead of silent no-op
This commit is contained in:
2026-04-26 14:45:22 -04:00
parent da074f52b4
commit 8a8d8d1477
13 changed files with 448 additions and 105 deletions
+44 -30
View File
@@ -8,17 +8,17 @@ import (
"strings"
"time"
"gophergate/internal/db"
"gophergate/internal/models"
"gophergate/internal/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"gophergate/internal/db"
"gophergate/internal/models"
"gophergate/internal/utils"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/process"
)
@@ -168,7 +168,9 @@ func (s *Server) handleChangePassword(c *gin.Context) {
func (s *Server) handleLogout(c *gin.Context) {
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
s.sessions.RevokeSession(token)
if err := s.sessions.RevokeSession(token); err != nil {
fmt.Printf("Error revoking session: %v\n", err)
}
c.JSON(http.StatusOK, SuccessResponse(gin.H{"message": "Logged out"}))
}
@@ -226,7 +228,7 @@ func (s *Server) handleUsageSummary(c *gin.Context) {
}
clause, binds := filter.ToSQL()
// Total stats
var totalStats struct {
TotalRequests int `db:"total_requests"`
@@ -307,7 +309,7 @@ func (s *Server) handleTimeSeries(c *gin.Context) {
}
clause, binds := filter.ToSQL()
if clause == "" {
cutoff := time.Now().UTC().Add(-30 * 24 * time.Hour)
clause = " AND timestamp >= ?"
@@ -444,7 +446,10 @@ func (s *Server) handleAnalyticsBreakdown(c *gin.Context) {
var label string
var value int
if err := mRows.Scan(&label, &value); err == nil {
models = append(models, struct{Label string `json:"label"`; Value int `json:"value"`}{label, value})
models = append(models, struct {
Label string `json:"label"`
Value int `json:"value"`
}{label, value})
}
}
mRows.Close()
@@ -461,7 +466,10 @@ func (s *Server) handleAnalyticsBreakdown(c *gin.Context) {
var label string
var value int
if err := cRows.Scan(&label, &value); err == nil {
clients = append(clients, struct{Label string `json:"label"`; Value int `json:"value"`}{label, value})
clients = append(clients, struct {
Label string `json:"label"`
Value int `json:"value"`
}{label, value})
}
}
cRows.Close()
@@ -537,15 +545,15 @@ func (s *Server) handleGetClients(c *gin.Context) {
}
type UIClient struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
LastUsed *time.Time `json:"last_used"`
RequestsCount int `json:"requests_count"`
TokensCount int `json:"tokens_count"`
Status string `json:"status"`
RateLimitPerMinute int `json:"rate_limit_per_minute"`
RequestsCount int `json:"requests_count"`
TokensCount int `json:"tokens_count"`
Status string `json:"status"`
RateLimitPerMinute int `json:"rate_limit_per_minute"`
}
uiClients := make([]UIClient, len(clients))
@@ -608,12 +616,12 @@ func (s *Server) handleGetClient(c *gin.Context) {
}
c.JSON(http.StatusOK, SuccessResponse(gin.H{
"id": cl.ClientID,
"name": name,
"description": desc,
"is_active": cl.IsActive,
"rate_limit_per_minute": cl.RateLimitPerMinute,
"created_at": cl.CreatedAt,
"id": cl.ClientID,
"name": name,
"description": desc,
"is_active": cl.IsActive,
"rate_limit_per_minute": cl.RateLimitPerMinute,
"created_at": cl.CreatedAt,
}))
}
@@ -873,10 +881,16 @@ func (s *Server) handleGetProviders(c *gin.Context) {
var models []string
if s.registry != nil {
registryID := id
if id == "gemini" { registryID = "google" }
if id == "moonshot" { registryID = "moonshot" }
if id == "grok" { registryID = "xai" }
if id == "gemini" {
registryID = "google"
}
if id == "moonshot" {
registryID = "moonshot"
}
if id == "grok" {
registryID = "xai"
}
if pInfo, ok := s.registry.Providers[registryID]; ok {
for mID := range pInfo.Models {
models = append(models, mID)
@@ -969,7 +983,7 @@ func (s *Server) handleTestProvider(c *gin.Context) {
}
startTime := time.Now()
// Prepare a simple test request
testReq := &models.UnifiedRequest{
Model: "gpt-4o-mini", // Default cheap test model
@@ -1023,7 +1037,7 @@ func (s *Server) handleGetModels(c *gin.Context) {
// Merge registry models with DB overrides
var dbModels []db.ModelConfig
_ = s.database.Select(&dbModels, "SELECT * FROM model_configs")
dbMap := make(map[string]db.ModelConfig)
for _, m := range dbModels {
dbMap[m.ID] = m
@@ -1305,7 +1319,7 @@ func (s *Server) handleUpdateUser(c *gin.Context) {
func (s *Server) handleDeleteUser(c *gin.Context) {
id := c.Param("id")
session, _ := c.Get("session")
if sess, ok := session.(*Session); ok {
var username string