v1.4: Segurança multi-tenant, file serving via API e UX humanizada
- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
This commit is contained in:
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -59,13 +60,40 @@ func Auth(cfg *config.Config) func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// tenant_id pode ser nil para SuperAdmin
|
||||
var tenantID string
|
||||
var tenantIDFromJWT string
|
||||
if tenantIDClaim, ok := claims["tenant_id"]; ok && tenantIDClaim != nil {
|
||||
tenantID, _ = tenantIDClaim.(string)
|
||||
tenantIDFromJWT, _ = tenantIDClaim.(string)
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), UserIDKey, userID)
|
||||
ctx = context.WithValue(ctx, TenantIDKey, tenantID)
|
||||
// VALIDAÇÃO DE SEGURANÇA: Verificar se o tenant_id do JWT corresponde ao subdomínio acessado
|
||||
// Pegar o tenant_id do contexto (detectado pelo TenantDetector middleware ANTES deste)
|
||||
tenantIDFromContext := ""
|
||||
if ctxTenantID := r.Context().Value(TenantIDKey); ctxTenantID != nil {
|
||||
tenantIDFromContext, _ = ctxTenantID.(string)
|
||||
}
|
||||
|
||||
log.Printf("🔐 AUTH VALIDATION: JWT tenant=%s | Context tenant=%s | Path=%s",
|
||||
tenantIDFromJWT, tenantIDFromContext, r.RequestURI)
|
||||
|
||||
// Se o usuário não é SuperAdmin (tem tenant_id) e está acessando uma agência (subdomain detectado)
|
||||
if tenantIDFromJWT != "" && tenantIDFromContext != "" {
|
||||
// Validar se o tenant_id do JWT corresponde ao tenant detectado
|
||||
if tenantIDFromJWT != tenantIDFromContext {
|
||||
log.Printf("❌ CROSS-TENANT ACCESS BLOCKED: User from tenant %s tried to access tenant %s",
|
||||
tenantIDFromJWT, tenantIDFromContext)
|
||||
http.Error(w, "Forbidden: You don't have access to this tenant", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
log.Printf("✅ TENANT VALIDATION PASSED: %s", tenantIDFromJWT)
|
||||
}
|
||||
|
||||
// Preservar TODOS os valores do contexto anterior (incluindo o tenantID do TenantDetector)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserIDKey, userID)
|
||||
// Só sobrescrever o TenantIDKey se vier do JWT (para não perder o do TenantDetector)
|
||||
if tenantIDFromJWT != "" {
|
||||
ctx = context.WithValue(ctx, TenantIDKey, tenantIDFromJWT)
|
||||
}
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user