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)
This commit is contained in:
Duc Nguyen authored and GitHub committed 2026-03-24 11:28:53 +07:00
1 parent eab3766c5f
commit 39b7c689f2
1 file changed
+5 -1
+5 -1
View File
@@ -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)