package handler import ( "encoding/json" "errors" "net/http" "github.com/daniil/homelab-api/internal/middleware" "github.com/daniil/homelab-api/internal/model" "github.com/daniil/homelab-api/internal/repository" "github.com/daniil/homelab-api/internal/service" ) type AuthHandler struct { authService *service.AuthService } func NewAuthHandler(authService *service.AuthService) *AuthHandler { return &AuthHandler{authService: authService} } func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { var req model.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Email == "" || req.Username == "" || req.Password == "" { writeError(w, "email, username and password are required", http.StatusBadRequest) return } resp, err := h.authService.Register(&req) if err != nil { if errors.Is(err, repository.ErrUserExists) { writeError(w, "user with this email already exists", http.StatusConflict) return } if errors.Is(err, service.ErrWeakPassword) { writeError(w, err.Error(), http.StatusBadRequest) return } writeError(w, "internal error", http.StatusInternalServerError) return } writeJSON(w, resp, http.StatusCreated) } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { var req model.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Email == "" || req.Password == "" { writeError(w, "email and password are required", http.StatusBadRequest) return } resp, err := h.authService.Login(&req) if err != nil { if errors.Is(err, service.ErrInvalidCredentials) { writeError(w, "invalid email or password", http.StatusUnauthorized) return } if errors.Is(err, service.ErrEmailNotVerified) { writeError(w, "please verify your email first", http.StatusForbidden) return } writeError(w, "internal error", http.StatusInternalServerError) return } writeJSON(w, resp, http.StatusOK) } func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) { var req model.RefreshRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.RefreshToken == "" { writeError(w, "refresh_token is required", http.StatusBadRequest) return } resp, err := h.authService.Refresh(req.RefreshToken) if err != nil { writeError(w, "invalid refresh token", http.StatusUnauthorized) return } writeJSON(w, resp, http.StatusOK) } func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) user, err := h.authService.GetUser(userID) if err != nil { writeError(w, "user not found", http.StatusNotFound) return } writeJSON(w, user, http.StatusOK) } func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) var req model.UpdateProfileRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } user, err := h.authService.UpdateProfile(userID, &req) if err != nil { writeError(w, "failed to update profile", http.StatusInternalServerError) return } writeJSON(w, user, http.StatusOK) } func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) var req model.ChangePasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.OldPassword == "" || req.NewPassword == "" { writeError(w, "old_password and new_password are required", http.StatusBadRequest) return } err := h.authService.ChangePassword(userID, &req) if err != nil { if errors.Is(err, service.ErrInvalidCredentials) { writeError(w, "invalid old password", http.StatusUnauthorized) return } if errors.Is(err, service.ErrWeakPassword) { writeError(w, err.Error(), http.StatusBadRequest) return } writeError(w, "failed to change password", http.StatusInternalServerError) return } writeJSON(w, map[string]string{"message": "password changed successfully"}, http.StatusOK) } // Email verification func (h *AuthHandler) VerifyEmail(w http.ResponseWriter, r *http.Request) { var req model.VerifyEmailRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Token == "" { writeError(w, "token is required", http.StatusBadRequest) return } err := h.authService.VerifyEmail(&req) if err != nil { if errors.Is(err, repository.ErrTokenNotFound) { writeError(w, "invalid token", http.StatusBadRequest) return } if errors.Is(err, repository.ErrTokenExpired) { writeError(w, "token expired", http.StatusBadRequest) return } if errors.Is(err, repository.ErrTokenUsed) { writeError(w, "token already used", http.StatusBadRequest) return } writeError(w, "failed to verify email", http.StatusInternalServerError) return } writeJSON(w, map[string]string{"message": "email verified successfully"}, http.StatusOK) } func (h *AuthHandler) ResendVerification(w http.ResponseWriter, r *http.Request) { var req model.ResendVerificationRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Email == "" { writeError(w, "email is required", http.StatusBadRequest) return } // Always return success to not reveal if user exists _ = h.authService.ResendVerification(&req) writeJSON(w, map[string]string{"message": "if the email exists, a verification link has been sent"}, http.StatusOK) } // Password reset func (h *AuthHandler) ForgotPassword(w http.ResponseWriter, r *http.Request) { var req model.ForgotPasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Email == "" { writeError(w, "email is required", http.StatusBadRequest) return } // Always return success to not reveal if user exists _ = h.authService.ForgotPassword(&req) writeJSON(w, map[string]string{"message": "if the email exists, a password reset link has been sent"}, http.StatusOK) } func (h *AuthHandler) ResetPassword(w http.ResponseWriter, r *http.Request) { var req model.ResetPasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Token == "" || req.NewPassword == "" { writeError(w, "token and new_password are required", http.StatusBadRequest) return } err := h.authService.ResetPassword(&req) if err != nil { if errors.Is(err, repository.ErrTokenNotFound) { writeError(w, "invalid token", http.StatusBadRequest) return } if errors.Is(err, repository.ErrTokenExpired) { writeError(w, "token expired", http.StatusBadRequest) return } if errors.Is(err, repository.ErrTokenUsed) { writeError(w, "token already used", http.StatusBadRequest) return } if errors.Is(err, service.ErrWeakPassword) { writeError(w, err.Error(), http.StatusBadRequest) return } writeError(w, "failed to reset password", http.StatusInternalServerError) return } writeJSON(w, map[string]string{"message": "password reset successfully"}, http.StatusOK) } func writeJSON(w http.ResponseWriter, data interface{}, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } func writeError(w http.ResponseWriter, message string, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(map[string]string{"error": message}) }