112 lines
2.6 KiB
Go
112 lines
2.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"smoop-api/internal/crypto"
|
|
"smoop-api/internal/models"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
// UserContext holds structured user information from JWT
|
|
type UserContext struct {
|
|
ID uint `json:"id"`
|
|
Username string `json:"username"`
|
|
Role models.Role `json:"role"`
|
|
}
|
|
|
|
func Auth(jwtMgr *crypto.JWTManager) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
h := c.GetHeader("Authorization")
|
|
if !strings.HasPrefix(h, "Bearer ") {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing bearer token"})
|
|
return
|
|
}
|
|
tok := strings.TrimPrefix(h, "Bearer ")
|
|
token, err := jwtMgr.Parse(tok)
|
|
if err != nil || !token.Valid {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
|
return
|
|
}
|
|
claims, _ := token.Claims.(jwt.MapClaims)
|
|
userContext := UserContext{
|
|
ID: uint(claims["sub"].(float64)),
|
|
Username: claims["name"].(string),
|
|
Role: models.Role(claims["role"].(string)),
|
|
}
|
|
c.Set("user", userContext)
|
|
c.Set("claims", claims)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func RequireRole(role string) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
userContext, exists := c.Get("user")
|
|
if !exists {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
}
|
|
user, ok := userContext.(UserContext)
|
|
if !ok {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid user data"})
|
|
return
|
|
}
|
|
if string(user.Role) != role {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "forbidden"})
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// helpers used by handlers
|
|
func MustClaims(c *gin.Context) map[string]interface{} {
|
|
val, ok := c.Get("claims")
|
|
if !ok {
|
|
return jwt.MapClaims{}
|
|
}
|
|
switch t := val.(type) {
|
|
case jwt.MapClaims:
|
|
return t
|
|
case map[string]interface{}:
|
|
return jwt.MapClaims(t)
|
|
default:
|
|
return jwt.MapClaims{}
|
|
}
|
|
}
|
|
func ClaimUserID(claims map[string]interface{}) uint {
|
|
if claims == nil {
|
|
return 0
|
|
}
|
|
if v, ok := claims["sub"]; ok {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return uint(n)
|
|
case int:
|
|
return uint(n)
|
|
case int64:
|
|
return uint(n)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
func ClaimRole(claims map[string]interface{}) string {
|
|
if r, ok := claims["role"].(string); ok {
|
|
return r
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// New helper to get UserContext from context
|
|
func GetUserContext(c *gin.Context) (UserContext, bool) {
|
|
userContext, exists := c.Get("user")
|
|
if !exists {
|
|
return UserContext{}, false
|
|
}
|
|
|
|
user, ok := userContext.(UserContext)
|
|
return user, ok
|
|
}
|