diff --git a/main_dc/yalarba/api_es/internal/dto/user.go b/main_dc/yalarba/api_es/internal/dto/user.go index 76efc39..c67f1f4 100644 --- a/main_dc/yalarba/api_es/internal/dto/user.go +++ b/main_dc/yalarba/api_es/internal/dto/user.go @@ -83,3 +83,8 @@ func ToUserResponse(user *models.User) UserResponse { CreatedAt: user.CreatedAt, } } + +// dto/auth.go (добавляем если нужно) +type RefreshTokenRequest struct { + RefreshToken string `json:"refresh_token" validate:"required"` +} diff --git a/main_dc/yalarba/api_es/internal/handler/user_handler.go b/main_dc/yalarba/api_es/internal/handler/user_handler.go index 53df86a..25554d1 100644 --- a/main_dc/yalarba/api_es/internal/handler/user_handler.go +++ b/main_dc/yalarba/api_es/internal/handler/user_handler.go @@ -1,13 +1,14 @@ package handler import ( - "encoding/json" - "net/http" - "strconv" "api_es/internal/dto" appMiddleware "api_es/internal/middleware" "api_es/internal/service" + "api_es/internal/utils" "api_es/pkg/logger" + "encoding/json" + "net/http" + "strconv" "github.com/go-chi/chi/v5" "github.com/go-playground/validator/v10" @@ -61,6 +62,9 @@ func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) { return } + // Устанавливаем куку с токеном + appMiddleware.SetAuthCookie(w, response.Token) + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) zapLogger.Debug("End register") @@ -103,11 +107,79 @@ func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) { return } + // Устанавливаем куку с токеном + appMiddleware.SetAuthCookie(w, response.Token) + w.Header().Set("Content-Type", "application/json") zapLogger.Debug("End login") json.NewEncoder(w).Encode(response) } +// Добавляем новый метод для logout +// Logout godoc +// @Summary Logout user +// @Description Clear authentication cookies and tokens +// @Tags auth +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} map[string]string +// @Router /auth/logout [post] +func (h *UserHandler) Logout(w http.ResponseWriter, r *http.Request) { + // Очищаем auth cookie + appMiddleware.ClearAuthCookie(w) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "message": "Successfully logged out", + }) +} + +// Добавляем метод для обновления токена +// RefreshToken godoc +// @Summary Refresh authentication token +// @Description Refresh JWT token using refresh token or existing auth +// @Tags auth +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} dto.AuthResponse +// @Failure 401 {object} map[string]string +// @Router /auth/refresh [post] +func (h *UserHandler) RefreshToken(w http.ResponseWriter, r *http.Request) { + userID, ok := r.Context().Value(appMiddleware.UserIDKey).(uint) + if !ok { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + user, err := h.userService.GetUserProfile(r.Context(), userID) + if err != nil { + http.Error(w, "User not found", http.StatusUnauthorized) + return + } + + // Генерируем новый токен + // В реальном приложении здесь должна быть логика с refresh token + jwtUtil := utils.NewJWTUtil("secret") + newToken, err := jwtUtil.GenerateToken(userID, user.Email, user.Role) + if err != nil { + http.Error(w, "Failed to generate token", http.StatusInternalServerError) + return + } + + // Обновляем куку + appMiddleware.SetAuthCookie(w, newToken) + + response := &dto.AuthResponse{ + Token: newToken, + User: *user, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + // GetProfile godoc // @Summary Get user profile // @Description Get current user profile @@ -243,24 +315,3 @@ func (h *UserHandler) ListUsers(w http.ResponseWriter, r *http.Request) { zapLogger.Debug("Debug end handler listUsers") json.NewEncoder(w).Encode(users) } - -func (h *UserHandler) Routes(r chi.Router) chi.Router { - - r.Route("/auth", func(r chi.Router) { - r.Post("/register", h.Register) - r.Post("/login", h.Login) - }) - - r.Route("/users", func(r chi.Router) { - r.Use(appMiddleware.AuthMiddleware) - - r.Get("/profile", h.GetProfile) - r.Put("/profile", h.UpdateProfile) - - // Admin routes - r.With(appMiddleware.AdminMiddleware).Get("/", h.ListUsers) - r.With(appMiddleware.AdminMiddleware).Get("/{id}", h.GetUser) - }) - - return r -} diff --git a/main_dc/yalarba/api_es/internal/middleware/auth.go b/main_dc/yalarba/api_es/internal/middleware/auth.go index d299a0e..ef283c9 100644 --- a/main_dc/yalarba/api_es/internal/middleware/auth.go +++ b/main_dc/yalarba/api_es/internal/middleware/auth.go @@ -1,3 +1,4 @@ +// auth.go package middleware import ( @@ -18,28 +19,54 @@ const ( UserRoleKey contextKey = "userRole" ) +// Cookie конфигурация +const ( + AuthCookieName = "auth_token" + CookieMaxAge = 24 * 60 * 60 // 24 часа +) + func AuthMiddleware(next http.Handler) http.Handler { zapLogger := logger.Get() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zapLogger.Debug("Debug start AuthMiddleware") + + var tokenString string + + // Пробуем получить токен из заголовка Authorization authHeader := r.Header.Get("Authorization") - zapLogger.Debug("authHeader", zap.String("authHeader", authHeader)) - if authHeader == "" { - http.Error(w, "Authorization header required", http.StatusUnauthorized) - return + if authHeader != "" { + tokenString = strings.Replace(authHeader, "Bearer ", "", 1) + zapLogger.Debug("Token from Authorization header", zap.String("token", tokenString)) } - tokenString := strings.Replace(authHeader, "Bearer ", "", 1) - zapLogger.Debug("tokenString", zap.String("tokenString", tokenString)) + // Если токена нет в заголовке, пробуем получить из куки if tokenString == "" { - http.Error(w, "Invalid token", http.StatusUnauthorized) + cookie, err := r.Cookie(AuthCookieName) + if err == nil && cookie.Value != "" { + tokenString = cookie.Value + zapLogger.Debug("Token from cookie", zap.String("token", tokenString)) + } + } + + if tokenString == "" { + http.Error(w, "Authorization required", http.StatusUnauthorized) return } - // Здесь нужно использовать ваш JWT утилити + // Валидируем токен jwtUtil := utils.NewJWTUtil("secret") claims, err := jwtUtil.ValidateToken(tokenString) if err != nil { + // Если токен невалиден, удаляем куку + http.SetCookie(w, &http.Cookie{ + Name: AuthCookieName, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) http.Error(w, "Invalid token", http.StatusUnauthorized) return } @@ -47,13 +74,39 @@ func AuthMiddleware(next http.Handler) http.Handler { ctx := context.WithValue(r.Context(), UserIDKey, claims.UserID) ctx = context.WithValue(ctx, UserEmailKey, claims.Email) ctx = context.WithValue(ctx, UserRoleKey, claims.Role) - + zapLogger.Debug("Debug end AuthMiddleware") next.ServeHTTP(w, r.WithContext(ctx)) }) } +// Вспомогательная функция для установки auth cookie +func SetAuthCookie(w http.ResponseWriter, token string) { + http.SetCookie(w, &http.Cookie{ + Name: AuthCookieName, + Value: token, + Path: "/", + MaxAge: CookieMaxAge, + HttpOnly: true, + Secure: true, // В production должно быть true + SameSite: http.SameSiteStrictMode, + }) +} + +// Вспомогательная функция для удаления auth cookie +func ClearAuthCookie(w http.ResponseWriter) { + http.SetCookie(w, &http.Cookie{ + Name: AuthCookieName, + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) +} + func AdminMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { role, ok := r.Context().Value(UserRoleKey).(string) diff --git a/main_dc/yalarba/api_es/internal/router/router.go b/main_dc/yalarba/api_es/internal/router/router.go index f13749a..d7243bc 100644 --- a/main_dc/yalarba/api_es/internal/router/router.go +++ b/main_dc/yalarba/api_es/internal/router/router.go @@ -37,9 +37,12 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Get("/check", h.HealthHandler().Check) }) + // router.go (обновляем секцию auth routes) r.Route("/auth", func(r chi.Router) { r.Post("/register", h.UserHandler().Register) r.Post("/login", h.UserHandler().Login) + r.Post("/logout", h.UserHandler().Logout) + r.Post("/refresh", h.UserHandler().RefreshToken) }) r.Route("/users", func(r chi.Router) {