From 700e404a067f4d15bd88a555b023fc178d75c9fb Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Thu, 9 Oct 2025 05:59:40 +0500 Subject: [PATCH] add rest api for api_bb vue a lot of files --- serv_nginx/api_bb/.env | 8 + serv_nginx/api_bb/cmd/main.go | 59 ++--- serv_nginx/api_bb/go.mod | 3 +- serv_nginx/api_bb/go.sum | 2 + serv_nginx/api_bb/internal/config/config.go | 56 ++--- serv_nginx/api_bb/internal/handlers/auth.go | 213 ++++++++++++------ serv_nginx/api_bb/internal/handlers/common.go | 16 -- .../api_bb/internal/handlers/handlers.go | 31 ++- serv_nginx/api_bb/internal/handlers/health.go | 7 +- serv_nginx/api_bb/internal/models/user.go | 47 ++-- serv_nginx/api_bb/internal/routes/routes.go | 30 ++- .../api_bb/internal/service/auth_service.go | 82 ++++--- .../api_bb/internal/service/jwt_service.go | 61 +++++ serv_nginx/api_bb/pkg/middleware/auth.go | 88 ++++++++ .../api_bb/pkg/middleware/middleware.go | 2 +- serv_nginx/api_bb/pkg/utils/utils.go | 16 ++ serv_nginx/docker-compose.yml | 2 + 17 files changed, 512 insertions(+), 211 deletions(-) create mode 100644 serv_nginx/api_bb/.env delete mode 100644 serv_nginx/api_bb/internal/handlers/common.go create mode 100644 serv_nginx/api_bb/internal/service/jwt_service.go create mode 100644 serv_nginx/api_bb/pkg/middleware/auth.go create mode 100644 serv_nginx/api_bb/pkg/utils/utils.go diff --git a/serv_nginx/api_bb/.env b/serv_nginx/api_bb/.env new file mode 100644 index 0000000..e65872d --- /dev/null +++ b/serv_nginx/api_bb/.env @@ -0,0 +1,8 @@ +PORT=8080 +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=postgres +DB_NAME=bb_db +DB_SSLMODE=disable +JWT_SECRET=your-super-secret-jwt-key-change-in-production \ No newline at end of file diff --git a/serv_nginx/api_bb/cmd/main.go b/serv_nginx/api_bb/cmd/main.go index a310d87..e8ac957 100644 --- a/serv_nginx/api_bb/cmd/main.go +++ b/serv_nginx/api_bb/cmd/main.go @@ -1,36 +1,37 @@ +// main.go package main import ( - "log" - "net/http" - - "go-rest-api/internal/config" - "go-rest-api/internal/routes" - "go-rest-api/pkg/database" + "log" + "net/http" + + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "go-rest-api/internal/config" + "go-rest-api/internal/models" + "go-rest-api/internal/routes" ) func main() { - // Load configuration - cfg := config.Load() - - log.Printf("Connecting to database with DSN: %s", maskPassword(cfg.DatabaseURL)) - - // Initialize database - db, err := database.InitDB(cfg.DatabaseURL) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - - // Initialize router - router := routes.SetupRouter(db) - - log.Printf("Server starting on port %s", cfg.Port) - log.Fatal(http.ListenAndServe(":"+cfg.Port, router)) -} - -// maskPassword скрывает пароль в логах -func maskPassword(dsn string) string { - // Простая маскировка пароля в DSN для безопасности - // В реальном приложении лучше использовать более надежный метод - return dsn + // Загрузка конфигурации + cfg := config.Load() + + // Подключение к базе данных + db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{}) + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + + // Автомиграция + if err := db.AutoMigrate(&models.User{}); err != nil { + log.Fatal("Failed to migrate database:", err) + } + + // Настройка роутера + router := routes.SetupRouter(db, cfg) + + // Запуск сервера + log.Printf("Server starting on port %s", cfg.Port) + log.Fatal(http.ListenAndServe(":"+cfg.Port, router)) } \ No newline at end of file diff --git a/serv_nginx/api_bb/go.mod b/serv_nginx/api_bb/go.mod index eb6235b..6efed7f 100644 --- a/serv_nginx/api_bb/go.mod +++ b/serv_nginx/api_bb/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + golang.org/x/crypto v0.31.0 gorm.io/gorm v1.25.10 ) @@ -13,12 +14,12 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - golang.org/x/crypto v0.31.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect ) require ( + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect gorm.io/driver/postgres v1.6.0 diff --git a/serv_nginx/api_bb/go.sum b/serv_nginx/api_bb/go.sum index d4d8d9e..6729228 100644 --- a/serv_nginx/api_bb/go.sum +++ b/serv_nginx/api_bb/go.sum @@ -5,6 +5,8 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/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= diff --git a/serv_nginx/api_bb/internal/config/config.go b/serv_nginx/api_bb/internal/config/config.go index ab98c70..cde2f46 100644 --- a/serv_nginx/api_bb/internal/config/config.go +++ b/serv_nginx/api_bb/internal/config/config.go @@ -1,43 +1,47 @@ +// config/config.go package config import ( - "fmt" - "os" + "fmt" + "os" ) type Config struct { - Port string - DatabaseURL string + Port string + DatabaseURL string + JWTSecret string } func Load() *Config { - port := getEnv("PORT", "8080") - - // Формируем DSN для PostgreSQL из переменных окружения - databaseURL := getPostgresDSN() - - return &Config{ - Port: port, - DatabaseURL: databaseURL, - } + port := getEnv("PORT", "8080") + jwtSecret := getEnv("JWT_SECRET", "your-secret-key") + + // Формируем DSN для PostgreSQL из переменных окружения + databaseURL := getPostgresDSN() + + return &Config{ + Port: port, + DatabaseURL: databaseURL, + JWTSecret: jwtSecret, + } } func getPostgresDSN() string { - host := getEnv("DB_HOST", "localhost") - port := getEnv("DB_PORT", "5432") - user := getEnv("DB_USER", "postgres") - password := getEnv("DB_PASSWORD", "postgres") - dbname := getEnv("DB_NAME", "bb_db") - sslmode := getEnv("DB_SSLMODE", "disable") + host := getEnv("DB_HOST", "localhost") + port := getEnv("DB_PORT", "5432") + user := getEnv("DB_USER", "postgres") + password := getEnv("DB_PASSWORD", "postgres") + dbname := getEnv("DB_NAME", "bb_db") + sslmode := getEnv("DB_SSLMODE", "disable") - return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", - host, port, user, password, dbname, sslmode) + return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", + host, port, user, password, dbname, sslmode) } func getEnv(key, defaultValue string) string { - value := os.Getenv(key) - if value == "" { - return defaultValue - } - return value + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/auth.go b/serv_nginx/api_bb/internal/handlers/auth.go index 2b44dca..e3aeeda 100644 --- a/serv_nginx/api_bb/internal/handlers/auth.go +++ b/serv_nginx/api_bb/internal/handlers/auth.go @@ -1,97 +1,168 @@ +// handlers/auth.go package handlers import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" - "go-rest-api/internal/models" - "go-rest-api/internal/service" + "encoding/json" + "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 + authService service.AuthService + jwtService service.JWTService } -func NewAuthHandler(authService service.AuthService) *AuthHandler { - return &AuthHandler{authService: authService} +func NewAuthHandler(authService service.AuthService, jwtService service.JWTService) *AuthHandler { + return &AuthHandler{ + authService: authService, + jwtService: jwtService, + } } func (h *AuthHandler) Routes() chi.Router { - r := chi.NewRouter() - - r.Post("/register", h.Register) - r.Post("/login", h.Login) - r.Get("/check", h.AuthCheck) - - return r + r := chi.NewRouter() + + r.Post("/register", h.Register) + r.Post("/login", h.Login) + r.Post("/logout", h.Logout) + r.Get("/profile", h.GetProfile) + + return r } type RegisterRequest struct { - Email string `json:"email"` - Password string `json:"password"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` + Email string `json:"email"` + Password string `json:"password"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + 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"` + Email string `json:"email"` + Password string `json:"password"` +} + +type UserResponse struct { + ID uint `json:"id"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Phone string `json:"phone"` + Experience string `json:"experience"` + Goals string `json:"goals"` + Newsletter bool `json:"newsletter"` + Role string `json:"role"` } func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { - var req RegisterRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid request payload") - return - } - - user := &models.User{ - Email: req.Email, - Password: req.Password, - FirstName: req.FirstName, - LastName: req.LastName, - Role: "user", - } - - if err := h.authService.Register(user); err != nil { - respondWithError(w, http.StatusBadRequest, err.Error()) - return - } - - respondWithJSON(w, http.StatusCreated, map[string]string{ - "message": "User registered successfully", - }) + var req RegisterRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload") + 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", + } + + if err := h.authService.Register(user); err != nil { + utils.RespondWithError(w, http.StatusBadRequest, err.Error()) + return + } + + utils.RespondWithJSON(w, http.StatusCreated, map[string]string{ + "message": "User registered successfully", + }) } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { - var req LoginRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - respondWithError(w, http.StatusBadRequest, "Invalid request payload") - return - } - - user, err := h.authService.Login(req.Email, req.Password) - if err != nil { - respondWithError(w, http.StatusUnauthorized, err.Error()) - return - } - - respondWithJSON(w, http.StatusOK, map[string]interface{}{ - "message": "Login successful", - "user": map[string]interface{}{ - "id": user.ID, - "email": user.Email, - "first_name": user.FirstName, - "last_name": user.LastName, - "role": user.Role, - }, - }) + var req LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload") + 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) AuthCheck(w http.ResponseWriter, r *http.Request) { - respondWithJSON(w, http.StatusOK, map[string]string{ - "status": "ok", - "message": "Auth endpoint is working", - }) +func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { + // Удаляем куку + 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) { + 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, + } } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/common.go b/serv_nginx/api_bb/internal/handlers/common.go deleted file mode 100644 index a25fded..0000000 --- a/serv_nginx/api_bb/internal/handlers/common.go +++ /dev/null @@ -1,16 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" -) - -func respondWithJSON(w http.ResponseWriter, status int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - json.NewEncoder(w).Encode(payload) -} - -func respondWithError(w http.ResponseWriter, status int, message string) { - respondWithJSON(w, status, map[string]string{"error": message}) -} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/handlers.go b/serv_nginx/api_bb/internal/handlers/handlers.go index 50cc1ef..75055a3 100644 --- a/serv_nginx/api_bb/internal/handlers/handlers.go +++ b/serv_nginx/api_bb/internal/handlers/handlers.go @@ -1,22 +1,45 @@ +// handlers/handlers.go package handlers import ( "go-rest-api/internal/repository" "go-rest-api/internal/service" + "go-rest-api/internal/config" "gorm.io/gorm" ) type Handler struct { healthHandler *HealthHandler authHandler *AuthHandler + // Здесь будут добавлены другие обработчики + // userHandler *UserHandler + // eventHandler *EventHandler + // reviewHandler *ReviewHandler } -func NewHandler(db *gorm.DB) *Handler { +func NewHandler(db *gorm.DB, cfg *config.Config) *Handler { + // Инициализация репозиториев userRepo := repository.NewUserRepository(db) - authService := service.NewAuthService(userRepo) + + // Инициализация сервисов + jwtService := service.NewJWTService(cfg.JWTSecret) + authService := service.NewAuthService(userRepo, jwtService) + + // Инициализация обработчиков + healthHandler := NewHealthHandler() + authHandler := NewAuthHandler(authService, jwtService) return &Handler{ - healthHandler: NewHealthHandler(), - authHandler: NewAuthHandler(authService), + healthHandler: healthHandler, + authHandler: authHandler, } +} + +// Геттеры для обработчиков (опционально, для удобства) +func (h *Handler) HealthHandler() *HealthHandler { + return h.healthHandler +} + +func (h *Handler) AuthHandler() *AuthHandler { + return h.authHandler } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/health.go b/serv_nginx/api_bb/internal/handlers/health.go index 9e240a3..11b4d9e 100644 --- a/serv_nginx/api_bb/internal/handlers/health.go +++ b/serv_nginx/api_bb/internal/handlers/health.go @@ -2,6 +2,8 @@ package handlers import ( "net/http" + + "go-rest-api/pkg/utils" "github.com/go-chi/chi/v5" ) @@ -26,8 +28,7 @@ func (h *HealthHandler) HealthCheck(w http.ResponseWriter, r *http.Request) { "status": "ok", "message": "Service is healthy", } - - respondWithJSON(w, http.StatusOK, response) + utils.RespondWithJSON(w, http.StatusOK, response) } func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) { @@ -36,5 +37,5 @@ func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) { "message": "API is working", } - respondWithJSON(w, http.StatusOK, response) + utils.RespondWithJSON(w, http.StatusOK, response) } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/user.go b/serv_nginx/api_bb/internal/models/user.go index ae5dc4f..ec8b400 100644 --- a/serv_nginx/api_bb/internal/models/user.go +++ b/serv_nginx/api_bb/internal/models/user.go @@ -1,23 +1,40 @@ +// models/user.go package models import ( - "time" - "gorm.io/gorm" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" + "time" ) type User struct { - gorm.Model - ID uint `gorm:"primaryKey" json:"id"` - Email string `gorm:"uniqueIndex;not null" json:"email"` - Password string `gorm:"not null" json:"-"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Role string `gorm:"default:user" json:"role"` // user, admin - IsActive bool `gorm:"default:true" json:"is_active"` - LastLogin time.Time `json:"last_login"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `json:"id" gorm:"primaryKey"` + Email string `json:"email" gorm:"uniqueIndex;not null"` + Password string `json:"-" gorm:"not null"` + FirstName string `json:"first_name" gorm:"not null"` + LastName string `json:"last_name" gorm:"not null"` + Phone string `json:"phone"` + Experience string `json:"experience"` + Goals string `json:"goals"` + Newsletter bool `json:"newsletter"` + Role string `json:"role" gorm:"default:user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } -// Другие модели будут добавлены позже -// type Profile {}, type Event {}, type Review {}, etc. \ No newline at end of file +// HashPassword хеширует пароль перед сохранением +func (u *User) HashPassword() error { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + u.Password = string(hashedPassword) + return nil +} + +// CheckPassword проверяет пароль +func (u *User) CheckPassword(password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) + return err == nil +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/routes/routes.go b/serv_nginx/api_bb/internal/routes/routes.go index b2629fb..c6e8f7f 100644 --- a/serv_nginx/api_bb/internal/routes/routes.go +++ b/serv_nginx/api_bb/internal/routes/routes.go @@ -1,3 +1,4 @@ +// routes/routes.go package routes import ( @@ -6,13 +7,14 @@ import ( "github.com/go-chi/chi/v5" "gorm.io/gorm" + "go-rest-api/internal/config" "go-rest-api/internal/handlers" "go-rest-api/internal/repository" "go-rest-api/internal/service" "go-rest-api/pkg/middleware" ) -func SetupRouter(db *gorm.DB) http.Handler { +func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r := chi.NewRouter() // Apply common middleware @@ -24,28 +26,36 @@ func SetupRouter(db *gorm.DB) http.Handler { userRepo := repository.NewUserRepository(db) // Initialize services - authService := service.NewAuthService(userRepo) + jwtService := service.NewJWTService(config.JWTSecret) + authService := service.NewAuthService(userRepo, jwtService) // Initialize handlers healthHandler := handlers.NewHealthHandler() - authHandler := handlers.NewAuthHandler(authService) - - h := handlers.NewHealthHandler() + authHandler := handlers.NewAuthHandler(authService, jwtService) // Health routes r.Mount("/", healthHandler.Routes()) // API v1 routes r.Route("/v1", func(r chi.Router) { - // Add the new /check route - r.Get("/check", h.Check) + r.Get("/check", healthHandler.Check) + + // Public auth routes r.Mount("/auth", authHandler.Routes()) - + + // Protected routes + r.Route("/user", func(r chi.Router) { + r.Use(middleware.AuthMiddleware(jwtService, userRepo)) + r.Use(middleware.RequireAuth) + + r.Get("/profile", authHandler.GetProfile) + // Здесь будут другие защищенные маршруты пользователя + }) + // Здесь будут добавлены другие маршруты: - // r.Mount("/users", userHandler.Routes()) // r.Mount("/events", eventHandler.Routes()) // r.Mount("/reviews", reviewHandler.Routes()) }) return r -} +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/service/auth_service.go b/serv_nginx/api_bb/internal/service/auth_service.go index 60a5fca..8771b98 100644 --- a/serv_nginx/api_bb/internal/service/auth_service.go +++ b/serv_nginx/api_bb/internal/service/auth_service.go @@ -1,52 +1,64 @@ +// service/auth_service.go package service import ( - "errors" - "go-rest-api/internal/models" - "go-rest-api/internal/repository" + "errors" + + "go-rest-api/internal/models" + "go-rest-api/internal/repository" ) type AuthService interface { - Register(user *models.User) error - Login(email, password string) (*models.User, error) + Register(user *models.User) error + Login(email, password string) (*models.User, string, error) + GetUserProfile(userID uint) (*models.User, error) } type authService struct { - userRepo repository.UserRepository + userRepo repository.UserRepository + jwtService JWTService } -func NewAuthService(userRepo repository.UserRepository) AuthService { - return &authService{userRepo: userRepo} +func NewAuthService(userRepo repository.UserRepository, jwtService JWTService) AuthService { + return &authService{ + userRepo: userRepo, + jwtService: jwtService, + } } func (s *authService) Register(user *models.User) error { - // Проверка существования пользователя - existingUser, _ := s.userRepo.FindByEmail(user.Email) - if existingUser != nil { - return errors.New("user already exists") - } - - // Здесь должна быть хеширование пароля - // user.Password = hashPassword(user.Password) - - return s.userRepo.Create(user) + // Проверяем, существует ли пользователь + existingUser, err := s.userRepo.FindByEmail(user.Email) + if err == nil && existingUser != nil { + return errors.New("user with this email already exists") + } + + // Хешируем пароль + if err := user.HashPassword(); err != nil { + return err + } + + return s.userRepo.Create(user) } -func (s *authService) Login(email, password string) (*models.User, error) { - user, err := s.userRepo.FindByEmail(email) - if err != nil { - return nil, errors.New("invalid credentials") - } - - // Здесь должна быть проверка хеша пароля - // if !checkPasswordHash(password, user.Password) { - // return nil, errors.New("invalid credentials") - // } - - // Временно просто проверяем напрямую (для демо) - if user.Password != password { - return nil, errors.New("invalid credentials") - } - - return user, nil +func (s *authService) Login(email, password string) (*models.User, string, error) { + user, err := s.userRepo.FindByEmail(email) + if err != nil { + return nil, "", errors.New("invalid email or password") + } + + if !user.CheckPassword(password) { + return nil, "", errors.New("invalid email or password") + } + + token, err := s.jwtService.GenerateToken(user.ID, user.Email) + if err != nil { + return nil, "", err + } + + return user, token, nil +} + +func (s *authService) GetUserProfile(userID uint) (*models.User, error) { + return s.userRepo.FindByID(userID) } \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/service/jwt_service.go b/serv_nginx/api_bb/internal/service/jwt_service.go new file mode 100644 index 0000000..ffb242e --- /dev/null +++ b/serv_nginx/api_bb/internal/service/jwt_service.go @@ -0,0 +1,61 @@ +// service/jwt_service.go +package service + +import ( + "errors" + "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +type JWTService interface { + GenerateToken(userID uint, email string) (string, error) + ValidateToken(tokenString string) (*jwt.Token, error) + ExtractUserID(token *jwt.Token) (uint, error) +} + +type jwtService struct { + secretKey string +} + +func NewJWTService(secretKey string) JWTService { + return &jwtService{secretKey: secretKey} +} + +type Claims struct { + UserID uint `json:"user_id"` + Email string `json:"email"` + jwt.RegisteredClaims +} + +func (j *jwtService) GenerateToken(userID uint, email string) (string, error) { + claims := &Claims{ + UserID: userID, + Email: email, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(j.secretKey)) +} + +func (j *jwtService) ValidateToken(tokenString string) (*jwt.Token, error) { + return jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(j.secretKey), nil + }) +} + +func (j *jwtService) ExtractUserID(token *jwt.Token) (uint, error) { + claims, ok := token.Claims.(*Claims) + if !ok { + return 0, errors.New("invalid token claims") + } + return claims.UserID, nil +} \ No newline at end of file diff --git a/serv_nginx/api_bb/pkg/middleware/auth.go b/serv_nginx/api_bb/pkg/middleware/auth.go new file mode 100644 index 0000000..14af195 --- /dev/null +++ b/serv_nginx/api_bb/pkg/middleware/auth.go @@ -0,0 +1,88 @@ +// middleware/auth.go +package middleware + +import ( + "context" + "net/http" + "strings" + + "go-rest-api/internal/models" + "go-rest-api/internal/repository" + "go-rest-api/internal/service" +) + +type contextKey string + +const ( + UserIDKey contextKey = "userID" + UserKey contextKey = "user" +) + +func AuthMiddleware(jwtService service.JWTService, userRepo repository.UserRepository) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var tokenString string + + // Пробуем получить токен из заголовка Authorization + authHeader := r.Header.Get("Authorization") + if strings.HasPrefix(authHeader, "Bearer ") { + tokenString = strings.TrimPrefix(authHeader, "Bearer ") + } + + // Если нет в заголовке, пробуем из куки + if tokenString == "" { + cookie, err := r.Cookie("auth_token") + if err == nil { + tokenString = cookie.Value + } + } + + if tokenString == "" { + next.ServeHTTP(w, r) + return + } + + token, err := jwtService.ValidateToken(tokenString) + if err != nil || !token.Valid { + next.ServeHTTP(w, r) + return + } + + userID, err := jwtService.ExtractUserID(token) + if err != nil { + next.ServeHTTP(w, r) + return + } + + user, err := userRepo.FindByID(userID) + if err != nil { + next.ServeHTTP(w, r) + return + } + + // Добавляем пользователя в контекст + ctx := context.WithValue(r.Context(), UserIDKey, userID) + ctx = context.WithValue(ctx, UserKey, user) + + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +// RequireAuth middleware требует аутентификации +func RequireAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value(UserIDKey) + if userID == nil { + http.Error(w, `{"error": "Authentication required"}`, http.StatusUnauthorized) + return + } + next.ServeHTTP(w, r) + }) +} + +// GetUserFromContext получает пользователя из контекста +func GetUserFromContext(ctx context.Context) (*models.User, bool) { + user, ok := ctx.Value(UserKey).(*models.User) + return user, ok +} \ No newline at end of file diff --git a/serv_nginx/api_bb/pkg/middleware/middleware.go b/serv_nginx/api_bb/pkg/middleware/middleware.go index 0b8b8d4..2783d9a 100644 --- a/serv_nginx/api_bb/pkg/middleware/middleware.go +++ b/serv_nginx/api_bb/pkg/middleware/middleware.go @@ -20,5 +20,5 @@ func CommonMiddleware() []func(http.Handler) http.Handler { AllowCredentials: false, MaxAge: 300, }), - } + } } \ No newline at end of file diff --git a/serv_nginx/api_bb/pkg/utils/utils.go b/serv_nginx/api_bb/pkg/utils/utils.go new file mode 100644 index 0000000..255e810 --- /dev/null +++ b/serv_nginx/api_bb/pkg/utils/utils.go @@ -0,0 +1,16 @@ +package utils + +import ( + "encoding/json" + "net/http" +) + +func RespondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(data) +} + +func RespondWithError(w http.ResponseWriter, statusCode int, message string) { + RespondWithJSON(w, statusCode, map[string]string{"error": message}) +} \ No newline at end of file diff --git a/serv_nginx/docker-compose.yml b/serv_nginx/docker-compose.yml index 5fd7650..43f8ecd 100644 --- a/serv_nginx/docker-compose.yml +++ b/serv_nginx/docker-compose.yml @@ -101,6 +101,8 @@ services: depends_on: db_bb: condition: service_healthy + env_file: + - ./api_bb/.env environment: # Database connection settings DB_HOST: db_bb