# Permission Nodes

This document lists all known permission nodes in the optima-api application, categorized by resource type.

## Permission System Overview

The permission system uses a dot-notation format: `resource.action[.modifier]`

### Special Tokens

The permission validator supports special tokens for flexible permission management:

- **Asterisk (\*)**: Matches the token and all following tokens (e.g., `credential.*` grants all credential permissions)
- **Question Mark (?)**: Matches only the specific token (single character wildcard)
- **Inclusive List ([a,b,c])**: Matches only the tokens in the list
- **Exclusive List (<a,b,c>)**: Matches all tokens except those in the list

### Global Permissions

- `*` - Full access to all resources and actions (typically assigned to administrator role)

## Permission Nodes by Resource

### Company Permissions

| Permission Node                | Description                                                             | Used In                                                                              |
| ------------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `company.fetch`                | Fetch a single company                                                  | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts)                   |
| `company.fetch.address`        | View company address information (requires `company.fetch` as well)     | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts)                   |
| `company.fetch.contacts`       | View all company contacts (requires `company.fetch` as well)            | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts)                   |
| `company.fetch.many`           | Fetch multiple companies                                                | [src/api/companies/fetchAll.ts](src/api/companies/fetchAll.ts)                       |
| `company.fetch.configurations` | Fetch company configurations (requires `company.fetch` as well)         | [src/api/companies/[id]/configurations.ts](src/api/companies/[id]/configurations.ts) |
| `company.fetch.sites`          | Fetch company sites from ConnectWise (requires `company.fetch` as well) | [src/api/companies/[id]/sites.ts](src/api/companies/[id]/sites.ts)                   |

### Credential Permissions

| Permission Node                     | Description                                                                            | Used In                                                                                                                                                              |
| ----------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `credential.create`                 | Create a new credential                                                                | [src/api/credentials/create.ts](src/api/credentials/create.ts)                                                                                                       |
| `credential.fetch`                  | Fetch a single credential                                                              | [src/api/credentials/fetch.ts](src/api/credentials/fetch.ts)                                                                                                         |
| `credential.fetch.many`             | Fetch multiple credentials                                                             | [src/api/credentials/fetchByCompany.ts](src/api/credentials/fetchByCompany.ts)                                                                                       |
| `credential.update`                 | Update a credential                                                                    | [src/api/credentials/update.ts](src/api/credentials/update.ts)                                                                                                       |
| `credential.delete`                 | Delete a credential                                                                    | [src/api/credentials/delete.ts](src/api/credentials/delete.ts)                                                                                                       |
| `credential.fields.fetch`           | Fetch credential fields (requires `credential.fetch` as well)                          | [src/api/credentials/fetchFields.ts](src/api/credentials/fetchFields.ts)                                                                                             |
| `credential.fields.update`          | Update credential fields (requires `credential.update` as well)                        | [src/api/credentials/updateFields.ts](src/api/credentials/updateFields.ts)                                                                                           |
| `credential.secure_values.read`     | Read secure values of a credential (requires `credential.fetch` as well)               | [src/api/credentials/readSecureValues.ts](src/api/credentials/readSecureValues.ts), [src/api/credentials/readSecureValue.ts](src/api/credentials/readSecureValue.ts) |
| `credential.sub_credentials.fetch`  | Fetch sub-credentials of a parent credential (requires `credential.fetch` as well)     | [src/api/credentials/fetchSubCredentials.ts](src/api/credentials/fetchSubCredentials.ts)                                                                             |
| `credential.sub_credentials.create` | Create a sub-credential on a parent credential (requires `credential.fetch` as well)   | [src/api/credentials/addSubCredential.ts](src/api/credentials/addSubCredential.ts)                                                                                   |
| `credential.sub_credentials.delete` | Remove a sub-credential from a parent credential (requires `credential.fetch` as well) | [src/api/credentials/removeSubCredential.ts](src/api/credentials/removeSubCredential.ts)                                                                             |

### Credential Type Permissions

| Permission Node              | Description                     | Used In                                                                      |
| ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------- |
| `credential_type.create`     | Create a new credential type    | [src/api/credential-types/create.ts](src/api/credential-types/create.ts)     |
| `credential_type.fetch`      | Fetch a single credential type  | [src/api/credential-types/fetch.ts](src/api/credential-types/fetch.ts)       |
| `credential_type.fetch.many` | Fetch multiple credential types | [src/api/credential-types/fetchAll.ts](src/api/credential-types/fetchAll.ts) |
| `credential_type.update`     | Update a credential type        | [src/api/credential-types/update.ts](src/api/credential-types/update.ts)     |
| `credential_type.delete`     | Delete a credential type        | [src/api/credential-types/delete.ts](src/api/credential-types/delete.ts)     |

### Role Permissions

