150 lines
3.1 KiB
Go
150 lines
3.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/wmms/wmms/internal/service"
|
|
"github.com/wmms/wmms/pkg/response"
|
|
)
|
|
|
|
const (
|
|
AuthorizationHeader = "Authorization"
|
|
BearerPrefix = "Bearer "
|
|
UserIDKey = "user_id"
|
|
UsernameKey = "username"
|
|
RolesKey = "roles"
|
|
)
|
|
|
|
// AuthMiddleware validates JWT tokens
|
|
func AuthMiddleware() gin.HandlerFunc {
|
|
authSvc := service.NewAuthService()
|
|
|
|
return func(c *gin.Context) {
|
|
authHeader := c.GetHeader(AuthorizationHeader)
|
|
if authHeader == "" {
|
|
response.Unauthorized(c, "missing authorization header")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(authHeader, BearerPrefix) {
|
|
response.Unauthorized(c, "invalid authorization format")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
tokenString := strings.TrimPrefix(authHeader, BearerPrefix)
|
|
|
|
token, err := authSvc.ValidateToken(tokenString)
|
|
if err != nil {
|
|
response.Unauthorized(c, "invalid or expired token")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
// Set user info in context
|
|
c.Set(UserIDKey, token.UserID)
|
|
c.Set(UsernameKey, token.Username)
|
|
c.Set(RolesKey, token.Roles)
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// AdminOnly middleware restricts access to admin role only
|
|
func AdminOnly() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
roles, exists := c.Get(RolesKey)
|
|
if !exists {
|
|
response.Unauthorized(c, "unauthorized")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
if !hasRole(roles, "super_admin") {
|
|
response.Forbidden(c, "admin access required")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// GetCurrentUserID returns the current user's ID from context
|
|
func GetCurrentUserID(c *gin.Context) int64 {
|
|
if id, exists := c.Get(UserIDKey); exists {
|
|
return id.(int64)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// GetCurrentUsername returns the current username from context
|
|
func GetCurrentUsername(c *gin.Context) string {
|
|
if username, exists := c.Get(UsernameKey); exists {
|
|
return username.(string)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetCurrentRoles returns the authenticated role-code snapshot. This is the
|
|
// extension point for replacing token roles with a request-time principal.
|
|
func GetCurrentRoles(c *gin.Context) []string {
|
|
if roles, exists := c.Get(RolesKey); exists {
|
|
if values, ok := roles.([]string); ok {
|
|
return values
|
|
}
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
func IsSuperAdmin(c *gin.Context) bool {
|
|
return hasRole(GetCurrentRoles(c), "super_admin")
|
|
}
|
|
|
|
func hasRole(value interface{}, expected string) bool {
|
|
roles, ok := value.([]string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
for _, role := range roles {
|
|
if role == expected {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// OptionalAuth middleware extracts user info if token is present but doesn't require it
|
|
func OptionalAuth() gin.HandlerFunc {
|
|
authSvc := service.NewAuthService()
|
|
|
|
return func(c *gin.Context) {
|
|
authHeader := c.GetHeader(AuthorizationHeader)
|
|
if authHeader == "" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
if !strings.HasPrefix(authHeader, BearerPrefix) {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
tokenString := strings.TrimPrefix(authHeader, BearerPrefix)
|
|
|
|
token, err := authSvc.ValidateToken(tokenString)
|
|
if err != nil {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
c.Set(UserIDKey, token.UserID)
|
|
c.Set(UsernameKey, token.Username)
|
|
c.Set(RolesKey, token.Roles)
|
|
|
|
c.Next()
|
|
}
|
|
}
|