backend-service/internal/middleware/auth.go
ats-tech25 8309f38999 feat(auth): Implement comprehensive authentication and authorization middleware
- Add JWT authentication middleware with token validation
- Implement user context extraction methods for user ID, email, and admin status
- Create admin middleware to restrict access to admin-only routes
- Add convenience method to combine authentication and admin authorization
- Update auth middleware to handle token parsing, validation, and context setting
- Enhance error handling for various authentication scenarios
- Add new JWT service and related dependencies in go.mod
2025-11-05 15:21:56 +00:00

148 lines
3.4 KiB
Go

package middleware
import (
"net/http"
"strings"
"attune-heart-therapy/internal/services"
"github.com/gin-gonic/gin"
)
// AuthMiddleware creates a middleware for JWT authentication
func AuthMiddleware(jwtService services.JWTService) gin.HandlerFunc {
return func(c *gin.Context) {
// Get token from Authorization header
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Authorization header is required",
})
c.Abort()
return
}
// Check if header starts with "Bearer "
tokenParts := strings.SplitN(authHeader, " ", 2)
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid authorization header format. Expected: Bearer <token>",
})
c.Abort()
return
}
tokenString := tokenParts[1]
if tokenString == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Token is required",
})
c.Abort()
return
}
// Validate the token
claims, err := jwtService.ValidateToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid or expired token",
})
c.Abort()
return
}
// Set user information in context for use in handlers
c.Set("user_id", claims.UserID)
c.Set("user_email", claims.Email)
c.Set("is_admin", claims.IsAdmin)
c.Set("jwt_claims", claims)
c.Next()
}
}
// GetUserIDFromContext extracts user ID from Gin context
func GetUserIDFromContext(c *gin.Context) (uint, bool) {
userID, exists := c.Get("user_id")
if !exists {
return 0, false
}
id, ok := userID.(uint)
return id, ok
}
// GetUserEmailFromContext extracts user email from Gin context
func GetUserEmailFromContext(c *gin.Context) (string, bool) {
email, exists := c.Get("user_email")
if !exists {
return "", false
}
emailStr, ok := email.(string)
return emailStr, ok
}
// IsAdminFromContext checks if user is admin from Gin context
func IsAdminFromContext(c *gin.Context) bool {
isAdmin, exists := c.Get("is_admin")
if !exists {
return false
}
admin, ok := isAdmin.(bool)
return ok && admin
}
// GetJWTClaimsFromContext extracts JWT claims from Gin context
func GetJWTClaimsFromContext(c *gin.Context) (*services.JWTClaims, bool) {
claims, exists := c.Get("jwt_claims")
if !exists {
return nil, false
}
jwtClaims, ok := claims.(*services.JWTClaims)
return jwtClaims, ok
}
// AdminMiddleware creates a middleware for admin authorization
// This middleware should be used after AuthMiddleware to ensure user is authenticated first
func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Check if user is authenticated (should be set by AuthMiddleware)
userID, exists := GetUserIDFromContext(c)
if !exists || userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Authentication required",
})
c.Abort()
return
}
// Check if user has admin privileges
if !IsAdminFromContext(c) {
c.JSON(http.StatusForbidden, gin.H{
"error": "Admin privileges required",
})
c.Abort()
return
}
c.Next()
}
}
// RequireAdmin is a convenience function that combines auth and admin middleware
func RequireAdmin(jwtService services.JWTService) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
// First authenticate the user
AuthMiddleware(jwtService)(c)
if c.IsAborted() {
return
}
// Then check admin privileges
AdminMiddleware()(c)
})
}