On branch main

modified:   internal/domain/auth/dto.go
	modified:   internal/domain/auth/handler.go
	modified:   internal/domain/auth/router.go
	modified:   internal/domain/auth/servcie.go
	modified:   internal/middleware/auth.go
	modified:   internal/router/router.go
auth implemented without reset password
This commit is contained in:
2026-03-31 04:22:54 +05:00
parent 659cd3584c
commit 8b40d1bfe5
6 changed files with 372 additions and 119 deletions
@@ -1,3 +1,4 @@
// handler.go
package auth
import (
@@ -5,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"time"
"api_yal/internal/logger"
"api_yal/internal/middleware"
@@ -13,6 +15,12 @@ import (
"go.uber.org/zap"
)
// Cookie константы
const (
RefreshTokenCookieName = "refresh_token"
RefreshTokenExpiration = 7 * 24 * time.Hour // 7 дней
)
// AuthHandler обработчик для аутентификации
type AuthHandler struct {
authService AuthService
@@ -20,13 +28,39 @@ type AuthHandler struct {
}
// NewAuthHandler создает новый экземпляр AuthHandler
func NewAuthHandler(authService *AuthService) *AuthHandler {
func NewAuthHandler(authService AuthService) *AuthHandler {
return &AuthHandler{
authService: *authService,
authService: authService,
validator: validator.New(),
}
}
// setRefreshTokenCookie устанавливает HttpOnly cookie с refresh token
func (h *AuthHandler) setRefreshTokenCookie(w http.ResponseWriter, refreshToken string) {
http.SetCookie(w, &http.Cookie{
Name: RefreshTokenCookieName,
Value: refreshToken,
Path: "/",
HttpOnly: true,
Secure: true, // Всегда true в production
SameSite: http.SameSiteStrictMode,
MaxAge: int(RefreshTokenExpiration.Seconds()),
})
}
// clearRefreshTokenCookie удаляет refresh token cookie
func (h *AuthHandler) clearRefreshTokenCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: RefreshTokenCookieName,
Value: "",
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
MaxAge: -1,
})
}
// Register регистрация аккаунта пользователя
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
@@ -110,7 +144,7 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
return
}
response, err := h.authService.Login(req)
response, refreshToken, err := h.authService.Login(req)
if err != nil {
l.Error("Ошибка входа: %v", zap.Error(err))
@@ -127,39 +161,81 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
return
}
// Устанавливаем refresh token в HttpOnly cookie
h.setRefreshTokenCookie(w, refreshToken)
l.Debug("Завершение обработки запроса входа")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// RefreshToken обновление токена
// Поддерживает два способа получения refresh token:
// 1. Из HttpOnly cookie (для web приложений)
// 2. Из тела запроса (для мобильных приложений)
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
l.Info("Начало обработки запроса обновления токена")
var req RefreshTokenRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
var refreshToken string
// Пытаемся получить refresh token из cookie
cookie, err := r.Cookie(RefreshTokenCookieName)
if err == nil && cookie != nil {
refreshToken = cookie.Value
}
// Если в cookie нет, пробуем получить из тела запроса (для мобильных приложений)
if refreshToken == "" {
var req RefreshTokenRequest
if err := json.NewDecoder(r.Body).Decode(&req); err == nil {
refreshToken = req.RefreshToken
}
}
if refreshToken == "" {
http.Error(w, "Refresh token required", http.StatusBadRequest)
return
}
if err := h.validator.Struct(req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
response, err := h.authService.RefreshToken(req.RefreshToken)
response, err := h.authService.RefreshToken(refreshToken)
if err != nil {
l.Error("Ошибка обновления токена", zap.Error(err))
if errors.Is(err, ErrInvalidToken) || errors.Is(err, ErrTokenExpired) {
// Очищаем невалидный refresh token
h.clearRefreshTokenCookie(w)
http.Error(w, "Invalid or expired refresh token", http.StatusUnauthorized)
return
}
http.Error(w, "Token refresh failed", http.StatusUnauthorized)
return
}
// Генерируем новый refresh token
newRefreshToken, err := h.generateNewRefreshTokenFromUser(response.User.ID)
if err != nil {
l.Error("Ошибка генерации нового refresh token", zap.Error(err))
http.Error(w, "Failed to generate refresh token", http.StatusInternalServerError)
return
}
// Обновляем refresh token в cookie
h.setRefreshTokenCookie(w, newRefreshToken)
l.Info("Завершение обработки запроса обновления токена")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// generateNewRefreshTokenFromUser генерирует новый refresh token для пользователя
// Вспомогательная функция для обновления refresh token
func (h *AuthHandler) generateNewRefreshTokenFromUser(userID uint) (string, error) {
// Используем сервис для генерации refresh token
return h.authService.GenerateNewRefreshToken(userID)
}
// Logout выход пользователя
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
l := logger.Get()
@@ -178,6 +254,9 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
return
}
// Очищаем refresh token cookie
h.clearRefreshTokenCookie(w)
l.Info("Завершение обработки запроса выхода")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
@@ -247,4 +326,4 @@ func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
"user_id": userID,
"message": "Change password endpoint - to be implemented",
})
}
}