93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
"git.gobha.me/xcaliber/chat-switchboard/config"
|
|
"git.gobha.me/xcaliber/chat-switchboard/database"
|
|
)
|
|
|
|
// Claims represents the JWT payload. Must match handlers.Claims.
|
|
type Claims struct {
|
|
UserID string `json:"user_id"`
|
|
Email string `json:"email"`
|
|
Role string `json:"role"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
// Auth returns a Gin middleware that validates JWT bearer tokens.
|
|
// When no database is connected (unmanaged mode), all requests
|
|
// are allowed through without authentication.
|
|
func Auth(cfg *config.Config) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Skip auth when running without a database (unmanaged mode)
|
|
if !database.IsConnected() {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
header := c.GetHeader("Authorization")
|
|
if header == "" {
|
|
// WebSocket connections can't set headers from browser.
|
|
// Fall back to ?token= query parameter.
|
|
if qToken := c.Query("token"); qToken != "" {
|
|
header = "Bearer " + qToken
|
|
}
|
|
}
|
|
if header == "" {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "missing authorization header",
|
|
})
|
|
return
|
|
}
|
|
|
|
tokenString := strings.TrimPrefix(header, "Bearer ")
|
|
if tokenString == header {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "invalid authorization format",
|
|
})
|
|
return
|
|
}
|
|
|
|
claims := &Claims{}
|
|
token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) {
|
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, jwt.ErrSignatureInvalid
|
|
}
|
|
return []byte(cfg.JWTSecret), nil
|
|
})
|
|
|
|
if err != nil || !token.Valid {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
|
"error": "invalid or expired token",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Store claims in context for downstream handlers
|
|
c.Set("user_id", claims.UserID)
|
|
c.Set("email", claims.Email)
|
|
c.Set("role", claims.Role)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// CORS returns a middleware that sets cross-origin headers.
|
|
// NOTE: If you have a separate cors.go, delete it — CORS lives here only.
|
|
func CORS() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Header("Access-Control-Allow-Origin", "*")
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|