> For the complete documentation index, see [llms.txt](https://flapjax.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flapjax.gitbook.io/docs/start-here/roles-and-permissions.md).

# Roles & Permissions

### Overview

Flapjax uses **role-based access control that is enforced in two layers**:

* **The backend is the real authority.** Every admin-API request meets the permissions carried in the user's Auth0 access token. A token missing the permission an operation requires gets **403 Forbidden**, whatever the interface showed.
* **The frontend restricts what you see.** The admin interface hides navigation items, whole pages and individual action buttons your role does not grant. A route guard also redirects you away from pages you cannot reach.

The two layers use **two different permission vocabularies**, both explained below. The frontend vocabulary decides visibility. The backend vocabulary decides authorization. They are related and not identical, and the backend check governs what actually happens where they diverge.

***

### Roles

The admin interface recognises **three roles**, held as a fixed set in code. Nothing fetches them dynamically from the backend, and no "Editor" role exists.

| Role       | Meaning                                                                                      |
| ---------- | -------------------------------------------------------------------------------------------- |
| **admin**  | Full access, meaning every interface permission.                                             |
| **member** | Views and manages all data domains, views every settings page, manages a subset of settings. |
| **viewer** | Read-only everywhere. They view data and settings pages, and write controls stay hidden.     |

> This corrects two earlier claims. First, the middle role is **member**, not "Editor". Second, roles are a **hardcoded map** in the frontend, rather than a dynamic list fetched per organization.

#### How a user's role is resolved

A user can carry more than one role. The interface resolves the effective role from the **first** entry in the role array, `roles[0]`, rather than the last.

Backend responses send capitalized role names such as `Admin`, and the frontend lowercases them to match the map above. With no role resolving, the interface **defaults to viewer**, the least privilege.

> This corrects the earlier claim that the interface displays the **last** role. The code reads `roles[0]`, the first one.

***

### The two permission vocabularies

#### Frontend permissions: `domain:action`, for interface visibility

The frontend defines a flat set of **28 permissions**, each of the form `domain:action`. They control **what the interface shows and hides**: navigation items, route access and write buttons. Nothing sends them to the backend as an authorization request, and they evaluate locally against the user's role.

| Group                 | Permissions                                                                                                                                                                                                                |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Data domains**      | `people:view` · `people:manage` · `stacks:view` · `stacks:manage` · `automations:view` · `automations:manage` · `labels:view` · `labels:manage` · `reports:view` · `reports:manage`                                        |
| **Settings (view)**   | `settings:users` · `settings:organization` · `settings:access_clients` · `settings:applications` · `settings:custom_actions` · `settings:templates` · `settings:campaigns` · `settings:files` · `settings:data_management` |
| **Settings (manage)** | The parallel `settings:*_manage` set (`settings:users_manage`, `settings:organization_manage`, … `settings:data_management_manage`)                                                                                        |

`:view` permissions gate route access and nav visibility. `:manage` permissions gate the write buttons on the matching page.

#### Backend permissions: `{read|modify}:{section}`, the real authorization

The backend enforces a smaller vocabulary: **14 permissions**, one `read` and one `modify` for each of **seven sections**. These arrive in the Auth0 JWT as a `permissions` claim, and every protected handler checks the specific permission it needs before doing any work.

| Section     | Read               | Modify               |
| ----------- | ------------------ | -------------------- |
| automations | `read:automations` | `modify:automations` |
| settings    | `read:settings`    | `modify:settings`    |
| labels      | `read:labels`      | `modify:labels`      |
| people      | `read:people`      | `modify:people`      |
| stacks      | `read:stacks`      | `modify:stacks`      |
| users       | `read:users`       | `modify:users`       |
| lists       | `read:lists`       | `modify:lists`       |

Rules the backend applies:

* **`modify` implies `read`.** A handler needing `read:people` is satisfied by a token holding `modify:people`.
* **Enforcement is per-handler, rather than middleware-wide.** Each endpoint declares the permission and section it needs, then returns **401** for a missing or invalid token, and **403** for a missing permission.
* The section an operation maps to is not always obvious. Person-attribute *schema* changes need `settings`, since they are a workspace setting, and CSV exports need `modify` rather than `read`. The full mapping lives in the backend docs.

***

### Frontend role → permission matrix

This is the hardcoded map the admin interface applies to decide what each role sees and does.

| Frontend permission               | admin | member | viewer |
| --------------------------------- | :---: | :----: | :----: |
| `people:view`                     |   ✅   |    ✅   |    ✅   |
| `people:manage`                   |   ✅   |    ✅   |    ❌   |
| `stacks:view`                     |   ✅   |    ✅   |    ✅   |
| `stacks:manage`                   |   ✅   |    ✅   |    ❌   |
| `automations:view`                |   ✅   |    ✅   |    ✅   |
| `automations:manage`              |   ✅   |    ✅   |    ❌   |
| `labels:view`                     |   ✅   |    ✅   |    ✅   |
| `labels:manage`                   |   ✅   |    ✅   |    ❌   |
| `reports:view`                    |   ✅   |    ✅   |    ✅   |
| `reports:manage`                  |   ✅   |    ✅   |    ❌   |
| `settings:users` (view)           |   ✅   |    ✅   |    ✅   |
| `settings:organization` (view)    |   ✅   |    ✅   |    ✅   |
| `settings:access_clients` (view)  |   ✅   |    ✅   |    ✅   |
| `settings:applications` (view)    |   ✅   |    ✅   |    ✅   |
| `settings:custom_actions` (view)  |   ✅   |    ✅   |    ✅   |
| `settings:templates` (view)       |   ✅   |    ✅   |    ✅   |
| `settings:campaigns` (view)       |   ✅   |    ✅   |    ✅   |
| `settings:files` (view)           |   ✅   |    ✅   |    ✅   |
| `settings:data_management` (view) |   ✅   |    ✅   |    ✅   |
| `settings:users_manage`           |   ✅   |    ❌   |    ❌   |
| `settings:organization_manage`    |   ✅   |    ❌   |    ❌   |
| `settings:access_clients_manage`  |   ✅   |    ❌   |    ❌   |
| `settings:applications_manage`    |   ✅   |    ❌   |    ❌   |
| `settings:custom_actions_manage`  |   ✅   |    ✅   |    ❌   |
| `settings:templates_manage`       |   ✅   |    ✅   |    ❌   |
| `settings:campaigns_manage`       |   ✅   |    ✅   |    ❌   |
| `settings:files_manage`           |   ✅   |    ✅   |    ❌   |
| `settings:data_management_manage` |   ✅   |    ✅   |    ❌   |

**Reading the matrix:**

* **admin** holds all 28 permissions.
* **member** holds view and manage on every data domain, view on every settings page, and manage on the editor-level settings alone: custom actions, templates, campaigns, files and data management. A member cannot manage **users, organization, access clients or applications**.
* **viewer** holds the `:view` permissions alone, meaning 5 data-domain views plus 9 settings views. Every `:manage` write button stays hidden.

***

### Where roles actually come from (Auth0)

The three role *names* and the frontend permission map are hardcoded in the interface. The backend defines no roles at all. Instead:

* A user's **roles come from their Auth0 organization token**, and the JWT carries the **already-resolved list of backend permissions** (`{read|modify}:{section}`).
* The **mapping from an Auth0 role to backend permissions is tenant configuration in Auth0**, outside both repositories. Adjusting what a role grants server-side is an Auth0 configuration change, never a code change.

***

### Authentication

#### Auth0 integration

Flapjax uses Auth0 for authentication with organization-level tenant isolation:

| Setting      | Purpose                                              |
| ------------ | ---------------------------------------------------- |
| Auth0 Domain | Identity provider tenant                             |
| Client ID    | SPA application identifier                           |
| Organization | Tenant isolation, keeping each organization separate |
| Audience     | API identifier for token claims                      |

#### Authentication flow

1. User navigates to the application
2. App checks authentication state via the Auth0 SDK
3. An unauthenticated visitor gets redirected to the Auth0 hosted login
4. Auth0 processes login and redirects to `/callback`
5. App obtains an access token via `getAccessTokenSilently()`
6. The app injects the token into every API request as `Authorization: Bearer {token}`
7. The backend validates the token and checks the specific permission each request needs

Accepting a team invitation is a special case. The invitation ticket must reach Auth0, so the invited organization's roles apply. See App Shell & Authentication for the details.

#### Organization isolation

* Each organization has its own Auth0 organization ID
* Users are scoped to a single organization
* Every backend manager and store call is scoped by the token's organization ID, and no path reads or writes another org's data
* Cross-organization access is not possible

***

### User management

#### Managing users (`/settings/users`)

**Users tab:**

* Lists current team members
* Displays: name, email, avatar, role, joined date
* Delete action available per user

**Invitees tab:**

* Lists pending invitations
* Shows invitation status and sent date
* Options to resend or revoke invitations

#### Inviting users

1. Click "Invite User"
2. Enter the email address of the person to invite
3. Select a role
4. Submit, and an invitation email goes out

**Form validation:**

* Email: required, valid format, max 254 characters
* Role: required, must select from available options

#### Updating user roles

You can update a user's role after the invitation. The update sends both the old and the new role IDs, which lets the backend track the change.

#### Deleting users

Removing a user from the organization revokes their access.

***

### Two behaviors worth knowing

These are observed behaviours rather than recommendations.

* **Manual sends are gated differently in the interface and the API.** The batch and manual "send email" and "send SMS" controls appear for anyone holding `people:manage`. The server authorizes those same send endpoints against **`modify:settings`**. The visibility gate and the authorization check are two different permissions, and the server check decides whether the send succeeds.
