From 39b7c689f2c78cb6874df58ce5149b77a6e8b6e0 Mon Sep 17 00:00:00 2001 From: Duc Nguyen Date: Tue, 24 Mar 2026 11:28:53 +0700 Subject: [PATCH] fix(http): propagate cross-tenant context in HTTP auth middleware (#427) The requireAuth and requireAuthBearer middlewares correctly detect cross-tenant admin status (gateway token + owner user ID) but never call store.WithCrossTenant(ctx). This causes all HTTP tenant management endpoints (GET/POST /v1/tenants, etc.) to return 403 "insufficient role" because handlers check store.IsCrossTenant(ctx) which was always false. The WebSocket path works correctly because the gateway Client object tracks cross-tenant status independently via client.IsCrossTenant(). Fix: Add store.WithCrossTenant(ctx) in both auth functions when auth.CrossTenant is true, matching the design of the WS path. Affected endpoints: - GET/POST /v1/tenants (list, create) - GET/PATCH /v1/tenants/{id} (get, update) - GET/POST/DELETE /v1/tenants/{id}/users (membership) - Any future endpoint checking store.IsCrossTenant(ctx) --- internal/http/auth.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/http/auth.go b/internal/http/auth.go index c3d284e8..07bacb40 100644 --- a/internal/http/auth.go +++ b/internal/http/auth.go @@ -289,14 +289,17 @@ func requireAuth(minRole permissions.Role, next http.HandlerFunc) http.HandlerFu } if auth.CrossTenant && auth.TenantID != uuid.Nil { // Cross-tenant admin with tenant scope: filter data by chosen tenant + ctx = store.WithCrossTenant(ctx) ctx = store.WithTenantID(ctx, auth.TenantID) slog.Debug("security.http_auth_resolved", "path", r.URL.Path, "role", string(auth.Role), + "cross_tenant", true, "tenant_scope", auth.TenantID.String(), ) } else if auth.CrossTenant { - // Auto-scope to MasterTenantID so all operations use a concrete tenant. + // Cross-tenant admin without scope: default to MasterTenantID. + ctx = store.WithCrossTenant(ctx) ctx = store.WithTenantID(ctx, store.MasterTenantID) slog.Debug("security.http_auth_resolved", "path", r.URL.Path, @@ -367,6 +370,7 @@ func requireAuthBearer(minRole permissions.Role, bearer string, w http.ResponseW ctx = store.WithUserID(ctx, userID) } if auth.CrossTenant { + ctx = store.WithCrossTenant(ctx) ctx = store.WithTenantID(ctx, store.MasterTenantID) } else if auth.TenantID != uuid.Nil { ctx = store.WithTenantID(ctx, auth.TenantID)