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