> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pensionsportal.ie/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorisation — RBAC Model

> Role-based access control for PensionsPortal.ie: four roles, tenant scoping, and enforcement at every data layer.

PensionsPortal.ie enforces a **role-based access control (RBAC)** model with four roles and mandatory tenant scoping. Every API route checks both the user's role and their `brokerId` / `employerId` before returning or mutating data.

## Role Definitions

| Role (DB)     | Session Role | Description                                                                                        |
| ------------- | ------------ | -------------------------------------------------------------------------------------------------- |
| `SuperAdmin`  | `admin`      | Full platform access. No broker or employer scope restriction. Reserved for platform operators.    |
| `BrokerAdmin` | `broker`     | Full access within their `brokerId`. Manages schemes, members, compliance tasks for their clients. |
| `BrokerUser`  | `broker`     | Same data scope as BrokerAdmin. UI may restrict specific administrative actions.                   |
| `Trustee`     | `employer`   | Scoped to their `employerId`. Can view scheme data and submit required information.                |

<Note>
  `member` role is defined in the type system for future member-portal use but is not currently active in the application UI.
</Note>

## Tenant Scoping

Multi-tenancy is enforced structurally, not just by convention. Every database query that returns scheme, member, or employer data includes a `brokerId` or `employerId` filter derived from the authenticated session:

```ts theme={null}
// Example: broker can only see schemes belonging to their brokerId
const schemes = await db
  .select()
  .from(schemesTable)
  .where(eq(schemesTable.brokerId, session.user.brokerId))
```

A broker user **cannot** access another broker's data, even if they know the scheme or member ID — the WHERE clause makes cross-tenant access structurally impossible at the query level.

## Role Mapping

The application maps database role names to session role values in `src/lib/auth.ts`:

```ts theme={null}
const roleMap: Record<string, UserRole> = {
  SuperAdmin:  "admin",
  BrokerAdmin: "broker",
  BrokerUser:  "broker",
  Trustee:     "employer",
}
```

This mapping is applied at login and embedded in the JWT. Changes to a user's role in the database take effect at next login.

## Session Claims

Every authenticated session carries:

```ts theme={null}
interface PensionsUser {
  id: string
  email: string
  role: UserRole        // "admin" | "broker" | "employer" | "member"
  brokerId: string | null
  employerId: string | null
}
```

These claims are used by API route handlers and React Server Components to gate access.

## API Enforcement Pattern

<Steps>
  <Step title="Extract session">
    `const session = await auth()` — retrieves the validated JWT session server-side.
  </Step>

  <Step title="Check authentication">
    If `!session?.user`, return `401 Unauthorized`.
  </Step>

  <Step title="Check role">
    If the route requires a specific role (e.g., `broker` or `admin`), check `session.user.role`. Return `403 Forbidden` if insufficient.
  </Step>

  <Step title="Scope the query">
    Append `brokerId` or `employerId` to every database query using the session claim.
  </Step>
</Steps>

## Audit Trail

All material actions — regardless of role — are written to the append-only `audit_logs` table, recording the `actorId`, `actorType`, action performed, previous state, and new state. See [Audit Logging](/security/audit-logging) for schema details.

## AI Change Proposals

AI-generated suggestions never bypass RBAC. The `changeProposals` table enforces:

* Proposals are scoped to a `brokerId` (and optionally `schemeId`, `memberId`)
* Only an authorised broker user can approve or reject a proposal
* Required attestations must be completed before approval is permitted
* Proposals auto-expire if not acted upon

This ensures AI capability cannot exceed human-authorised permissions.
