IAM Dashboard
The IAM (Identity & Access Management) dashboard is the admin-only control surface where Company Admins manage who belongs to a company, what role each member holds, and which custom roles exist. It layers a friendly, AWS-style UI over the backend role/permission model documented in Roles & Permissions.
Where It Lives
The page component is app/company/team/iam/page.tsx, but users reach it through Settings, not a /company/team/iam URL. lib/utils/settingsPresets.ts maps the companyIam panel to the iam tab, so the dashboard is surfaced at:
/userprofile/iam
The Team space overview page also links to it through an IAM Access card (admin-only).
Entry is gated on canManageTeam from useCompanyPermissions. When a non-admin lands on the route, the page renders an "Admins only" lock state instead of the tabs. The backend remains the final authority — the UI gate only hides controls the caller cannot use.
Layout
The dashboard is a three-tab workspace.
Members Tab
The Members tab renders the company roster (COMPANY_MEMBERS) as a table:
| Column | Behaviour |
|---|---|
| Member | Name + email. The billing owner's row is locked and marked with an amber crown. |
| Role | Editable role chip per row. Changing it stages a pending change; nothing is written until Save. |
| Tier | A tier pill (Admin / Member / Viewer) derived from the assigned role. |
| Status | Active for accepted members, Pending for invitations that have not yet been accepted. |
Reassigning a role
Selecting a new role on a member's chip stages the change locally. Pressing Save issues UPDATE_USER_COMPANY_ROLE (backend updateUserCompanyRole). The owner row cannot be reassigned from here — ownership moves through Company Ownership Transfer, not a role edit.
The list of roles a member can be assigned is filtered by the assignable-role whitelist, so platform-only roles (super admin, support) never appear in the chip.
Recent role changes (audit feed)
Below the roster, a Recent role changes feed reads ROLE_CHANGE_AUDIT (backend roleChangeAudit) and shows the last 8 reassignments. Each entry reads:
{actor} changed {target}'s role from {old} to {new} — {reason} · {relative time}
This gives admins a lightweight, in-context trail of who changed access and why, without leaving the dashboard. See Roles & Permissions → Role reassignment & audit for the backend contract.
Roles Tab
The Roles tab is an AWS-IAM-style table of every role available to the company.
| Column | Meaning |
|---|---|
| Name / Description | The role name and its human description. |
| Type | Custom (created by this company) vs Global (platform-seeded). |
| Tier | Admin / Member / Viewer pill. |
| Permissions | Count of permissions attached to the role. |
| Members | Count of members currently assigned the role. |
Actions
| Action | Availability |
|---|---|
| Clone | Any role — global or custom. Produces an editable custom copy. |
| Edit | Custom roles only. Disabled on global roles. |
| Delete | Custom roles only. Disabled on global roles. |
Global roles are intentionally read-only. Their Edit/Delete controls are disabled with the hint "clone it to make your own editable copy." This keeps the platform-seeded matrix (documented in Roles & Permissions) stable while still letting a company tailor access.
Role editor modal
Clone, Edit, and Create open the same modal editor:
- Name and Description fields.
- Access tier radio — Admin / Member / Viewer. This drives the tier pill and the write/manage gating downstream.
- Permission catalog grouped by resource, with a search box and a per-group select-all toggle, so an admin can grant an entire resource's read/write/delete set in one click.
Mutations wired to the editor:
| Trigger | Mutation |
|---|---|
| Create a new role | CREATE_COMPANY_ROLE |
| Save edits to a custom role | UPDATE_COMPANY_ROLE |
| Clone any role into a custom copy | CLONE_ROLE_AS_CUSTOM (backend cloneRoleAsCustom) |
| Delete a custom role | DELETE_COMPANY_ROLE |
Global roles get their tier from the code arrays in lib/utils/roles.ts (ADMIN_ROLES / MEMBER_ROLES / VIEWER_ROLES). Custom roles instead carry a backend tier field, which getRoleTier(roleName, tier?) reads as a fallback. So a custom role's write/manage capability follows the tier the admin picked in the editor.
Permissions Tab
The Permissions tab is a read-only catalog of every permission in the platform, grouped by resource in color-tinted cards. It is a reference view — there is no mutation here. Admins use it to understand what a permission grants before attaching it to a role in the Roles tab. The underlying set is the 39-permission / 13-resource matrix documented in Roles & Permissions.
Assignable-role whitelist
Not every role in the platform can be handed out by a company admin. Platform-only roles (super admin, support) must never be assignable from the IAM UI.
- Server-side filter —
GET_COMPANY_ASSIGNABLE_ROLES(backendcompanyAssignableRoles) returns only the roles a company admin is allowed to assign, filtering platform-only roles out server-side. - Defensive frontend intersection — the Members-tab role chip intersects role IDs against the assignable-role result. While that query is still loading, the UI defensively hides super admin / support so a privileged role can never flash into an assignable dropdown.
Tier model
The tier a role belongs to determines whether members holding it can write or manage. From lib/utils/roles.ts:
| Tier | Roles | Capability |
|---|---|---|
| Admin | Super Admin, Company Admin | Full — manage team, invite, company settings |
| Member | Internal Sales, Recruiter/HR | Create / edit / delete records |
| Viewer | External Sales, Economy, Auditor/Viewer | Read-only |
Custom roles do not appear in these arrays; their tier comes from the backend tier field set in the role editor.
Per-member navigation gating (Phase 1)
Beyond company-record gating, the IAM model also feeds per-member navigation visibility. Each entry in jobSeekerNavItems and teamNavItems (lib/config/navigation.ts) carries a requiredPermission key, for example:
jobseeker.dashboard.viewpipeline.viewteam.view
The Sidebar hides a nav item when the member lacks the effective permission for its key.
Until the backend ships effectivePermissions on whoAmI, the gate defaults to show everything — no item is hidden yet. The requiredPermission keys are wired on the frontend now so that, once the backend surfaces effective permissions, per-member nav visibility becomes a data change rather than a code change.
See RBAC Frontend → requiredPermission nav gating for the frontend mechanics.
Related Pages
- Roles & Permissions — the backend role/permission matrix, guards, and custom-role rules
- Team Member Management — invitation lifecycle, ownership transfer, invited-member onboarding
- Security & Authentication — auth model overview
- RBAC Frontend —
useCompanyPermissions, the three-view sidebar, and IAM UI gating - Company Pages → Team — where the IAM Access card lives