| Permission Node | Description                                       | Used In                                                                                                                                                                                          | Dependencies |
| --------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ |
| `role.create`   | Create a new role                                 | [src/api/roles/create.ts](src/api/roles/create.ts)                                                                                                                                               |              |
| `role.read`     | Fetch a single role or view role information      | [src/api/roles/fetch.ts](src/api/roles/fetch.ts)                                                                                                                                                 |              |
| `role.list`     | Fetch all roles                                   | [src/api/roles/fetchAll.ts](src/api/roles/fetchAll.ts)                                                                                                                                           | `role.read`  |
| `role.modify`   | Update role properties or manage role permissions | [src/api/roles/update.ts](src/api/roles/update.ts), [src/api/roles/addPermissions.ts](src/api/roles/addPermissions.ts), [src/api/roles/removePermissions.ts](src/api/roles/removePermissions.ts) |              |
| `role.delete`   | Delete a role                                     | [src/api/roles/delete.ts](src/api/roles/delete.ts)                                                                                                                                               |              |
| `user.read`     | View users assigned to a role                     | [src/api/roles/getUsers.ts](src/api/roles/getUsers.ts)                                                                                                                                           | `role.read`  |

### User Permissions

| Permission Node          | Description                                       | Used In                                                                                                                                                                      | Dependencies       |
| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `user.read`              | Read user information                             | [src/api/user/@me/fetch.ts](src/api/user/@me/fetch.ts)                                                                                                                       |                    |
| `user.write`             | Update user information                           | [src/api/user/@me/update.ts](src/api/user/@me/update.ts)                                                                                                                     |                    |
| `user.read.other`        | Read other users' information                     | [src/api/user/fetch.ts](src/api/user/fetch.ts), [src/api/user/fetchRoles.ts](src/api/user/fetchRoles.ts), [src/api/user/checkPermission.ts](src/api/user/checkPermission.ts) |                    |
| `user.list.other`        | List all users                                    | [src/api/user/fetchAll.ts](src/api/user/fetchAll.ts)                                                                                                                         | `user.read.other`  |
| `user.write.other`       | Update other users' information                   | [src/api/user/update.ts](src/api/user/update.ts)                                                                                                                             |                    |
| `user.roles.other`       | Modify roles assigned to other users              | [src/api/user/update.ts](src/api/user/update.ts)                                                                                                                             | `user.write.other` |
| `user.permissions.other` | Modify direct permissions assigned to other users | [src/api/user/update.ts](src/api/user/update.ts)                                                                                                                             | `user.write.other` |
| `user.delete.other`      | Delete other users                                | [src/api/user/delete.ts](src/api/user/delete.ts)                                                                                                                             |                    |

### Permission Routes

Permissions required for accessing the permission node definitions API.

| Permission Node | Description                                      | Used In                                                                          |
| --------------- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
| `role.read`     | Fetch all permission nodes organized by category | [src/api/permissions/fetchAll.ts](src/api/permissions/fetchAll.ts)               |
| `role.read`     | Fetch a flat list of all permission nodes        | [src/api/permissions/fetchNodes.ts](src/api/permissions/fetchNodes.ts)           |
| `role.read`     | Fetch permission nodes by category               | [src/api/permissions/fetchByCategory.ts](src/api/permissions/fetchByCategory.ts) |

### UI Navigation Permissions

Permissions for controlling navigation visibility on the frontend.

| Permission Node        | Description                                                          | Usage Pattern                                     |
| ---------------------- | -------------------------------------------------------------------- | ------------------------------------------------- |
| `ui.navigation.*.view` | View specific navigation sections (e.g., `ui.navigation.admin.view`) | Control which navigation menu items are displayed |

### Admin UI Permissions

Admin-specific UI permissions that control visibility and data loading for admin sub-tabs.

| Permission Node               | Description                                      | Usage Pattern                              |
| ----------------------------- | ------------------------------------------------ | ------------------------------------------ |
| `admin.users.view`            | Show the Users tab and load user data            | Show/hide users tab, allow user list fetch |
| `admin.roles.view`            | Show the Roles tab and load role data            | Show/hide roles tab, allow role list fetch |
| `admin.credential-types.view` | Show the Credential Types tab and load type data | Show/hide types tab, allow type list fetch |

#### Notes on UI Permissions

- **Client-side validation is not secure**: Always enforce permissions on the API level. UI permissions only control visibility and user experience.
- **Combine with API permissions**: A user with an admin UI permission should also have the corresponding API permission (e.g., `role.list`) to actually load data.
- **Use wildcards for flexibility**: Grant `ui.navigation.*.view` to allow all navigation sections.

### Procurement Permissions

| Permission Node                         | Description                                                                  | Used In                                                                                                                                                                                                                                                                    | Dependencies                |
| --------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `procurement.catalog.fetch`             | Fetch a single catalog item                                                  | [src/api/procurement/[id]/fetch.ts](src/api/procurement/[id]/fetch.ts)                                                                                                                                                                                                     |                             |
| `procurement.catalog.fetch.many`        | Fetch multiple catalog items, count, categories/ecosystems, or filter values | [src/api/procurement/fetchAll.ts](src/api/procurement/fetchAll.ts), [src/api/procurement/count.ts](src/api/procurement/count.ts), [src/api/procurement/categories.ts](src/api/procurement/categories.ts), [src/api/procurement/filters.ts](src/api/procurement/filters.ts) |                             |
| `procurement.catalog.inventory.refresh` | Refresh on-hand inventory for a catalog item from ConnectWise                | [src/api/procurement/[id]/refreshInventory.ts](src/api/procurement/[id]/refreshInventory.ts)                                                                                                                                                                               | `procurement.catalog.fetch` |
| `procurement.catalog.link`              | Link or unlink catalog items to each other                                   | [src/api/procurement/[id]/link.ts](src/api/procurement/[id]/link.ts), [src/api/procurement/[id]/unlink.ts](src/api/procurement/[id]/unlink.ts)                                                                                                                             | `procurement.catalog.fetch` |

