fix: code quality, memory safety, and install improvements
Critical fixes: - Fix Dockerfile: reorder stages so frontend assets embed into Go binary - Fix Go version 1.25 (nonexistent) to 1.24 across Dockerfile, go.mod, CI - Add graceful game server shutdown on SIGTERM/SIGINT - Order startup tasks: updates complete before auto-start - Fix TOCTOU race in UpdateSettings with atomic Update() method Security: - Add optional AUTH_TOKEN bearer auth middleware on API/WS routes - Fix path traversal in DeleteMod using filepath.Rel instead of HasPrefix - Add input validation for IPPort, ServerParameters, ScheduledUpdate Memory safety: - Cap RPT buffer allocation to 64KB to prevent OOM on large logs - Cap GetLog file read to 10MB - Fix context cancel leak in SteamCmdManager.run() - Remove data-raced cancel field in steamcmd.go - Atomic file writes (write-temp-then-rename) across all managers Reliability: - Log save errors in ProcessManager.Stop() - Atomic file writes prevent corruption on crash Tests: - Add mod_manager_test.go (12 tests: ListWorkshopMods, ListLocalMods, BuildUsageMap, RemoveMod, dirSize) - Add scheduler_test.go (6 tests: Start/Stop, Refresh with empty, invalid, valid, and replaced cron expressions) - Add TestRestart to server_process_test.go CI/Docs: - Add -race flag to go test in CI and Makefile - Add npm lint step to CI - Add Go/npm module caching to CI - Update README: prerequisites, AUTH_TOKEN/GIN_MODE/SERVERS_DIR docs, fix manual quickstart to use make build
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"arma3-web-server/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -20,6 +24,7 @@ type Handler struct {
|
||||
cfgDir string
|
||||
profilesDir string
|
||||
frontendServed bool
|
||||
authToken string
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -47,11 +52,44 @@ func New(
|
||||
cfgDir: cfgDir,
|
||||
profilesDir: profilesDir,
|
||||
frontendServed: frontendServed,
|
||||
authToken: os.Getenv("AUTH_TOKEN"),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) authMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if h.authToken == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
auth := c.GetHeader("Authorization")
|
||||
if !strings.HasPrefix(auth, "Bearer ") {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing or invalid authorization header"})
|
||||
return
|
||||
}
|
||||
token := strings.TrimPrefix(auth, "Bearer ")
|
||||
if !secureCompare(token, h.authToken) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func secureCompare(a, b string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *Handler) SetupRoutes(r *gin.Engine) {
|
||||
api := r.Group("/api")
|
||||
api := r.Group("/api", h.authMiddleware())
|
||||
{
|
||||
api.GET("/server/settings", h.GetSettings)
|
||||
api.PUT("/server/settings", h.UpdateSettings)
|
||||
@@ -93,7 +131,8 @@ func (h *Handler) SetupRoutes(r *gin.Engine) {
|
||||
api.POST("/mods/cleanup", h.CleanupMods)
|
||||
}
|
||||
|
||||
r.GET("/ws/server/logs", h.StreamLogs)
|
||||
r.GET("/ws/steamcmd/logs", h.StreamSteamCMDLogs)
|
||||
r.GET("/ws/server/rpt", h.StreamRPTLogs)
|
||||
ws := r.Group("/ws", h.authMiddleware())
|
||||
ws.GET("/server/logs", h.StreamLogs)
|
||||
ws.GET("/steamcmd/logs", h.StreamSteamCMDLogs)
|
||||
ws.GET("/server/rpt", h.StreamRPTLogs)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user