Files
boc/backend/middleware/tenant.go
T

104 lines
2.6 KiB
Go
Raw Normal View History

package middleware
import (
"context"
"net/http"
"strings"
)
// TenantContext key for storing tenant ID
type TenantContextKey struct{}
// TenantConfig holds tenant configuration
type TenantConfig struct {
ID string
Name string
Slug string
Domain string
IsActive bool
}
// MultiTenancy middleware handles tenant identification and isolation
func MultiTenancy(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract tenant from multiple sources (in priority order)
tenantID := extractTenantID(r)
if tenantID == "" {
http.Error(w, `{"error":"tenant not identified"}`, http.StatusBadRequest)
return
}
// Add tenant to context
ctx := context.WithValue(r.Context(), TenantContextKey{}, tenantID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// extractTenantID tries multiple methods to identify tenant
func extractTenantID(r *http.Request) string {
// 1. Header (for API clients)
if tenantID := r.Header.Get("X-Tenant-ID"); tenantID != "" {
return tenantID
}
// 2. Subdomain (e.g., landvex.boc.aamos.systems)
host := r.Host
if idx := strings.Index(host, "."); idx > 0 {
subdomain := host[:idx]
if subdomain != "www" && subdomain != "boc" {
// Map subdomain to tenant ID
return resolveSubdomain(subdomain)
}
}
// 3. Query parameter (for testing/debugging)
if tenantID := r.URL.Query().Get("tenant"); tenantID != "" {
return tenantID
}
// 4. JWT token claim (if authenticated)
// This would be handled by auth middleware
// 5. Default tenant (for backward compatibility)
return "default"
}
// resolveSubdomain maps subdomain to tenant ID
func resolveSubdomain(subdomain string) string {
// In production, this would query the database
// For now, use a simple mapping
subdomainMap := map[string]string{
"landvex": "11111111-1111-1111-1111-111111111111",
"landvex-ab": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"quixzoom": "quixzoom-tenant-id",
"aamos": "aamos-tenant-id",
}
if id, ok := subdomainMap[subdomain]; ok {
return id
}
return ""
}
// GetTenantID retrieves tenant ID from context
func GetTenantID(ctx context.Context) string {
if tenantID, ok := ctx.Value(TenantContextKey{}).(string); ok {
return tenantID
}
return ""
}
// TenantIsolation ensures all database queries are scoped to tenant
func TenantIsolation(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tenantID := GetTenantID(r.Context())
if tenantID == "" {
http.Error(w, `{"error":"tenant isolation required"}`, http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}