From 397dad830f7c8ae94cb5ea3f3bb7cf74d3bbefa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B0=D0=BD=D0=B5=D0=B5=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Tue, 28 Oct 2025 21:23:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B3=D0=B8=D0=B1=D0=BA=D0=B0=D1=8F=20=D1=81?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20=D0=B0=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=B5=20=D1=80=D0=BE=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен базовый middleware userIdentity для валидации токенов - Реализован middleware requireRole для проверки конкретных ролей - Добавлены вспомогательные методы requireAdmin, requireTeacher, requireStudent - Обновлена структура роутов с разделением прав доступа - Добавлены примеры handler'ов: getAllUsers, getUserByUsername, deleteUser - Исправлен роутинг api группы (было router.Group, стало serviceRouter.Group) Теперь поддерживается: - GET /api/users - доступ для всех авторизованных пользователей - GET /api/users/:username - доступ для всех авторизованных - POST /api/users/:username - только для администраторов - DELETE /api/users/:username - только для администраторов 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/handler/handler.go | 21 +++++++-- internal/handler/middleware.go | 82 ++++++++++++++++++++++++++++++++-- internal/handler/users.go | 39 ++++++++++++++++ 3 files changed, 134 insertions(+), 8 deletions(-) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 53df14f..4c2b410 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -20,21 +20,34 @@ func (h *Handler) InitRoutes() *gin.Engine { router := gin.New() serviceRouter := router.Group("/auth-service") { + // ========== Публичные эндпоинты (без авторизации) ========== auth := serviceRouter.Group("/auth") { auth.POST("/sign-up", h.signUp) auth.POST("/sign-in", h.signIn) auth.POST("/refresh", h.refresh) } - api := router.Group("/api") + + // ========== Защищенные эндпоинты ========== + api := serviceRouter.Group("/api") { - users := api.Group("/users", h.checkAdminIdentity) + // Группа /users - базовая авторизация на всей группе + users := api.Group("/users", h.userIdentity) { - users.POST("/:username", h.changeUserRole) + // GET /api/users - любой авторизованный пользователь + users.GET("", h.getAllUsers) + + // GET /api/users/:username - любой авторизованный пользователь + users.GET("/:username", h.getUserByUsername) + + // POST /api/users/:username - ТОЛЬКО администраторы + users.POST("/:username", h.requireAdmin(), h.changeUserRole) + + // DELETE /api/users/:username - ТОЛЬКО администраторы + users.DELETE("/:username", h.requireAdmin(), h.deleteUser) } } } return router } - \ No newline at end of file diff --git a/internal/handler/middleware.go b/internal/handler/middleware.go index 0083c71..4079584 100644 --- a/internal/handler/middleware.go +++ b/internal/handler/middleware.go @@ -6,14 +6,88 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" ) const ( authorizationHeader = "Authorization" - roleKey = "user_role" + userRoleKey = "user_role" + userIdKey = "user_id" ) +// userIdentity - базовый middleware, извлекает пользователя из токена +// Не проверяет роли, только валидность токена +func (h *Handler) userIdentity(c *gin.Context) { + header := c.GetHeader(authorizationHeader) + if header == "" { + newErrorResponse(c, http.StatusUnauthorized, "Пустой header авторизации") + c.Abort() + return + } + + headerParts := strings.Split(header, " ") + if len(headerParts) != 2 || headerParts[0] != "Bearer" { + newErrorResponse(c, http.StatusUnauthorized, "Невалидный формат токена") + c.Abort() + return + } + + userRole, err := h.services.ParseToken(headerParts[1]) + if err != nil { + newErrorResponse(c, http.StatusUnauthorized, "Невалидный токен") + c.Abort() + return + } + + c.Set(userRoleKey, userRole) + c.Next() +} + +// requireRole - middleware-фабрика, возвращает middleware для проверки конкретных ролей +func (h *Handler) requireRole(allowedRoles ...internal.UserRole) gin.HandlerFunc { + return func(c *gin.Context) { + userRole, exists := c.Get(userRoleKey) + if !exists { + newErrorResponse(c, http.StatusUnauthorized, "Пользователь не авторизован") + c.Abort() + return + } + + userRoleStr, ok := userRole.(string) + if !ok { + newErrorResponse(c, http.StatusInternalServerError, "Ошибка извлечения роли пользователя") + c.Abort() + return + } + + // Проверяем, есть ли роль пользователя в списке разрешенных + for _, allowedRole := range allowedRoles { + if userRoleStr == allowedRole.ToString() { + c.Next() + return + } + } + + newErrorResponse(c, http.StatusForbidden, "Недостаточно прав для выполнения запроса") + c.Abort() + } +} + +// Вспомогательные методы для удобства + +// requireAdmin - только администраторы +func (h *Handler) requireAdmin() gin.HandlerFunc { + return h.requireRole(internal.Admin) +} + +// requireTeacher - учителя и администраторы +func (h *Handler) requireTeacher() gin.HandlerFunc { + return h.requireRole(internal.Teacher, internal.Admin) +} + +func (h *Handler) requireStudent() gin.HandlerFunc { + return h.requireRole(internal.Student, internal.Teacher, internal.Admin) +} + func (h *Handler) checkAdminIdentity(c *gin.Context) { header := c.GetHeader(authorizationHeader) if header == "" { @@ -28,7 +102,7 @@ func (h *Handler) checkAdminIdentity(c *gin.Context) { } userRole, err := h.services.ParseToken(headerParts[1]) - + if userRole != string(internal.Admin) { newErrorResponse(c, http.StatusUnauthorized, "Недостаточно прав для выполнения запроса") return @@ -38,5 +112,5 @@ func (h *Handler) checkAdminIdentity(c *gin.Context) { return } - c.Set(roleKey, userRole) + c.Set(userRoleKey, userRole) } diff --git a/internal/handler/users.go b/internal/handler/users.go index 475e6f6..700dfe7 100644 --- a/internal/handler/users.go +++ b/internal/handler/users.go @@ -10,6 +10,7 @@ type ChangeUserRoleRequest struct { Role string `json:"role"` } +// changeUserRole - изменение роли пользователя (только для админов) func (h *Handler) changeUserRole(c *gin.Context) { var input ChangeUserRoleRequest @@ -34,3 +35,41 @@ func (h *Handler) changeUserRole(c *gin.Context) { "newRole": role, }) } + +// Примеры других handler'ов для демонстрации использования middleware + +// getAllUsers - получить список всех пользователей (доступно всем авторизованным) +func (h *Handler) getAllUsers(c *gin.Context) { + // Получаем роль текущего пользователя из контекста + userRole, _ := c.Get(userRoleKey) + + c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Список пользователей", + "your_role": userRole, + "access_level": "любой авторизованный пользователь", + }) +} + +// getUserByUsername - получить пользователя по username (доступно всем авторизованным) +func (h *Handler) getUserByUsername(c *gin.Context) { + username := c.Param("username") + userRole, _ := c.Get(userRoleKey) + + c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Информация о пользователе", + "username": username, + "your_role": userRole, + "access_level": "любой авторизованный пользователь", + }) +} + +// deleteUser - удалить пользователя (только для админов) +func (h *Handler) deleteUser(c *gin.Context) { + username := c.Param("username") + + c.JSON(http.StatusOK, map[string]interface{}{ + "message": "Пользователь удален", + "username": username, + "access_level": "только администраторы", + }) +}