feat: block unknown subdomains via tenant check

This commit is contained in:
Erik Silva
2025-12-09 03:04:28 -03:00
parent 74857bf106
commit 9e80aa1d70
4 changed files with 51 additions and 1 deletions

View File

@@ -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)))

View File

@@ -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"})
}

View File

@@ -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 {

View File

@@ -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();
}