sno-quiz/backend/internal/middleware/rbac.go
2025-09-17 22:22:14 +03:00

154 lines
3.7 KiB
Go

package middleware
import (
"fmt"
"sno/internal/service"
"sno/internal/types"
"github.com/gofiber/fiber/v2"
)
// AdminConfig holds configuration for admin middleware
type AdminConfig struct {
AdminService service.AdminService
}
// RequireRole middleware to check user roles
func RequireRole(roles ...types.UserRole) fiber.Handler {
return func(c *fiber.Ctx) error {
// Skip role checking for OPTIONS preflight requests
if c.Method() == "OPTIONS" {
return c.Next()
}
userData := GetTelegramUser(c)
if userData == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"success": false,
"message": "User not authenticated",
})
}
// Check if user has any of the required roles
userRole, err := getUserRole(c, userData.ID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"message": "Failed to verify user role",
})
}
for _, role := range roles {
if userRole == role {
return c.Next()
}
}
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"success": false,
"message": fmt.Sprintf("Insufficient permissions. Required role: one of %v", roles),
})
}
}
// RequireAdmin middleware to check if user is admin
func RequireAdmin(config AdminConfig) fiber.Handler {
return RequireRole(types.RoleAdmin)
}
// RequireOperator middleware to check if user is operator or admin
func RequireOperator(config AdminConfig) fiber.Handler {
return RequireRole(types.RoleAdmin, types.RoleOperator)
}
// getUserRole retrieves user role from database or cache
func getUserRole(c *fiber.Ctx, userID int64) (types.UserRole, error) {
// For now, we'll check if user exists in admins table
// In production, you might want to cache this in Redis
// Try to get from context first (cached)
if role, ok := c.Locals("user_role").(types.UserRole); ok {
return role, nil
}
// Get admin service from context
adminService, ok := c.Locals("admin_service").(service.AdminService)
if !ok {
// Fallback: if no admin service, assume user role
return types.RoleUser, nil
}
// Check if user is admin
role, err := adminService.GetUserRole(c.Context(), userID)
if err != nil {
// If user not found in admins table, they are regular user
return types.RoleUser, nil
}
// Cache role in context
c.Locals("user_role", role)
return role, nil
}
// AdminMiddleware middleware for admin routes
func AdminMiddleware(config AdminConfig) fiber.Handler {
return func(c *fiber.Ctx) error {
// Store admin service in context for role checking
c.Locals("admin_service", config.AdminService)
// Apply auth middleware first
return AuthMiddleware(AuthConfig{})(c)
}
}
// WithAdminRoles applies both admin middleware and role checking
func WithAdminRoles(config AdminConfig, roles ...types.UserRole) fiber.Handler {
return func(c *fiber.Ctx) error {
// First, apply admin middleware (includes auth)
if err := AdminMiddleware(config)(c); err != nil {
return err
}
// Then check roles
return RequireRole(roles...)(c)
}
}
// Convenience functions
func IsAdmin(c *fiber.Ctx) bool {
userData := GetTelegramUser(c)
if userData == nil {
return false
}
role, err := getUserRole(c, userData.ID)
if err != nil {
return false
}
return role == types.RoleAdmin
}
func IsOperator(c *fiber.Ctx) bool {
userData := GetTelegramUser(c)
if userData == nil {
return false
}
role, err := getUserRole(c, userData.ID)
if err != nil {
return false
}
return role == types.RoleOperator || role == types.RoleAdmin
}
func GetCurrentUserRole(c *fiber.Ctx) (types.UserRole, error) {
userData := GetTelegramUser(c)
if userData == nil {
return "", fmt.Errorf("user not authenticated")
}
return getUserRole(c, userData.ID)
}