461 lines
83 KiB
Markdown
461 lines
83 KiB
Markdown
# 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
|