feat(operator): show per-tenant role in tenant users list
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.
This commit is contained in:
@@ -203,7 +203,7 @@ async function reconcile() {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Badge :tone="u.platformAdmin ? 'accent' : 'neutral'">
|
<Badge :tone="u.platformAdmin ? 'accent' : 'neutral'">
|
||||||
{{ u.platformAdmin ? 'platform-admin' : u.role }}
|
{{ u.platformAdmin ? 'platform-admin' : u.tenantRole }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -41,7 +41,12 @@ export interface TenantUser {
|
|||||||
authentikSubjectId: string
|
authentikSubjectId: string
|
||||||
email: string
|
email: string
|
||||||
name: 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: '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
|
active: boolean
|
||||||
platformAdmin: boolean
|
platformAdmin: boolean
|
||||||
tenantIds: string[]
|
tenantIds: string[]
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { UpdateTenantBrandingDto } from './dto/update-tenant-branding.dto.js'
|
|||||||
import { UpdateTenantDto } from './dto/update-tenant.dto.js'
|
import { UpdateTenantDto } from './dto/update-tenant.dto.js'
|
||||||
import { StorageService } from './storage.service.js'
|
import { StorageService } from './storage.service.js'
|
||||||
import { TenantBrandingService } from './tenant-branding.service.js'
|
import { TenantBrandingService } from './tenant-branding.service.js'
|
||||||
|
import { roleForTenant } from '../schemas/user.schema.js'
|
||||||
import { TenantSsoService } from './tenant-sso.service.js'
|
import { TenantSsoService } from './tenant-sso.service.js'
|
||||||
import { TenantsService } from './tenants.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))) {
|
if (!actor.platformAdmin && !actor.tenantIds.some((id) => id.equals(tenant._id))) {
|
||||||
throw new ForbiddenException(`No access to tenant "${slug}"`)
|
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
|
// Aggregate storage usage for the customer-admin Storage page. Same membership
|
||||||
|
|||||||
Reference in New Issue
Block a user