package handlers import ( "encoding/json" "io" "log" "net/http" "strings" "aggios-app/backend/internal/api/middleware" "aggios-app/backend/internal/domain" "aggios-app/backend/internal/service" ) // AuthHandler handles authentication endpoints type AuthHandler struct { authService *service.AuthService } // NewAuthHandler creates a new auth handler func NewAuthHandler(authService *service.AuthService) *AuthHandler { return &AuthHandler{ authService: authService, } } // Register handles user registration func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req domain.CreateUserRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } user, err := h.authService.Register(req) if err != nil { switch err { case service.ErrEmailAlreadyExists: http.Error(w, err.Error(), http.StatusConflict) case service.ErrWeakPassword: http.Error(w, err.Error(), http.StatusBadRequest) default: http.Error(w, "Internal server error", http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(user) } // Login handles user login func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { log.Printf("🔐 LOGIN HANDLER CALLED - Method: %s", r.Method) if r.Method != http.MethodPost { log.Printf("❌ Method not allowed: %s", r.Method) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } bodyBytes, err := io.ReadAll(r.Body) if err != nil { log.Printf("❌ Failed to read body: %v", err) http.Error(w, "Failed to read request body", http.StatusBadRequest) return } defer r.Body.Close() log.Printf("📥 Raw body: %s", string(bodyBytes)) // Trim whitespace to avoid decode errors caused by BOM or stray chars sanitized := strings.TrimSpace(string(bodyBytes)) var req domain.LoginRequest if err := json.Unmarshal([]byte(sanitized), &req); err != nil { log.Printf("❌ JSON parse error: %v", err) http.Error(w, "Invalid request body", http.StatusBadRequest) return } log.Printf("📧 Login attempt for email: %s", req.Email) response, err := h.authService.Login(req) if err != nil { log.Printf("❌ authService.Login error: %v", err) if err == service.ErrInvalidCredentials { http.Error(w, err.Error(), http.StatusUnauthorized) } else { http.Error(w, "Internal server error", http.StatusInternalServerError) } return } // VALIDAÇÃO DE SEGURANÇA: Verificar se o tenant do usuário corresponde ao subdomain acessado tenantIDFromContext := "" if ctxTenantID := r.Context().Value(middleware.TenantIDKey); ctxTenantID != nil { tenantIDFromContext, _ = ctxTenantID.(string) } // Se foi detectado um tenant no contexto (não é superadmin ou site institucional) if tenantIDFromContext != "" && response.User.TenantID != nil { userTenantID := response.User.TenantID.String() if userTenantID != tenantIDFromContext { log.Printf("❌ LOGIN BLOCKED: User from tenant %s tried to login in tenant %s subdomain", userTenantID, tenantIDFromContext) http.Error(w, "Forbidden: Invalid credentials for this tenant", http.StatusForbidden) return } log.Printf("✅ TENANT LOGIN VALIDATION PASSED: %s", userTenantID) } log.Printf("✅ Login successful for %s, role=%s", response.User.Email, response.User.Role) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } // ChangePasswordRequest represents a password change request type ChangePasswordRequest struct { CurrentPassword string `json:"currentPassword"` NewPassword string `json:"newPassword"` } // ChangePassword handles password change func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Get user ID from context (set by auth middleware) userID, ok := r.Context().Value("userID").(string) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var req ChangePasswordRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if req.CurrentPassword == "" || req.NewPassword == "" { http.Error(w, "Current password and new password are required", http.StatusBadRequest) return } // Call auth service to change password if err := h.authService.ChangePassword(userID, req.CurrentPassword, req.NewPassword); err != nil { if err == service.ErrInvalidCredentials { http.Error(w, "Current password is incorrect", http.StatusUnauthorized) } else if err == service.ErrWeakPassword { http.Error(w, "New password is too weak", http.StatusBadRequest) } else { http.Error(w, "Error changing password", http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": "Password changed successfully", }) }