### ConnectWise Routes

`GET /v1/cw/members` requires only authentication (any logged-in user) and does **not** require a specific permission node.

`POST /v1/cw/callback/:secret/:resource` is intentionally unauthenticated for inbound ConnectWise callbacks and does **not** require a permission node.

| Permission Node | Description                                                                     | Used In                                                  | Dependencies |
| --------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------ |
| _None_          | Fetch CW members (auth only)                                                    | [src/api/cw/fetchMembers.ts](src/api/cw/fetchMembers.ts) | N/A          |
| _None_          | Inbound callback route; secured operationally (network controls / source trust) | [src/api/cw/callback.ts](src/api/cw/callback.ts)         | N/A          |

### Sales Permissions

Permissions for accessing and managing sales opportunities. Opportunities are synced from ConnectWise and stored locally; sub-resources (products, notes, contacts) are fetched live from CW.

**WebSocket note:** The `/secure` socket event chain `opp:live_quote_preview` and `opp:live_quote_preview:<id>:data` is gated by `sales.opportunity.fetch`.

| Permission Node                                 | Description                                                                                                                                                  | Used In                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Dependencies                   |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |   | ------------------------------ |
| `sales.opportunity.fetch`                       | Fetch a single opportunity and its CW sub-resources (products, notes, contacts)                                                                              | [src/api/sales/opportunities/[id]/fetch.ts](src/api/sales/opportunities/[id]/fetch.ts), [src/api/sales/opportunities/[id]/products/fetchAll.ts](src/api/sales/opportunities/[id]/products/fetchAll.ts), [src/api/sales/opportunities/[id]/notes/fetchAll.ts](src/api/sales/opportunities/[id]/notes/fetchAll.ts), [src/api/sales/opportunities/[id]/notes/fetch.ts](src/api/sales/opportunities/[id]/notes/fetch.ts), [src/api/sales/opportunities/[id]/contacts/fetchAll.ts](src/api/sales/opportunities/[id]/contacts/fetchAll.ts), [src/api/sockets/events/liveQuotePreview.ts](src/api/sockets/events/liveQuotePreview.ts) |                                |
| `sales.opportunity.fetch.many`                  | Fetch multiple opportunities (paginated/searchable), count, or opportunity types                                                                             | [src/api/sales/opportunities/fetchAll.ts](src/api/sales/opportunities/fetchAll.ts), [src/api/sales/opportunities/count.ts](src/api/sales/opportunities/count.ts), [src/api/sales/opportunities/fetchTypes.ts](src/api/sales/opportunities/fetchTypes.ts)                                                                                                                                                                                                                                                                                                                                                                       |                                |
| `sales.opportunity.fetch.@me`                   | View the personal sales dashboard showing opportunities assigned to the current user                                                                         | UI-only (client-side gate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |                                |
| `sales.opportunity.fetch.all`                   | View all opportunities across all users (All Opportunities tab and View All button in the sales dashboard)                                                   | UI-only (client-side gate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch.many` |
| `sales.opportunity.metrics.all`                 | Allow `scope=all` on sales opportunity metrics endpoint to read cached metrics for all active members                                                        | [src/api/sales/opportunities/metrics.ts](src/api/sales/opportunities/metrics.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `sales.opportunity.fetch.many` |
| `sales.opportunity.metrics.identifier.override` | Allow `identifier=<cwIdentifier>` override on sales opportunity metrics endpoint for querying another member                                                 | [src/api/sales/opportunities/metrics.ts](src/api/sales/opportunities/metrics.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `sales.opportunity.fetch.many` |
| `sales.opportunity.refresh`                     | Refresh a single opportunity's local data from ConnectWise                                                                                                   | [src/api/sales/opportunities/[id]/refresh.ts](src/api/sales/opportunities/[id]/refresh.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.update`                      | Update an opportunity's fields (rating, sales rep, company, contact, site, description, etc.) in ConnectWise                                                 | [src/api/sales/opportunities/[id]/update.ts](src/api/sales/opportunities/[id]/update.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `sales.opportunity.fetch`      |
| `sales.opportunity.create`                      | Create a new opportunity in ConnectWise                                                                                                                      | [src/api/sales/opportunities/create.ts](src/api/sales/opportunities/create.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |                                |
| `sales.opportunity.delete`                      | Delete an opportunity from ConnectWise and the local database                                                                                                | [src/api/sales/opportunities/[id]/delete.ts](src/api/sales/opportunities/[id]/delete.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `sales.opportunity.fetch`      |
| `sales.opportunity.note.create`                 | Create a new note on an opportunity                                                                                                                          | [src/api/sales/opportunities/[id]/notes/create.ts](src/api/sales/opportunities/[id]/notes/create.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `sales.opportunity.fetch`      |
| `sales.opportunity.note.update`                 | Update an existing note on an opportunity                                                                                                                    | [src/api/sales/opportunities/[id]/notes/update.ts](src/api/sales/opportunities/[id]/notes/update.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `sales.opportunity.fetch`      |
| `sales.opportunity.note.delete`                 | Delete a note from an opportunity                                                                                                                            | [src/api/sales/opportunities/[id]/notes/delete.ts](src/api/sales/opportunities/[id]/notes/delete.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `sales.opportunity.fetch`      |
| `sales.opportunity.product.update`              | Update products (forecast items) on an opportunity, including resequencing                                                                                   | [src/api/sales/opportunities/[id]/products/resequence.ts](src/api/sales/opportunities/[id]/products/resequence.ts), [src/api/sales/opportunities/[id]/products/update.ts](src/api/sales/opportunities/[id]/products/update.ts), [src/api/sales/opportunities/[id]/products/cancel.ts](src/api/sales/opportunities/[id]/products/cancel.ts)                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.product.delete`              | Delete a product (forecast item) from an opportunity                                                                                                         | [src/api/sales/opportunities/[id]/products/delete.ts](src/api/sales/opportunities/[id]/products/delete.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.product.add`                 | Add a new product (forecast item) to an opportunity. Individual fields gated by `sales.opportunity.product.field.<field>` permissions.                       | [src/api/sales/opportunities/[id]/products/add.ts](src/api/sales/opportunities/[id]/products/add.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `sales.opportunity.fetch`      |
| `sales.opportunity.product.add.specialOrder`    | Add one or more "SPECIAL ORDER" products via the dedicated special-order route.                                                                              | [src/api/sales/opportunities/[id]/products/addSpecialOrder.ts](src/api/sales/opportunities/[id]/products/addSpecialOrder.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `sales.opportunity.fetch`      |
| `sales.opportunity.product.add.labor`           | Add labor products via the dedicated labor route with Field/Tech catalog selection and labor pricing inputs.                                                 | [src/api/sales/opportunities/[id]/products/addLabor.ts](src/api/sales/opportunities/[id]/products/addLabor.ts), [src/api/sales/opportunities/[id]/products/laborOptions.ts](src/api/sales/opportunities/[id]/products/laborOptions.ts)                                                                                                                                                                                                                                                                                                                                                                                         | `sales.opportunity.fetch`      |
| `sales.opportunity.quote.fetch`                 | Fetch all committed quotes for an opportunity.                                                                                                               | [src/api/sales/opportunities/[id]/quotes/fetchAll.ts](src/api/sales/opportunities/[id]/quotes/fetchAll.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.quote.commit`                | Generate and store a finalized quote PDF for an opportunity with regeneration metadata and creator attribution.                                              | [src/api/sales/opportunities/[id]/quotes/commit.ts](src/api/sales/opportunities/[id]/quotes/commit.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `sales.opportunity.fetch`      |
| `sales.opportunity.quote.commit.backgenerate`   | Generate a quote on an opportunity that is in a workflow state other than New or Active (e.g. PendingWon, QuoteSent). Without this permission, quote generation is restricted to the New and Active states only. | [src/api/sales/opportunities/[id]/quotes/commit.ts](src/api/sales/opportunities/[id]/quotes/commit.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `sales.opportunity.quote.commit` |
| `sales.opportunity.quote.preview`               | Generate a preview-stamped quote PDF for an opportunity without storing it.                                                                                  | [src/api/sales/opportunities/[id]/quotes/preview.ts](src/api/sales/opportunities/[id]/quotes/preview.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `sales.opportunity.fetch`      |
| `sales.opportunity.quote.download`              | Download a committed quote PDF. Each download is recorded with timestamp and user info.                                                                      | [src/api/sales/opportunities/[id]/quotes/download.ts](src/api/sales/opportunities/[id]/quotes/download.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.quote.fetch_downloads`       | Fetch download/print history for all quotes on an opportunity. Admin-level permission.                                                                       | [src/api/sales/opportunities/[id]/quotes/fetchDownloads.ts](src/api/sales/opportunities/[id]/quotes/fetchDownloads.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `sales.opportunity.fetch`      |
| `sales.opportunity.view_margin`                 | View margin and markup data on opportunity products. Controls visibility of margin %, markup %, and related progress bars in the UI.                         | UI-only (client-side gate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.view_cost`                   | View cost data on opportunity products. Controls visibility of unit cost, total cost, and recurring cost in the UI.                                          | UI-only (client-side gate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.view_profit`                 | View profit data on opportunity products. Controls visibility of profit values in the UI.                                                                    | UI-only (client-side gate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `sales.opportunity.fetch`      |
| `sales.opportunity.workflow`                    | Execute opportunity workflow actions (status transitions, review decisions, quote sending, etc.). Base gate for the workflow dispatch endpoint.              | [dispatch.ts](src/api/sales/opportunities/[id]/workflow/dispatch.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `sales.opportunity.fetch`      |
| `sales.opportunity.finalize`                    | Finalize an opportunity as Won or Lost. Without this permission, win/lose actions route to PendingWon/PendingLost instead.                                   | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts), [dispatch.ts](src/api/sales/opportunities/[id]/workflow/dispatch.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `sales.opportunity.workflow`   |
| `sales.opportunity.cancel`                      | Cancel an opportunity. Required to transition any eligible opportunity to the Canceled status.                                                               | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts), [dispatch.ts](src/api/sales/opportunities/[id]/workflow/dispatch.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `sales.opportunity.workflow`   |
| `sales.opportunity.review`                      | Submit an opportunity for internal review. Required to transition an opportunity into the InternalReview status.                                             | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `sales.opportunity.workflow`   |
| `sales.opportunity.send`                        | Send a quote to the customer. Required to transition an opportunity to QuoteSent (and compound transitions like immediate won/lost/confirmed).               | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `sales.opportunity.workflow`   |
| `sales.opportunity.reopen`                      | Re-open a cancelled opportunity. Required to transition an opportunity from Canceled back to Active.                                                         | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `sales.opportunity.workflow`   |
| `sales.opportunity.win`                         | Mark an opportunity as won (or pending won). Gates the win button in the UI. Required for finalize(won), sendQuote(won), and transitionToPending(won).       | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `sales.opportunity.workflow`   |
| `sales.opportunity.lose`                        | Mark an opportunity as lost (or pending lost). Gates the lose button in the UI. Required for finalize(lost), sendQuote(lost), and transitionToPending(lost). | [src/workflows/wf.opportunity.ts](src/workflows/wf.opportunity.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `sales.opportunity.workflow`   |
| `sales.isRepresentative`                        | Designates the user as a sales representative; used for reporting and filtering purposes.                                                                    | _(not yet used in routes)_                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |                                |

<details>
<summary><strong>Field-level permissions for <code>sales.opportunity.product.add</code></strong></summary>

Each submitted field is gated by a `sales.opportunity.product.field.<field>` permission node. Only fields the user has permission for are forwarded to ConnectWise.

| Field Permission Node                                 | Description                                              |
| ----------------------------------------------------- | -------------------------------------------------------- |
| `sales.opportunity.product.field.catalogItem`         | Set the catalog item reference                           |
| `sales.opportunity.product.field.forecastDescription` | Set the forecast description                             |
| `sales.opportunity.product.field.productDescription`  | Set the product description                              |
| `sales.opportunity.product.field.quantity`            | Set the quantity                                         |
| `sales.opportunity.product.field.status`              | Set the status reference                                 |
| `sales.opportunity.product.field.productClass`        | Set the product class (e.g. Product, Service, Agreement) |
| `sales.opportunity.product.field.forecastType`        | Set the forecast type                                    |
| `sales.opportunity.product.field.revenue`             | Set the revenue amount                                   |
| `sales.opportunity.product.field.cost`                | Set the cost amount                                      |
| `sales.opportunity.product.field.includeFlag`         | Set the include flag                                     |
| `sales.opportunity.product.field.linkFlag`            | Set the link flag                                        |
| `sales.opportunity.product.field.recurringFlag`       | Set the recurring flag                                   |
| `sales.opportunity.product.field.taxableFlag`         | Set the taxable flag                                     |
| `sales.opportunity.product.field.recurringRevenue`    | Set the recurring revenue amount                         |
| `sales.opportunity.product.field.recurringCost`       | Set the recurring cost amount                            |
| `sales.opportunity.product.field.cycles`              | Set the number of recurring cycles                       |
| `sales.opportunity.product.field.sequenceNumber`      | Set the sequence number (display order)                  |

</details>

### UniFi Permissions

Permissions for accessing and managing UniFi network infrastructure. The `unifi.access` permission is a gate permission required for **all** UniFi routes.

| Permission Node                | Description                                                                       | Used In                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Dependencies                                              |
| ------------------------------ | --------------------------------------------------------------------------------- |   | --------------------------------------------------------- |
| `unifi.access`                 | Gate permission for the entire UniFi API — required for all                       | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts), [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts), [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts), [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts), [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts), [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts), [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts), [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts), [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts), [src/api/companies/[id]/unifiSites.ts](src/api/companies/[id]/unifiSites.ts), [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts) |                                                           |
| `unifi.sites.create`           | Create a new site on the UniFi controller                                         | [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `unifi.access`                                            |
| `unifi.sites.fetch`            | Fetch a single UniFi site                                                         | [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `unifi.access`                                            |
| `unifi.sites.fetch.many`       | Fetch all UniFi sites                                                             | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `unifi.access`                                            |
| `unifi.sites.sync`             | Sync sites from the UniFi controller into the database                            | [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `unifi.access`                                            |
| `unifi.sites.link`             | Link or unlink a UniFi site to/from a company                                     | [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `unifi.access`                                            |
| `unifi.site.overview`          | View live site overview from the UniFi controller                                 | [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `unifi.access`                                            |
| `unifi.site.devices`           | View live device list from the UniFi controller                                   | [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `unifi.access`                                            |
| `unifi.site.wifi`              | View WiFi networks (WLANs) from the UniFi controller                              | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `unifi.access`                                            |
| `unifi.site.wifi.read`         | Field-level gate for WiFi response data (see note below)                          | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `unifi.access`, `unifi.site.wifi`                         |
| `unifi.site.wifi.read.<field>` | Read a specific field from WiFi response (e.g. `unifi.site.wifi.read.passphrase`) | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.read` |
| `unifi.site.wifi.update`       | Update a WiFi network on the UniFi controller                                     | [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `unifi.access`, `unifi.site.wifi`                         |

#### Field-Level Permission Gating (`unifi.site.wifi.read`)

The WiFi fetch route uses `processObjectValuePerms` to filter each WLAN object on a per-field basis. For every key on the `WlanConf` response object, the system checks `unifi.site.wifi.read.<key>`. Only fields the user has permission for are included in the response. Use `unifi.site.wifi.read.*` to grant access to all fields.

**Available field-level nodes:**

`unifi.site.wifi.read.id`, `unifi.site.wifi.read.name`, `unifi.site.wifi.read.siteId`, `unifi.site.wifi.read.enabled`, `unifi.site.wifi.read.security`, `unifi.site.wifi.read.wpaMode`, `unifi.site.wifi.read.wpaEnc`, `unifi.site.wifi.read.wpa3Support`, `unifi.site.wifi.read.wpa3Transition`, `unifi.site.wifi.read.wpa3FastRoaming`, `unifi.site.wifi.read.wpa3Enhanced192`, `unifi.site.wifi.read.passphrase`, `unifi.site.wifi.read.passphraseAutogenerated`, `unifi.site.wifi.read.hideSSID`, `unifi.site.wifi.read.isGuest`, `unifi.site.wifi.read.band`, `unifi.site.wifi.read.bands`, `unifi.site.wifi.read.networkconfId`, `unifi.site.wifi.read.usergroupId`, `unifi.site.wifi.read.apGroupIds`, `unifi.site.wifi.read.apGroupMode`, `unifi.site.wifi.read.pmfMode`, `unifi.site.wifi.read.groupRekey`, `unifi.site.wifi.read.dtimMode`, `unifi.site.wifi.read.dtimNg`, `unifi.site.wifi.read.dtimNa`, `unifi.site.wifi.read.dtim6e`, `unifi.site.wifi.read.l2Isolation`, `unifi.site.wifi.read.fastRoamingEnabled`, `unifi.site.wifi.read.bssTransition`, `unifi.site.wifi.read.uapsdEnabled`, `unifi.site.wifi.read.iappEnabled`, `unifi.site.wifi.read.proxyArp`, `unifi.site.wifi.read.mcastenhanceEnabled`, `unifi.site.wifi.read.macFilterEnabled`, `unifi.site.wifi.read.macFilterPolicy`, `unifi.site.wifi.read.macFilterList`, `unifi.site.wifi.read.radiusDasEnabled`, `unifi.site.wifi.read.radiusMacAuthEnabled`, `unifi.site.wifi.read.radiusMacaclFormat`, `unifi.site.wifi.read.minrateSettingPreference`, `unifi.site.wifi.read.minrateNgEnabled`, `unifi.site.wifi.read.minrateNgDataRateKbps`, `unifi.site.wifi.read.minrateNgAdvertisingRates`, `unifi.site.wifi.read.minrateNaEnabled`, `unifi.site.wifi.read.minrateNaDataRateKbps`, `unifi.site.wifi.read.minrateNaAdvertisingRates`, `unifi.site.wifi.read.settingPreference`, `unifi.site.wifi.read.no2ghzOui`, `unifi.site.wifi.read.privatePreSharedKeysEnabled`, `unifi.site.wifi.read.privatePreSharedKeys`, `unifi.site.wifi.read.saeGroups`, `unifi.site.wifi.read.saePsk`, `unifi.site.wifi.read.schedule`, `unifi.site.wifi.read.scheduleWithDuration`, `unifi.site.wifi.read.bcFilterList`, `unifi.site.wifi.read.externalId`
| `unifi.site.networks` | View network configurations from the UniFi controller | [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts) | `unifi.access` |
| `unifi.site.wlan-groups` | View WLAN groups (AP broadcasting groups) from the UniFi controller for a site | [src/api/unifi/site/wlanGroups.ts](src/api/unifi/site/wlanGroups.ts), [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access` |
| `unifi.site.wlan-groups.create` | Create a new WLAN group (AP broadcasting group) on the UniFi controller | [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access`, `unifi.site.wlan-groups` |
| `unifi.site.access-points` | View access points (UAPs only) from the UniFi controller for a site | [src/api/unifi/site/accessPoints.ts](src/api/unifi/site/accessPoints.ts) | `unifi.access` |
| `unifi.site.ap-groups` | View AP groups — shows which APs are grouped together for SSID broadcasting | [src/api/unifi/site/apGroups.ts](src/api/unifi/site/apGroups.ts) | `unifi.access` |
| `unifi.site.wifi-limits` | View WiFi SSID limits per AP per radio band | [src/api/unifi/site/wifiLimits.ts](src/api/unifi/site/wifiLimits.ts) | `unifi.access` |
| `unifi.site.speed-profiles` | View speed limit profiles (user groups) from the UniFi controller | [src/api/unifi/site/speedProfilesFetchAll.ts](src/api/unifi/site/speedProfilesFetchAll.ts), [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access` |
| `unifi.site.speed-profiles.create` | Create a new speed limit profile (user group) on the UniFi controller | [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access`, `unifi.site.speed-profiles` |
| `unifi.site.wifi.ppsk` | View private pre-shared keys (PPSKs) for a specific WiFi network | [src/api/unifi/site/wifi/ppskFetchAll.ts](src/api/unifi/site/wifi/ppskFetchAll.ts), [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi` |
| `unifi.site.wifi.ppsk.create` | Create a private pre-shared key on a specific WiFi network | [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.ppsk` |

---

## Object Type Permissions (Field-Level Gating)

All fetch and fetchAll routes gate response object keys using `processObjectValuePerms`. For each object type, only fields whose corresponding `<scope>.<field>` permission the user holds are included in the response. Grant `<scope>.*` to allow all fields on that object type.

### Company (`obj.company`)

| Field Permission            | Description                               |
| --------------------------- | ----------------------------------------- |
| `obj.company.id`            | View company ID                           |
| `obj.company.name`          | View company name                         |
| `obj.company.cw_Identifier` | View ConnectWise identifier               |
| `obj.company.cw_CompanyId`  | View ConnectWise company ID               |
| `obj.company.cw_Data`       | View ConnectWise data (address, contacts) |
| `obj.company.createdAt`     | View creation timestamp                   |
| `obj.company.updatedAt`     | View last-updated timestamp               |

**Used in:** [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts), [src/api/companies/fetchAll.ts](src/api/companies/fetchAll.ts)

### Credential (`obj.credential`)

| Field Permission                   | Description                   |
| ---------------------------------- | ----------------------------- |
| `obj.credential.id`                | View credential ID            |
| `obj.credential.name`              | View credential name          |
| `obj.credential.notes`             | View credential notes         |
| `obj.credential.typeId`            | View credential type ID       |
| `obj.credential.companyId`         | View linked company ID        |
| `obj.credential.subCredentialOfId` | View parent credential ID     |
| `obj.credential.fields`            | View credential field values  |
| `obj.credential.type`              | View credential type object   |
| `obj.credential.company`           | View linked company object    |
| `obj.credential.subCredentials`    | View sub-credentials array    |
| `obj.credential.secureFieldIds`    | View secure field identifiers |
| `obj.credential.createdAt`         | View creation timestamp       |
| `obj.credential.updatedAt`         | View last-updated timestamp   |

**Used in:** [src/api/credentials/fetch.ts](src/api/credentials/fetch.ts), [src/api/credentials/fetchByCompany.ts](src/api/credentials/fetchByCompany.ts), [src/api/credentials/fetchSubCredentials.ts](src/api/credentials/fetchSubCredentials.ts), [src/api/credential-types/fetchCredentials.ts](src/api/credential-types/fetchCredentials.ts)

### Credential Type (`obj.credentialType`)

| Field Permission                     | Description                               |
| ------------------------------------ | ----------------------------------------- |
| `obj.credentialType.id`              | View credential type ID                   |
| `obj.credentialType.name`            | View credential type name                 |
| `obj.credentialType.permissionScope` | View permission scope                     |
| `obj.credentialType.icon`            | View icon                                 |
| `obj.credentialType.fields`          | View field definitions                    |
| `obj.credentialType.credentialCount` | View count of credentials using this type |
| `obj.credentialType.createdAt`       | View creation timestamp                   |
| `obj.credentialType.updatedAt`       | View last-updated timestamp               |

**Used in:** [src/api/credential-types/fetch.ts](src/api/credential-types/fetch.ts), [src/api/credential-types/fetchAll.ts](src/api/credential-types/fetchAll.ts)

### User (`obj.user`)

| Field Permission       | Description                      |
| ---------------------- | -------------------------------- |
| `obj.user.id`          | View user ID                     |
| `obj.user.name`        | View user display name           |
| `obj.user.roles`       | View assigned role monikers      |
| `obj.user.permissions` | View aggregated permission nodes |
| `obj.user.login`       | View login identifier            |
| `obj.user.email`       | View email address               |
| `obj.user.image`       | View profile image URL           |
| `obj.user.createdAt`   | View creation timestamp          |
| `obj.user.updatedAt`   | View last-updated timestamp      |

**Used in:** [src/api/user/@me/fetch.ts](src/api/user/@me/fetch.ts), [src/api/user/fetch.ts](src/api/user/fetch.ts), [src/api/user/fetchAll.ts](src/api/user/fetchAll.ts), [src/api/roles/getUsers.ts](src/api/roles/getUsers.ts)

### Role (`obj.role`)

| Field Permission       | Description                      |
| ---------------------- | -------------------------------- |
| `obj.role.id`          | View role ID                     |
| `obj.role.title`       | View role title                  |
| `obj.role.moniker`     | View role moniker                |
| `obj.role.permissions` | View role permission nodes       |
| `obj.role.users`       | View users assigned to this role |
| `obj.role.createdAt`   | View creation timestamp          |
| `obj.role.updatedAt`   | View last-updated timestamp      |

**Used in:** [src/api/roles/fetch.ts](src/api/roles/fetch.ts), [src/api/roles/fetchAll.ts](src/api/roles/fetchAll.ts), [src/api/user/fetchRoles.ts](src/api/user/fetchRoles.ts)

### Catalog Item (`obj.catalogItem`)

| Field Permission                      | Description                      |
| ------------------------------------- | -------------------------------- |
| `obj.catalogItem.id`                  | View catalog item ID             |
| `obj.catalogItem.cwCatalogId`         | View ConnectWise catalog ID      |
| `obj.catalogItem.identifier`          | View item identifier             |
| `obj.catalogItem.name`                | View item name                   |
| `obj.catalogItem.description`         | View description                 |
| `obj.catalogItem.customerDescription` | View customer-facing description |
| `obj.catalogItem.internalNotes`       | View internal notes              |
| `obj.catalogItem.manufacturer`        | View manufacturer name           |
| `obj.catalogItem.manufactureCwId`     | View manufacturer ConnectWise ID |
| `obj.catalogItem.partNumber`          | View part number                 |
| `obj.catalogItem.vendorName`          | View vendor name                 |
| `obj.catalogItem.vendorSku`           | View vendor SKU                  |
| `obj.catalogItem.vendorCwId`          | View vendor ConnectWise ID       |
| `obj.catalogItem.price`               | View price                       |
| `obj.catalogItem.cost`                | View cost                        |
| `obj.catalogItem.inactive`            | View inactive flag               |
| `obj.catalogItem.salesTaxable`        | View sales-taxable flag          |
| `obj.catalogItem.onHand`              | View on-hand inventory count     |
| `obj.catalogItem.cwLastUpdated`       | View CW last-updated timestamp   |
| `obj.catalogItem.linkedItems`         | View linked catalog items        |
| `obj.catalogItem.createdAt`           | View creation timestamp          |
| `obj.catalogItem.updatedAt`           | View last-updated timestamp      |

**Used in:** [src/api/procurement/fetchAll.ts](src/api/procurement/fetchAll.ts), [src/api/procurement/[id]/fetch.ts](src/api/procurement/[id]/fetch.ts), [src/api/procurement/[id]/fetchLinked.ts](src/api/procurement/[id]/fetchLinked.ts)

### Opportunity (`obj.opportunity`)

| Field Permission                     | Description                     |
| ------------------------------------ | ------------------------------- |
| `obj.opportunity.id`                 | View opportunity ID             |
| `obj.opportunity.cwOpportunityId`    | View ConnectWise opportunity ID |
| `obj.opportunity.name`               | View opportunity name           |
| `obj.opportunity.notes`              | View notes                      |
| `obj.opportunity.type`               | View opportunity type           |
| `obj.opportunity.stage`              | View stage                      |
| `obj.opportunity.status`             | View status                     |
| `obj.opportunity.priority`           | View priority                   |
| `obj.opportunity.rating`             | View rating                     |
| `obj.opportunity.source`             | View source                     |
| `obj.opportunity.campaign`           | View campaign                   |
| `obj.opportunity.primarySalesRep`    | View primary sales rep          |
| `obj.opportunity.secondarySalesRep`  | View secondary sales rep        |
| `obj.opportunity.company`            | View company                    |
| `obj.opportunity.contact`            | View contact                    |
| `obj.opportunity.site`               | View site                       |
| `obj.opportunity.customerPO`         | View customer PO                |
| `obj.opportunity.totalSalesTax`      | View total sales tax            |
| `obj.opportunity.probability`        | View probability percentage     |
| `obj.opportunity.location`           | View location                   |
| `obj.opportunity.department`         | View department                 |
| `obj.opportunity.expectedCloseDate`  | View expected close date        |
| `obj.opportunity.pipelineChangeDate` | View pipeline change date       |
| `obj.opportunity.dateBecameLead`     | View date became lead           |
| `obj.opportunity.closedDate`         | View closed date                |
| `obj.opportunity.closedFlag`         | View closed flag                |
| `obj.opportunity.closedBy`           | View closed-by member           |
| `obj.opportunity.companyId`          | View linked company ID          |
| `obj.opportunity.cwLastUpdated`      | View CW last-updated timestamp  |
| `obj.opportunity.createdAt`          | View creation timestamp         |
| `obj.opportunity.updatedAt`          | View last-updated timestamp     |

**Used in:** [src/api/sales/fetchAll.ts](src/api/sales/fetchAll.ts), [src/api/sales/[id]/fetch.ts](src/api/sales/[id]/fetch.ts)

### UniFi Site (`obj.unifiSite`)

| Field Permission          | Description                   |
| ------------------------- | ----------------------------- |
| `obj.unifiSite.id`        | View site internal ID         |
| `obj.unifiSite.name`      | View site name                |
| `obj.unifiSite.siteId`    | View UniFi controller site ID |
| `obj.unifiSite.companyId` | View linked company ID        |
| `obj.unifiSite.company`   | View linked company object    |
| `obj.unifiSite.createdAt` | View creation timestamp       |
| `obj.unifiSite.updatedAt` | View last-updated timestamp   |

**Used in:** [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts), [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts), [src/api/companies/[id]/unifiSites.ts](src/api/companies/[id]/unifiSites.ts)

### WiFi Network (`unifi.site.wifi.read`)

See **UniFi Permissions > Field-Level Permission Gating** above for the full list of `unifi.site.wifi.read.<field>` nodes.

---

## Permission Issuers

Permissions can be issued by different sources:

- `roles` - Permissions granted through role assignment
- `user` - Permissions granted directly to a user
- `api_key` - Permissions associated with an API key

## Permission Validation

The authorization middleware ([src/api/middleware/authorization.ts](src/api/middleware/authorization.ts)) enforces permissions by:

1. Extracting the authorization header (Bearer token or API Key)
2. Validating the session/token
3. Checking if the user has all required permissions for the route
4. Throwing an `InsufficentPermission` error (403) if any required permission is missing

## Usage Example

```typescript
// Require single permission
authMiddleware({ permissions: ["credential.fetch"] })

// Require multiple permissions (all must be satisfied)
authMiddleware({
  permissions: ["credential.fetch", "credential.secure_values.read"]
})

// Administrator role with wildcard permission
{
  moniker: "administrator",
  permissions: ["*"]  // Grants all permissions
}
```

## Notes

- Multiple permissions in a single route require **all** permissions to be satisfied (AND logic)
- The `*` wildcard permission grants access to everything in the application
- Permissions are signed using JWT with a private key for integrity
- Permission validation supports pattern matching for flexible permission management
