diff --git a/main_dc/yalarba/api_yal/internal/middleware/auth.go b/main_dc/yalarba/api_yal/internal/middleware/auth.go new file mode 100644 index 0000000..8f1182f --- /dev/null +++ b/main_dc/yalarba/api_yal/internal/middleware/auth.go @@ -0,0 +1,119 @@ +// auth.go +package middleware + +import ( + "api_yal/internal/utils" + "api_yal/internal/logger" + "context" + "net/http" + "strings" + + "go.uber.org/zap" +) + +type contextKey string + +const ( + UserIDKey contextKey = "userID" + UserEmailKey contextKey = "userEmail" + 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") + if authHeader != "" { + tokenString = strings.Replace(authHeader, "Bearer ", "", 1) + zapLogger.Debug("Token from Authorization header", zap.String("token", tokenString)) + } + + // Если токена нет в заголовке, пробуем получить из куки + if tokenString == "" { + 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 + } + + // Валидируем токен + 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 + } + + 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) + if !ok || role != "admin" { + http.Error(w, "Admin access required", http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/main_dc/yalarba/api_yal/internal/models/authentication.go b/main_dc/yalarba/api_yal/internal/models/authentication.go new file mode 100644 index 0000000..63ef49d --- /dev/null +++ b/main_dc/yalarba/api_yal/internal/models/authentication.go @@ -0,0 +1,45 @@ +package models + +import ( + +) + +// AuthRequest - запрос на аутентификацию +type AuthRequest struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` +} + +// AuthResponse - ответ с токенами +type AuthResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` // Bearer + ExpiresIn int64 `json:"expires_in"` + User UserResponse `json:"user"` +} + +// RefreshTokenRequest - запрос на обновление токена +type RefreshTokenRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} + +// UserRegisterRequest - запрос на регистрацию +type UserRegisterRequest struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` + FullName string `json:"full_name" binding:"required"` + Phone string `json:"phone"` + City string `json:"city"` +} + +// PasswordResetRequest - запрос на сброс пароля +type PasswordResetRequest struct { + Email string `json:"email" binding:"required,email"` +} + +// PasswordResetConfirmRequest - подтверждение сброса пароля +type PasswordResetConfirmRequest struct { + Token string `json:"token" binding:"required"` + Password string `json:"password" binding:"required,min=6"` +}