// handlers/auth.go package handlers import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" "go-rest-api/internal/models" "go-rest-api/internal/service" "go-rest-api/pkg/middleware" "go-rest-api/pkg/utils" "github.com/go-chi/chi/v5" ) type AuthHandler struct { authService service.AuthService jwtService service.JWTService } func NewAuthHandler(authService service.AuthService, jwtService service.JWTService) *AuthHandler { return &AuthHandler{ authService: authService, jwtService: jwtService, } } func (h *AuthHandler) Routes() chi.Router { r := chi.NewRouter() // Обработка OPTIONS запросов для CORS r.Options("/register", h.handleOptions) r.Options("/login", h.handleOptions) r.Options("/logout", h.handleOptions) r.Options("/profile", h.handleOptions) r.Post("/register", h.Register) r.Post("/login", h.Login) r.Post("/logout", h.Logout) r.Get("/profile", h.GetProfile) r.Put("/editProfile", h.UpdateProfile) return r } // Обработчик для OPTIONS запросов func (h *AuthHandler) handleOptions(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") w.Header().Set("Access-Control-Max-Age", "300") w.WriteHeader(http.StatusOK) } type RegisterRequest struct { Email string `json:"email"` Password string `json:"password"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Phone string `json:"phone"` Experience string `json:"experience"` Goals string `json:"goals"` Newsletter bool `json:"newsletter"` } type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } type UserResponse struct { ID uint `json:"id"` Email string `json:"email"` FirstName string `json:"firstName"` LastName string `json:"lastName"` Phone string `json:"phone"` Experience string `json:"experience"` Goals string `json:"goals"` Newsletter bool `json:"newsletter"` Role string `json:"role"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { // Устанавливаем CORS заголовки w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") // Логируем тело запроса для отладки bodyBytes, err := io.ReadAll(r.Body) if err != nil { utils.RespondWithError(w, http.StatusBadRequest, "Failed to read request body: "+err.Error()) return } // Восстанавливаем тело для дальнейшего использования r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) fmt.Printf("Raw request body: %s\n", string(bodyBytes)) var req RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { fmt.Printf("JSON decode error: %v\n", err) utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON payload: "+err.Error()) return } fmt.Printf("Parsed register request: %+v\n", req) // Валидация обязательных полей if req.FirstName == "" { utils.RespondWithError(w, http.StatusBadRequest, "First name is required") return } if req.LastName == "" { utils.RespondWithError(w, http.StatusBadRequest, "Last name is required") return } if req.Email == "" { utils.RespondWithError(w, http.StatusBadRequest, "Email is required") return } if req.Password == "" { utils.RespondWithError(w, http.StatusBadRequest, "Password is required") return } if len(req.Password) < 6 { utils.RespondWithError(w, http.StatusBadRequest, "Password must be at least 6 characters") return } user := &models.User{ Email: req.Email, Password: req.Password, FirstName: req.FirstName, LastName: req.LastName, Phone: req.Phone, Experience: req.Experience, Goals: req.Goals, Newsletter: req.Newsletter, Role: "user", CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := h.authService.Register(user); err != nil { fmt.Printf("Auth service error: %v\n", err) utils.RespondWithError(w, http.StatusBadRequest, err.Error()) return } // После успешной регистрации возвращаем данные пользователя utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{ "message": "User registered successfully", "user": toUserResponse(user), }) } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { // Устанавливаем CORS заголовки w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) return } // Валидация if req.Email == "" || req.Password == "" { utils.RespondWithError(w, http.StatusBadRequest, "Email and password are required") return } user, token, err := h.authService.Login(req.Email, req.Password) if err != nil { utils.RespondWithError(w, http.StatusUnauthorized, err.Error()) return } // Устанавливаем токен в куки http.SetCookie(w, &http.Cookie{ Name: "auth_token", Value: token, Path: "/", HttpOnly: true, Secure: false, // В production установить true SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(24 * time.Hour), }) utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ "message": "Login successful", "token": token, "user": toUserResponse(user), }) } func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { // Устанавливаем CORS заголовки w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") // Удаляем куку http.SetCookie(w, &http.Cookie{ Name: "auth_token", Value: "", Path: "/", HttpOnly: true, Secure: false, SameSite: http.SameSiteLaxMode, Expires: time.Now().Add(-1 * time.Hour), MaxAge: -1, }) utils.RespondWithJSON(w, http.StatusOK, map[string]string{ "message": "Logout successful", }) } func (h *AuthHandler) GetProfile(w http.ResponseWriter, r *http.Request) { // Устанавливаем CORS заголовки w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) w.Header().Set("Access-Control-Allow-Credentials", "true") user, ok := middleware.GetUserFromContext(r.Context()) if !ok { utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") return } utils.RespondWithJSON(w, http.StatusOK, toUserResponse(user)) } func toUserResponse(user *models.User) UserResponse { return UserResponse{ ID: user.ID, Email: user.Email, FirstName: user.FirstName, LastName: user.LastName, Phone: user.Phone, Experience: user.Experience, Goals: user.Goals, Newsletter: user.Newsletter, Role: user.Role, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, } }