From 2a43a7bbf3c1edf3cd798df238d1b41426fef992 Mon Sep 17 00:00:00 2001 From: Ronni Baslund Date: Sun, 31 May 2026 21:31:51 +0200 Subject: [PATCH] feat(operator): show per-tenant role in tenant users list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /tenants/:slug/users now returns a tenant-scoped `tenantRole` (resolved server-side via roleForTenant), and the operator tenant page displays it instead of the global `role` — so a user who is admin here but member elsewhere reads correctly in this tenant's context. The global `role` field is kept intact for other consumers. --- apps/operator/pages/tenants/[slug].vue | 2 +- apps/operator/types/tenant.ts | 5 +++++ .../platform-api/src/tenants/tenants.controller.ts | 11 ++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/operator/pages/tenants/[slug].vue b/apps/operator/pages/tenants/[slug].vue index 7a951a6..29c9ec9 100644 --- a/apps/operator/pages/tenants/[slug].vue +++ b/apps/operator/pages/tenants/[slug].vue @@ -203,7 +203,7 @@ async function reconcile() { - {{ u.platformAdmin ? 'platform-admin' : u.role }} + {{ u.platformAdmin ? 'platform-admin' : u.tenantRole }} diff --git a/apps/operator/types/tenant.ts b/apps/operator/types/tenant.ts index e5d94cb..72ff13e 100644 --- a/apps/operator/types/tenant.ts +++ b/apps/operator/types/tenant.ts @@ -41,7 +41,12 @@ export interface TenantUser { authentikSubjectId: string email: string name: string + // Global/legacy role + fallback. Prefer `tenantRole` for display in a tenant + // context — `role` can differ from the user's actual role in this tenant. role: 'owner' | 'admin' | 'member' + // Role resolved for THIS tenant (per-tenant override, else global `role`). + // Set by GET /tenants/:slug/users. + tenantRole: 'owner' | 'admin' | 'member' active: boolean platformAdmin: boolean tenantIds: string[] diff --git a/services/platform-api/src/tenants/tenants.controller.ts b/services/platform-api/src/tenants/tenants.controller.ts index d805d7b..df9ec6a 100644 --- a/services/platform-api/src/tenants/tenants.controller.ts +++ b/services/platform-api/src/tenants/tenants.controller.ts @@ -28,6 +28,7 @@ import { UpdateTenantBrandingDto } from './dto/update-tenant-branding.dto.js' import { UpdateTenantDto } from './dto/update-tenant.dto.js' import { StorageService } from './storage.service.js' import { TenantBrandingService } from './tenant-branding.service.js' +import { roleForTenant } from '../schemas/user.schema.js' import { TenantSsoService } from './tenant-sso.service.js' import { TenantsService } from './tenants.service.js' @@ -89,7 +90,15 @@ export class TenantsController { if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) { throw new ForbiddenException(`No access to tenant "${slug}"`) } - return this.tenants.listUsersForTenant(slug) + // Add a tenant-scoped `tenantRole` to each user so the UI shows the role + // for THIS tenant, not the global fallback. `role` is left intact for any + // other consumer; resolution stays server-side so the precedence in + // roleForTenant() isn't duplicated per frontend. + const users = await this.tenants.listUsersForTenant(slug) + return users.map((u) => ({ + ...u.toObject(), + tenantRole: roleForTenant(u, tenant._id), + })) } // Aggregate storage usage for the customer-admin Storage page. Same membership