From 9e80aa1d7039dd6191c0135130dac877116eff41 Mon Sep 17 00:00:00 2001 From: Erik Silva Date: Tue, 9 Dec 2025 03:04:28 -0300 Subject: [PATCH] feat: block unknown subdomains via tenant check --- backend/cmd/server/main.go | 1 + backend/internal/api/handlers/tenant.go | 28 ++++++++++++++++++++++ backend/internal/service/tenant_service.go | 5 ++++ front-end-dash.aggios.app/middleware.ts | 18 +++++++++++++- 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index df04750..6c981d6 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -92,6 +92,7 @@ func main() { mux.HandleFunc("/api/admin/agencies/register", agencyHandler.RegisterAgency) mux.HandleFunc("/api/admin/agencies", tenantHandler.ListAll) mux.HandleFunc("/api/admin/agencies/", agencyHandler.HandleAgency) + mux.HandleFunc("/api/tenant/check", tenantHandler.CheckExists) // Client registration (ADMIN_AGENCIA only - requires auth) mux.Handle("/api/agencies/clients/register", authMiddleware(http.HandlerFunc(agencyHandler.RegisterClient))) diff --git a/backend/internal/api/handlers/tenant.go b/backend/internal/api/handlers/tenant.go index 2c60c56..45327ed 100644 --- a/backend/internal/api/handlers/tenant.go +++ b/backend/internal/api/handlers/tenant.go @@ -40,3 +40,31 @@ func (h *TenantHandler) ListAll(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(tenants) } + +// CheckExists returns 200 if tenant exists by subdomain, otherwise 404 +func (h *TenantHandler) CheckExists(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + subdomain := r.URL.Query().Get("subdomain") + if subdomain == "" { + http.Error(w, "subdomain is required", http.StatusBadRequest) + return + } + + tenant, err := h.tenantService.GetBySubdomain(subdomain) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + if tenant == nil { + http.NotFound(w, r) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) +} diff --git a/backend/internal/service/tenant_service.go b/backend/internal/service/tenant_service.go index b3d971e..f3a4d02 100644 --- a/backend/internal/service/tenant_service.go +++ b/backend/internal/service/tenant_service.go @@ -79,6 +79,11 @@ func (s *TenantService) ListAll() ([]*domain.Tenant, error) { return s.tenantRepo.FindAll() } +// GetBySubdomain returns tenant by subdomain or nil +func (s *TenantService) GetBySubdomain(subdomain string) (*domain.Tenant, error) { + return s.tenantRepo.FindBySubdomain(subdomain) +} + // Delete removes a tenant by ID func (s *TenantService) Delete(id uuid.UUID) error { if err := s.tenantRepo.Delete(id); err != nil { diff --git a/front-end-dash.aggios.app/middleware.ts b/front-end-dash.aggios.app/middleware.ts index e636880..2fd70b6 100644 --- a/front-end-dash.aggios.app/middleware.ts +++ b/front-end-dash.aggios.app/middleware.ts @@ -14,7 +14,23 @@ export function middleware(request: NextRequest) { return NextResponse.next(); } - // Se for agência ({subdomain}.localhost) - rotas de tenant + // Se for agência ({subdomain}.localhost) - validar se existe + if (hostname.includes('.')) { + try { + const res = await fetch(`http://backend:8080/api/tenant/check?subdomain=${subdomain}`); + if (res.status === 404) { + // Redireciona para o host base (sem subdomínio) + const baseHost = hostname.split('.').slice(1).join('.') || hostname; + const redirectUrl = new URL(url.toString()); + redirectUrl.hostname = baseHost; + redirectUrl.pathname = '/'; + return NextResponse.redirect(redirectUrl); + } + } catch (err) { + // Em caso de erro de rede, não bloquear + } + } + // Permitir /dashboard, /login, /clientes, etc. return NextResponse.next(); }