package api import ( "encoding/json" "net/http" "dance-lessons-coach/pkg/user" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" ) // AuthHandler handles authentication-related HTTP requests type AuthHandler struct { authService user.AuthService userRepo user.UserRepository passwordResetService user.PasswordResetService } // NewAuthHandler creates a new authentication handler func NewAuthHandler(authService user.AuthService, userRepo user.UserRepository) *AuthHandler { passwordResetService := user.NewPasswordResetService(userRepo, authService.(*user.AuthServiceImpl)) return &AuthHandler{ authService: authService, userRepo: userRepo, passwordResetService: passwordResetService, } } // RegisterRoutes registers authentication routes func (h *AuthHandler) RegisterRoutes(router chi.Router) { router.Post("/login", h.handleLogin) router.Post("/admin/login", h.handleAdminLogin) router.Post("/register", h.handleRegister) router.Post("/password-reset/request", h.handlePasswordResetRequest) router.Post("/password-reset/complete", h.handlePasswordResetComplete) } // LoginRequest represents a login request type LoginRequest struct { Username string `json:"username" validate:"required,min=3,max=50"` Password string `json:"password" validate:"required,min=6"` } // LoginResponse represents a login response type LoginResponse struct { Token string `json:"token"` } // handleLogin handles user login requests func (h *AuthHandler) handleLogin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Authenticate user user, err := h.authService.Authenticate(ctx, req.Username, req.Password) if err != nil { log.Trace().Ctx(ctx).Err(err).Str("username", req.Username).Msg("Authentication failed") http.Error(w, `{"error":"invalid_credentials","message":"Invalid username or password"}`, http.StatusUnauthorized) return } // Generate JWT token token, err := h.authService.GenerateJWT(ctx, user) if err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to generate JWT token") http.Error(w, `{"error":"server_error","message":"Failed to generate authentication token"}`, http.StatusInternalServerError) return } // Return token w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(LoginResponse{Token: token}) } // handleAdminLogin handles admin login requests func (h *AuthHandler) handleAdminLogin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Authenticate admin adminUser, err := h.authService.AdminAuthenticate(ctx, req.Password) if err != nil { log.Trace().Ctx(ctx).Err(err).Msg("Admin authentication failed") http.Error(w, `{"error":"invalid_credentials","message":"Invalid admin credentials"}`, http.StatusUnauthorized) return } // Generate JWT token token, err := h.authService.GenerateJWT(ctx, adminUser) if err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to generate JWT token for admin") http.Error(w, `{"error":"server_error","message":"Failed to generate authentication token"}`, http.StatusInternalServerError) return } // Return token w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(LoginResponse{Token: token}) } // RegisterRequest represents a user registration request type RegisterRequest struct { Username string `json:"username" validate:"required,min=3,max=50"` Password string `json:"password" validate:"required,min=6"` } // handleRegister handles user registration requests func (h *AuthHandler) handleRegister(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Check if user already exists exists, err := h.userRepo.UserExists(ctx, req.Username) if err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to check if user exists") http.Error(w, `{"error":"server_error","message":"Failed to process registration"}`, http.StatusInternalServerError) return } if exists { http.Error(w, `{"error":"user_exists","message":"Username already taken"}`, http.StatusConflict) return } // Hash password hashedPassword, err := h.authService.HashPassword(ctx, req.Password) if err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to hash password") http.Error(w, `{"error":"server_error","message":"Failed to process registration"}`, http.StatusInternalServerError) return } // Create user newUser := &user.User{ Username: req.Username, PasswordHash: hashedPassword, IsAdmin: false, } if err := h.userRepo.CreateUser(ctx, newUser); err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to create user") http.Error(w, `{"error":"server_error","message":"Failed to create user"}`, http.StatusInternalServerError) return } // Return success w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"message": "User registered successfully"}) } // PasswordResetRequest represents a password reset request type PasswordResetRequest struct { Username string `json:"username" validate:"required,min=3,max=50"` } // handlePasswordResetRequest handles password reset requests func (h *AuthHandler) handlePasswordResetRequest(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req PasswordResetRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Request password reset if err := h.passwordResetService.RequestPasswordReset(ctx, req.Username); err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to request password reset") http.Error(w, `{"error":"server_error","message":"Failed to process password reset request"}`, http.StatusInternalServerError) return } // Return success w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Password reset allowed, user can now reset password"}) } // PasswordResetCompleteRequest represents a password reset completion request type PasswordResetCompleteRequest struct { Username string `json:"username" validate:"required,min=3,max=50"` NewPassword string `json:"new_password" validate:"required,min=6"` } // handlePasswordResetComplete handles password reset completion requests func (h *AuthHandler) handlePasswordResetComplete(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var req PasswordResetCompleteRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest) return } // Complete password reset if err := h.passwordResetService.CompletePasswordReset(ctx, req.Username, req.NewPassword); err != nil { log.Error().Ctx(ctx).Err(err).Msg("Failed to complete password reset") http.Error(w, `{"error":"server_error","message":"Failed to complete password reset"}`, http.StatusInternalServerError) return } // Return success w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Password reset completed successfully"}) }