Skip to main content

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).

Admins 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:

ColumnBehaviour
MemberName + email. The billing owner's row is locked and marked with an amber crown.
RoleEditable role chip per row. Changing it stages a pending change; nothing is written until Save.
TierA tier pill (Admin / Member / Viewer) derived from the assigned role.
StatusActive 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.

ColumnMeaning
Name / DescriptionThe role name and its human description.
TypeCustom (created by this company) vs Global (platform-seeded).
TierAdmin / Member / Viewer pill.
PermissionsCount of permissions attached to the role.
MembersCount of members currently assigned the role.

Actions

ActionAvailability
CloneAny role — global or custom. Produces an editable custom copy.
EditCustom roles only. Disabled on global roles.
DeleteCustom 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:

TriggerMutation
Create a new roleCREATE_COMPANY_ROLE
Save edits to a custom roleUPDATE_COMPANY_ROLE
Clone any role into a custom copyCLONE_ROLE_AS_CUSTOM (backend cloneRoleAsCustom)
Delete a custom roleDELETE_COMPANY_ROLE
Custom roles carry their own tier

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 filterGET_COMPANY_ASSIGNABLE_ROLES (backend companyAssignableRoles) 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:

TierRolesCapability
AdminSuper Admin, Company AdminFull — manage team, invite, company settings
MemberInternal Sales, Recruiter/HRCreate / edit / delete records
ViewerExternal Sales, Economy, Auditor/ViewerRead-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.view
  • pipeline.view
  • team.view

The Sidebar hides a nav item when the member lacks the effective permission for its key.

Phase 1 default is show-all

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.