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
|