diff --git a/handlers/user.go b/handlers/user.go index 615c8c0..04747e8 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -10,10 +10,11 @@ import ( type UserHandler struct { userService *services.UserService + authService *services.AuthService } -func NewUserHandler(us *services.UserService) *UserHandler { - return &UserHandler{userService: us} +func NewUserHandler(us *services.UserService, as *services.AuthService) *UserHandler { + return &UserHandler{userService: us, authService: as} } func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) { @@ -60,3 +61,39 @@ func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(user) } + +func (h *UserHandler) Login(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + var req models.LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "invalid request body", http.StatusBadRequest) + return + } + + req.Email = strings.ToLower(strings.TrimSpace(req.Email)) + user, err := h.userService.GetUserByEmail(req.Email) + + if err != nil || user == nil { + http.Error(w, "invalid credentials", http.StatusUnauthorized) + return + } + + if !h.userService.ValidatePassword(user, req.Password) { + http.Error(w, "invalid credentials", http.StatusUnauthorized) + return + } + + token, err := h.authService.GenerateToken(user.ID, user.Email) + if err != nil { + http.Error(w, "failed to generate token", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(models.LoginResponse{User: *user, Token: token}) + +} diff --git a/main.go b/main.go index ea95ff5..ba6019b 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "fmt" "go-weather/config" "go-weather/handlers" + "go-weather/middleware" "go-weather/services" "log" "net/http" @@ -64,16 +65,21 @@ func main() { databaseService := services.NewDatabaseService(database) weatherService := services.NewWeatherService(config.APIKey) userService := services.NewUserService(database) + authService := services.NewAuthService(config.JWTSecret) // Create route handlers weatherHandler := handlers.NewWeatherHandler(weatherService, databaseService, config.DefaultCity) - userHandler := handlers.NewUserHandler(userService) + userHandler := handlers.NewUserHandler(userService, authService) // Start background poller go startPoller(database, weatherService, config.DefaultCity, 10*time.Minute) // Register routes - http.HandleFunc("/weather/fetch", weatherHandler.FetchWeather) + http.HandleFunc("/login", userHandler.Login) + + http.Handle("/weather/fetch", middleware.AuthMiddleware(authService)( + http.HandlerFunc(weatherHandler.FetchWeather)), + ) http.HandleFunc("/weather/history", weatherHandler.GetHistory) http.HandleFunc("/health", handlers.Health) http.HandleFunc("/latest-temperature", handlers.LatestTemperature) diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..0374907 --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "context" + "go-weather/services" + "net/http" + "strings" +) + +type contextKey string + +const ClaimsKey contextKey = "claims" + +func AuthMiddleware(authService *services.AuthService) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + tokenString := strings.TrimPrefix(authHeader, "Bearer ") + claims, err := authService.ValidateToken(tokenString) + if err != nil { + http.Error(w, "invalid token", http.StatusUnauthorized) + return + } + + ctx := context.WithValue(r.Context(), ClaimsKey, claims) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} + +func GetClaims(r *http.Request) *services.Claims { + if claims, ok := r.Context().Value(ClaimsKey).(*services.Claims); ok { + return claims + } + return nil +} diff --git a/services/auth.go b/services/auth.go index 6e5289d..d767cd8 100644 --- a/services/auth.go +++ b/services/auth.go @@ -36,7 +36,7 @@ func (s *AuthService) GenerateToken(userID int, email string) (string, error) { } func (s *AuthService) ValidateToken(tokenString string) (*Claims, error) { - token, err := jwt.ParseWithClaims(tokenString, &Claims, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") }