modified: main_dc/yalarba/api_yal/go.mod
modified: main_dc/yalarba/api_yal/go.sum modified: main_dc/yalarba/api_yal/internal/domain/auth/dto.go modified: main_dc/yalarba/api_yal/internal/domain/auth/handler.go modified: main_dc/yalarba/api_yal/internal/domain/auth/router.go modified: main_dc/yalarba/api_yal/internal/domain/auth/servcie.go new file: main_dc/yalarba/api_yal/internal/middleware/auth.go deleted: main_dc/yalarba/api_yal/internal/middleware/authMiddleware.go modified: main_dc/yalarba/api_yal/internal/router/router.go set auth domain, not tested
This commit is contained in:
@@ -24,6 +24,7 @@ require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-playground/validator/v10 v10.30.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
|
||||
@@ -13,6 +13,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package auth
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RegisterRequest - запрос на регистрацию
|
||||
type RegisterRequest struct {
|
||||
@@ -24,6 +27,22 @@ type AuthResponse struct {
|
||||
User UserInfo `json:"user"`
|
||||
}
|
||||
|
||||
// ResetPasswordRequest - запрос на сброс пароля
|
||||
type ResetPasswordRequest struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
|
||||
// RefreshTokenRequest - запрос на обновление токена
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest - запрос на смену пароля
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"old_password" validate:"required"`
|
||||
NewPassword string `json:"new_password" validate:"required,min=6"`
|
||||
}
|
||||
|
||||
// UserInfo информация о пользователе для ответа
|
||||
type UserInfo struct {
|
||||
ID uint `json:"id"`
|
||||
@@ -33,3 +52,9 @@ type UserInfo struct {
|
||||
FullName string `json:"full_name"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrInvalidPassword = errors.New("invalid password")
|
||||
ErrUserAlreadyExists = errors.New("user with this email already exists")
|
||||
)
|
||||
|
||||
@@ -5,10 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/internal/middleware"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AuthHandler обработчик для аутентификации
|
||||
@@ -20,7 +23,7 @@ type AuthHandler struct {
|
||||
// NewAuthHandler создает новый экземпляр AuthHandler
|
||||
func NewAuthHandler(authService *AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: *NewAuthService(),
|
||||
authService: *authService,
|
||||
validator: validator.New(),
|
||||
}
|
||||
}
|
||||
@@ -29,6 +32,7 @@ func NewAuthHandler(authService *AuthService) *AuthHandler {
|
||||
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация нового пользователя AuthHandler")
|
||||
|
||||
var req RegisterRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
@@ -55,5 +59,192 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
AuthService.Register(req)
|
||||
response, err := h.authService.Register(req)
|
||||
if err != nil {
|
||||
l.Error("Ошибка регистрации: %v", zap.Error(err))
|
||||
|
||||
status := http.StatusInternalServerError
|
||||
message := "Registration failed"
|
||||
|
||||
if errors.Is(err, ErrUserAlreadyExists) {
|
||||
status = http.StatusConflict
|
||||
message = "User with this email already exists"
|
||||
}
|
||||
|
||||
http.Error(w, message, status)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// Login вход пользователя
|
||||
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Вход пользователя AuthHandler")
|
||||
|
||||
var req LoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.validator.Struct(req); err != nil {
|
||||
var invalidValidationError *validator.InvalidValidationError
|
||||
if errors.As(err, &invalidValidationError) {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var errs []string
|
||||
for _, err := range err.(validator.ValidationErrors) {
|
||||
errs = append(errs, fmt.Sprintf("field %s is invalid: %s", err.Field(), err.Tag()))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"error": "Validation failed",
|
||||
"fields": errs,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.authService.Login(req)
|
||||
if err != nil {
|
||||
l.Error("Ошибка входа: %v", zap.Error(err))
|
||||
|
||||
status := http.StatusUnauthorized
|
||||
message := "Login failed"
|
||||
|
||||
if errors.Is(err, ErrUserNotFound) {
|
||||
message = "User not found"
|
||||
} else if errors.Is(err, ErrInvalidPassword) {
|
||||
message = "Invalid password"
|
||||
}
|
||||
|
||||
http.Error(w, message, status)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// RefreshToken обновление токена
|
||||
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Обновление токена AuthHandler")
|
||||
|
||||
// Получаем токен из заголовка Authorization
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Ожидаем формат "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.authService.RefreshToken(parts[1])
|
||||
if err != nil {
|
||||
l.Error("Ошибка обновления токена: %v", zap.Error(err))
|
||||
http.Error(w, "Token refresh failed", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// Logout выход пользователя
|
||||
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Выход пользователя AuthHandler")
|
||||
|
||||
// Получаем ID пользователя из контекста (устанавливается middleware)
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.authService.Logout(userID); err != nil {
|
||||
l.Error("Ошибка выхода: %v", zap.Error(err))
|
||||
http.Error(w, "Logout failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"message": "Successfully logged out",
|
||||
})
|
||||
}
|
||||
|
||||
// GetProfile получение профиля пользователя
|
||||
func (h *AuthHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Получение профиля пользователя")
|
||||
|
||||
// Получаем ID пользователя из контекста
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Реализовать получение профиля через сервис
|
||||
// response, err := h.authService.GetProfile(userID)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"message": "Profile endpoint - to be implemented",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProfile обновление профиля пользователя
|
||||
func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Обновление профиля пользователя")
|
||||
|
||||
// Получаем ID пользователя из контекста
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Реализовать обновление профиля
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"message": "Update profile endpoint - to be implemented",
|
||||
})
|
||||
}
|
||||
|
||||
// ChangePassword смена пароля
|
||||
func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
l.Debug("Смена пароля пользователя")
|
||||
|
||||
// Получаем ID пользователя из контекста
|
||||
userID, ok := r.Context().Value(middleware.UserIDKey).(uint)
|
||||
if !ok {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Реализовать смену пароля
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"message": "Change password endpoint - to be implemented",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,33 +3,39 @@ package auth
|
||||
import (
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/internal/middleware"
|
||||
"api_yal/internal/repository"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RegisterRoutes регистрирует маршруты аутентификации
|
||||
func RegisterRoutes(r chi.Router) {
|
||||
handler := NewAuthHandler(NewAuthService())
|
||||
func RegisterRoutes(r chi.Router, db *gorm.DB, jwtSecret string) {
|
||||
// Создаем репозиторий и сервис
|
||||
accountRepo := repository.NewAccountRepository(db)
|
||||
authService := NewAuthService(accountRepo, jwtSecret)
|
||||
handler := NewAuthHandler(&authService)
|
||||
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация маршрутов аутентификации")
|
||||
|
||||
r.Route("/auth", func(r chi.Router) {
|
||||
// Публичные маршруты (без аутентификации)
|
||||
r.Group(func(r chi.Router) {
|
||||
// r.Post("/login", handler.Login)
|
||||
r.Post("/login", handler.Login)
|
||||
r.Post("/register", handler.Register)
|
||||
// r.Post("/refresh", handler.RefreshToken)
|
||||
r.Post("/refresh", handler.RefreshToken)
|
||||
// r.Post("/reset-password", handler.ResetPassword)
|
||||
})
|
||||
|
||||
// Защищенные маршруты (требуют аутентификации)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.AuthMiddlewareWithContext) // middleware специфичный для auth
|
||||
r.Use(middleware.AuthMiddleware(jwtSecret))
|
||||
|
||||
// r.Post("/logout", handler.Logout)
|
||||
// r.Get("/profile", handler.GetProfile)
|
||||
// r.Put("/profile", handler.UpdateProfile)
|
||||
// r.Post("/change-password", handler.ChangePassword)
|
||||
r.Post("/logout", handler.Logout)
|
||||
r.Get("/profile", handler.GetProfile)
|
||||
r.Put("/profile", handler.UpdateProfile)
|
||||
r.Post("/change-password", handler.ChangePassword)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,34 +1,229 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"api_yal/internal/logger"
|
||||
"api_yal/internal/models"
|
||||
"api_yal/internal/repository"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
// AuthService интерфейс сервиса аутентификации
|
||||
type AuthService interface {
|
||||
Register(req RegisterRequest) (*AuthResponse, error)
|
||||
Login(req LoginRequest) (*AuthResponse, error)
|
||||
RefreshToken(token string) (*AuthResponse, error)
|
||||
Logout(userID uint) error
|
||||
}
|
||||
|
||||
func NewAuthService() *AuthService {
|
||||
return &AuthService{
|
||||
// authServiceImpl реализация сервиса аутентификации
|
||||
type authServiceImpl struct {
|
||||
accountRepo repository.AccountRepository
|
||||
jwtSecret []byte
|
||||
}
|
||||
|
||||
// NewAuthService создает новый экземпляр сервиса аутентификации
|
||||
func NewAuthService(accountRepo repository.AccountRepository, jwtSecret string) AuthService {
|
||||
return &authServiceImpl{
|
||||
accountRepo: accountRepo,
|
||||
jwtSecret: []byte(jwtSecret),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AuthService) Register(regReq RegisterRequest) (AuthResponse, error) {
|
||||
// Register регистрация нового пользователя
|
||||
func (s *authServiceImpl) Register(req RegisterRequest) (*AuthResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Регистрация пользователя AuthSerice")
|
||||
l.Debug("Регистрация пользователя AuthService")
|
||||
|
||||
// Проверяем, существует ли пользователь с таким email
|
||||
existingUser, err := s.accountRepo.GetByEmail(req.Email)
|
||||
if err == nil && existingUser != nil {
|
||||
return nil, ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
// Хешируем пароль
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(regReq.Password), bcrypt.DefaultCost)
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
l.Error("Ошибка хеширования пароля: %v", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Формируем полное имя
|
||||
fullName := req.FirstName + " " + req.LastName
|
||||
|
||||
// Создаем аккаунт
|
||||
newAcc := &models.Account{
|
||||
Email: req.Email,
|
||||
PasswordHash: string(hashedPassword),
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
FullName: fullName,
|
||||
IsActive: true,
|
||||
IsVerified: false,
|
||||
Role: "user",
|
||||
}
|
||||
|
||||
// Сохраняем в базу данных
|
||||
if err := s.accountRepo.Create(newAcc); err != nil {
|
||||
l.Error("Ошибка создания аккаунта: %v", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Генерируем JWT токен
|
||||
token, expiresAt, err := s.generateToken(newAcc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newAcc := &models.Account{
|
||||
Email: regReq.Email,
|
||||
|
||||
// Формируем ответ
|
||||
response := &AuthResponse{
|
||||
Token: token,
|
||||
ExpiresAt: expiresAt,
|
||||
User: UserInfo{
|
||||
ID: newAcc.Base.ID,
|
||||
Email: newAcc.Email,
|
||||
FirstName: newAcc.FirstName,
|
||||
LastName: newAcc.LastName,
|
||||
FullName: newAcc.FullName,
|
||||
Role: newAcc.Role,
|
||||
},
|
||||
}
|
||||
|
||||
l.Info("Пользователь успешно зарегистрирован: %s", zap.String("Email", req.Email))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// Login вход пользователя
|
||||
func (s *authServiceImpl) Login(req LoginRequest) (*AuthResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Вход пользователя: %s", zap.String("Email", req.Email))
|
||||
|
||||
// Ищем пользователя по email
|
||||
account, err := s.accountRepo.GetByEmail(req.Email)
|
||||
if err != nil {
|
||||
l.Error("Пользователь не найден:",
|
||||
zap.String("Email", req.Email),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
||||
// Проверяем, активен ли аккаунт
|
||||
if !account.IsActive {
|
||||
l.Error("Аккаунт деактивирован: %s", zap.String("Email", req.Email))
|
||||
return nil, errors.New("account is deactivated")
|
||||
}
|
||||
|
||||
// Сравниваем пароли
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(req.Password)); err != nil {
|
||||
l.Error("Неверный пароль для пользователя: %s", zap.String("Email", req.Email))
|
||||
return nil, ErrInvalidPassword
|
||||
}
|
||||
|
||||
// Генерируем JWT токен
|
||||
token, expiresAt, err := s.generateToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Формируем ответ
|
||||
response := &AuthResponse{
|
||||
Token: token,
|
||||
ExpiresAt: expiresAt,
|
||||
User: UserInfo{
|
||||
ID: account.Base.ID,
|
||||
Email: account.Email,
|
||||
FirstName: account.FirstName,
|
||||
LastName: account.LastName,
|
||||
FullName: account.FullName,
|
||||
Role: account.Role,
|
||||
},
|
||||
}
|
||||
|
||||
l.Info("Пользователь успешно вошел: %s", zap.String("Email", req.Email))
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RefreshToken обновление токена
|
||||
func (s *authServiceImpl) RefreshToken(token string) (*AuthResponse, error) {
|
||||
l := logger.Get()
|
||||
l.Debug("Обновление токена")
|
||||
|
||||
// Парсим и валидируем токен
|
||||
claims := &jwt.RegisteredClaims{}
|
||||
parsedToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
return s.jwtSecret, nil
|
||||
})
|
||||
|
||||
if err != nil || !parsedToken.Valid {
|
||||
l.Error("Невалидный токен для обновления: %v", zap.Error(err))
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
// Получаем ID пользователя из claims
|
||||
userID, err := claims.GetSubject()
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid token claims")
|
||||
}
|
||||
|
||||
// Получаем пользователя из базы
|
||||
var account *models.Account
|
||||
// Здесь нужно преобразовать string в uint
|
||||
// В реальном проекте нужно добавить метод GetByIDString или аналогичный
|
||||
// Для простоты используем существующий метод
|
||||
// account, err = s.accountRepo.GetByEmail(???)
|
||||
|
||||
// Временное решение - нужно добавить метод GetByID
|
||||
// Пока пропускаем для демонстрации
|
||||
_ = userID
|
||||
|
||||
// Генерируем новый токен
|
||||
newToken, expiresAt, err := s.generateToken(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AuthResponse{
|
||||
Token: newToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Logout выход пользователя
|
||||
func (s *authServiceImpl) Logout(userID uint) error {
|
||||
l := logger.Get()
|
||||
l.Debug("Выход пользователя: %d", zap.Uint("userID", userID))
|
||||
// В реальном проекте здесь можно добавить токен в черный список
|
||||
// или удалить refresh token из базы данных
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateToken генерирует JWT токен для пользователя
|
||||
func (s *authServiceImpl) generateToken(account *models.Account) (string, time.Time, error) {
|
||||
// Устанавливаем время истечения (24 часа)
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
// Создаем claims
|
||||
claims := jwt.MapClaims{
|
||||
"sub": account.Base.ID,
|
||||
"email": account.Email,
|
||||
"role": account.Role,
|
||||
"exp": expiresAt.Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Создаем токен
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Подписываем токен
|
||||
tokenString, err := token.SignedString(s.jwtSecret)
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
return tokenString, expiresAt, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"api_yal/internal/logger"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
// UserIDKey ключ для хранения ID пользователя в контексте
|
||||
UserIDKey contextKey = "userID"
|
||||
// UserEmailKey ключ для хранения email пользователя в контексте
|
||||
UserEmailKey contextKey = "userEmail"
|
||||
// UserRoleKey ключ для хранения роли пользователя в контексте
|
||||
UserRoleKey contextKey = "userRole"
|
||||
)
|
||||
|
||||
// AuthMiddleware создает middleware для проверки JWT токена
|
||||
func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := logger.Get()
|
||||
|
||||
// Получаем токен из заголовка Authorization
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
l.Debug("Отсутствует заголовок Authorization")
|
||||
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Ожидаем формат "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
l.Debug("Неверный формат заголовка Authorization")
|
||||
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
|
||||
// Парсим и валидируем токен
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
// Проверяем метод подписи
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
}
|
||||
return []byte(jwtSecret), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
l.Debug("Невалидный токен: %v", zap.Error(err))
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Извлекаем claims
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
l.Debug("Не удалось извлечь claims из токена")
|
||||
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Добавляем информацию о пользователе в контекст
|
||||
ctx := r.Context()
|
||||
|
||||
// Извлекаем userID из sub (subject)
|
||||
if userID, ok := claims["sub"].(float64); ok {
|
||||
ctx = context.WithValue(ctx, UserIDKey, uint(userID))
|
||||
}
|
||||
|
||||
// Извлекаем email
|
||||
if email, ok := claims["email"].(string); ok {
|
||||
ctx = context.WithValue(ctx, UserEmailKey, email)
|
||||
}
|
||||
|
||||
// Извлекаем роль
|
||||
if role, ok := claims["role"].(string); ok {
|
||||
ctx = context.WithValue(ctx, UserRoleKey, role)
|
||||
}
|
||||
|
||||
// Передаем управление дальше с обновленным контекстом
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AuthMiddlewareWithContext (для обратной совместимости)
|
||||
func AuthMiddlewareWithContext(next http.Handler) http.Handler {
|
||||
// Эта функция должна быть реализована в основном приложении
|
||||
// с передачей jwtSecret из конфигурации
|
||||
return next
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
UserIDKey contextKey = "userID"
|
||||
IsAuthKey contextKey = "isAuthenticated"
|
||||
)
|
||||
|
||||
// AuthMiddlewareWithContext добавляет информацию об авторизации в контекст
|
||||
func AuthMiddlewareWithContext(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Создаем контекст с тестовыми данными
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserIDKey, 0)
|
||||
ctx = context.WithValue(ctx, IsAuthKey, false)
|
||||
|
||||
// В реальном проекте здесь будет:
|
||||
// token := r.Header.Get("Authorization")
|
||||
// if token != "" {
|
||||
// userID, err := validateToken(token)
|
||||
// if err == nil {
|
||||
// ctx = context.WithValue(ctx, UserIDKey, userID)
|
||||
// ctx = context.WithValue(ctx, IsAuthKey, true)
|
||||
// }
|
||||
// }
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
||||
zapLogger.Debug("Health check маршрут зарегистрирован")
|
||||
|
||||
// Здесь можно добавить другие маршруты, которые будут защищены аутентификацией
|
||||
auth.RegisterRoutes(r)
|
||||
auth.RegisterRoutes(r, db, config.JWTSecret)
|
||||
// r.Mount("/api/v1", apiRoutes(db, config))
|
||||
|
||||
zapLogger.Info("Настройка маршрутов завершена")
|
||||
|
||||
Reference in New Issue
Block a user