mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 18:52:56 +00:00
dev day #89
This commit is contained in:
@@ -75,6 +75,55 @@ func JWTAuth(db *gorm.DB) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// JWTOptional attempts to authenticate the request if a token or auth cookie is present.
|
||||
func JWTOptional(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" {
|
||||
if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" {
|
||||
c.Set("userRole", "admin")
|
||||
c.Set("user", &models.User{Role: "admin"})
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
var tokenString string
|
||||
if authHeader := c.GetHeader("Authorization"); authHeader != "" {
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) == 2 && parts[0] == "Bearer" {
|
||||
tokenString = parts[1]
|
||||
}
|
||||
}
|
||||
if tokenString == "" {
|
||||
if cookie, err := c.Request.Cookie("auth_token"); err == nil {
|
||||
tokenString = cookie.Value
|
||||
}
|
||||
}
|
||||
|
||||
if tokenString == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
claims, err := utils.ParseJWT(tokenString)
|
||||
if err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := db.First(&user, claims.UserID).Error; err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user", &user)
|
||||
c.Set("claims", claims)
|
||||
c.Set("userID", user.ID)
|
||||
c.Set("userRole", user.Role)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// DevBypass checks for special dev header and grants admin role when not in production
|
||||
func DevBypass() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fotbal-club/internal/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -64,6 +65,14 @@ func CSRFProtection() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
// Dev-only: skip CSRF when using X-Admin-Token (remote admin tools)
|
||||
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" {
|
||||
if token := c.GetHeader("X-Admin-Token"); token != "" && token == config.AppConfig.AdminAccessToken {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get token from header or form
|
||||
token := c.GetHeader("X-CSRF-Token")
|
||||
if token == "" {
|
||||
|
||||
@@ -15,10 +15,10 @@ func DBContext() gin.HandlerFunc {
|
||||
// 15 seconds is generous for most queries while preventing indefinite hangs
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
||||
// Store the context so controllers can use it with db.WithContext(ctx)
|
||||
c.Set("dbCtx", ctx)
|
||||
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fotbal-club/internal/services"
|
||||
"fotbal-club/pkg/logger"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ErrorStatusReporter(reporter *services.ErrorReporter) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
if reporter == nil {
|
||||
return
|
||||
}
|
||||
status := c.Writer.Status()
|
||||
if status >= 500 {
|
||||
msg := ""
|
||||
if len(c.Errors) > 0 {
|
||||
var parts []string
|
||||
for _, e := range c.Errors {
|
||||
if e != nil && e.Err != nil {
|
||||
parts = append(parts, e.Err.Error())
|
||||
} else if e != nil {
|
||||
parts = append(parts, e.Error())
|
||||
}
|
||||
}
|
||||
msg = strings.Join(parts, "; ")
|
||||
}
|
||||
reporter.Report(c.Request.Context(), &services.ErrorEvent{
|
||||
Origin: "backend",
|
||||
Language: "go",
|
||||
Severity: "error",
|
||||
Message: msg,
|
||||
URL: c.Request.URL.Path,
|
||||
Method: c.Request.Method,
|
||||
Status: status,
|
||||
RequestID: GetRequestID(c),
|
||||
})
|
||||
logger.Error("Reported 5xx status=%d path=%s", status, c.Request.URL.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"runtime/debug"
|
||||
|
||||
"fotbal-club/pkg/logger"
|
||||
"fotbal-club/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -41,3 +42,40 @@ func CustomRecovery() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func CustomRecoveryWithReporter(reporter *services.ErrorReporter) gin.HandlerFunc {
|
||||
if reporter == nil {
|
||||
return CustomRecovery()
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
stack := string(debug.Stack())
|
||||
requestID := GetRequestID(c)
|
||||
logger.Error("Panic recovered",
|
||||
"request_id", requestID,
|
||||
"error", fmt.Sprintf("%v", err),
|
||||
"stack", stack,
|
||||
"path", c.Request.URL.Path,
|
||||
"method", c.Request.Method,
|
||||
)
|
||||
reporter.Report(c.Request.Context(), &services.ErrorEvent{
|
||||
Origin: "backend",
|
||||
Language: "go",
|
||||
Severity: "fatal",
|
||||
Message: fmt.Sprintf("%v", err),
|
||||
Stack: stack,
|
||||
URL: c.Request.URL.Path,
|
||||
Method: c.Request.Method,
|
||||
RequestID: requestID,
|
||||
})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Internal server error",
|
||||
"request_id": requestID,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,18 @@ func ValidateContentType() gin.HandlerFunc {
|
||||
path := c.Request.URL.Path
|
||||
|
||||
// Allow multipart for uploads and image processing crop upload
|
||||
if strings.Contains(path, "/upload") || strings.Contains(path, "/image-processing/crop-upload") {
|
||||
if strings.Contains(path, "/upload") || strings.Contains(path, "/image-processing/crop-upload") || strings.Contains(path, "/admin/scoreboard/qr") || strings.Contains(path, "/admin/scoreboard/load") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Allow scoreboard timer control endpoints without requiring JSON body
|
||||
// These actions do not read request body and are triggered via simple POSTs from remote UI
|
||||
if strings.Contains(path, "/admin/scoreboard/timer/") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if strings.Contains(path, "/admin/scoreboard/swap-sides") || strings.Contains(path, "/admin/scoreboard/second-half") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user