roles
This commit is contained in:
@@ -70,4 +70,10 @@ Purpose: make AI coding agents immediately productive in this repository by desc
|
|||||||
|
|
||||||
- **When editing generated or infra files**: if you need to change `generated/prisma/*` (rare), explain why in the PR and show commands used to regenerate.
|
- **When editing generated or infra files**: if you need to change `generated/prisma/*` (rare), explain why in the PR and show commands used to regenerate.
|
||||||
|
|
||||||
|
- **Documentation sync (IMPORTANT)**: Whenever you add, remove, or modify API routes or permission nodes, you **must** update all three of the following files to keep them in sync:
|
||||||
|
1. `src/types/PermissionNodes.ts` — the single source of truth for all permission node definitions, categories, descriptions, and `usedIn` references.
|
||||||
|
2. `PERMISSIONS.md` — human-readable documentation of all permission nodes; must strictly reflect the data in `PermissionNodes.ts`.
|
||||||
|
3. `API_ROUTES.md` — comprehensive documentation of all API routes, including method, path, auth requirements, permissions, request/response examples.
|
||||||
|
Always verify that new routes have their required permissions listed in `PermissionNodes.ts`, that `PERMISSIONS.md` tables match the TS file exactly, and that `API_ROUTES.md` includes full documentation for every mounted route. Run through all three files at the end of any route or permission change to catch discrepancies.
|
||||||
|
|
||||||
If anything here is unclear or you'd like more examples (e.g., a walk-through editing a controller + manager + test run), tell me which area to expand and I'll iterate.
|
If anything here is unclear or you'd like more examples (e.g., a walk-through editing a controller + manager + test run), tell me which area to expand and I'll iterate.
|
||||||
|
|||||||
+563
-5
@@ -153,6 +153,52 @@ Update the currently authenticated user's information.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Check User Permissions
|
||||||
|
|
||||||
|
**POST** `/user/@me/check-permission`
|
||||||
|
|
||||||
|
Check if the currently authenticated user has specific permissions. Accepts an array of permission nodes and returns the status for each.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Scopes:** `user.read`
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"permissions": ["user.read", "company.create", "credential.write"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Permission check completed.",
|
||||||
|
"data": {
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"permission": "user.read",
|
||||||
|
"hasPermission": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "company.create",
|
||||||
|
"hasPermission": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "credential.write",
|
||||||
|
"hasPermission": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Company Routes
|
## Company Routes
|
||||||
|
|
||||||
### Get All Companies
|
### Get All Companies
|
||||||
@@ -203,17 +249,24 @@ Fetch a paginated list of all companies with optional search functionality.
|
|||||||
|
|
||||||
**GET** `/company/companies/:identifier`
|
**GET** `/company/companies/:identifier`
|
||||||
|
|
||||||
Fetch a single company by its ID.
|
Fetch a single company by its ID. Automatically fetches fresh data from ConnectWise and returns it along with internal company data.
|
||||||
|
|
||||||
**Authentication Required:** Yes
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
**Required Permissions:** `company.fetch`
|
**Required Permissions:**
|
||||||
|
|
||||||
|
- `company.fetch` (base permission)
|
||||||
|
- `company.fetch.address` (required when `includeAddress=true`)
|
||||||
|
|
||||||
**URL Parameters:**
|
**URL Parameters:**
|
||||||
|
|
||||||
- `identifier` - Company ID
|
- `identifier` - Company ID (internal database ID)
|
||||||
|
|
||||||
**Response:**
|
**Query Parameters:**
|
||||||
|
|
||||||
|
- `includeAddress` (optional) - Set to "true" to include full address information. Requires `company.fetch.address` permission. (default: false)
|
||||||
|
|
||||||
|
**Response (without includeAddress):**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -223,7 +276,34 @@ Fetch a single company by its ID.
|
|||||||
"id": "ckx...",
|
"id": "ckx...",
|
||||||
"name": "Acme Corp",
|
"name": "Acme Corp",
|
||||||
"cw_CompanyId": 12345,
|
"cw_CompanyId": 12345,
|
||||||
"cw_Identifier": "AcmeCorp"
|
"cw_Identifier": "AcmeCorp",
|
||||||
|
"cw_Data": {}
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (with includeAddress=true):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Company Fetched Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"name": "Acme Corp",
|
||||||
|
"cw_CompanyId": 12345,
|
||||||
|
"cw_Identifier": "AcmeCorp",
|
||||||
|
"cw_Data": {
|
||||||
|
"address": {
|
||||||
|
"line1": "123 Main St",
|
||||||
|
"line2": null,
|
||||||
|
"city": "Springfield",
|
||||||
|
"state": "IL",
|
||||||
|
"zip": "62701",
|
||||||
|
"country": "United States"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"successful": true
|
"successful": true
|
||||||
}
|
}
|
||||||
@@ -861,6 +941,484 @@ Fetch all credentials that use a specific credential type.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Role Routes
|
||||||
|
|
||||||
|
### Create Role
|
||||||
|
|
||||||
|
**POST** `/role`
|
||||||
|
|
||||||
|
Create a new role with a title, moniker, and optional permissions.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.create`
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": [
|
||||||
|
"user.read",
|
||||||
|
"user.write",
|
||||||
|
"company.fetch",
|
||||||
|
"credential.create"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 201,
|
||||||
|
"message": "Role Created Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": [
|
||||||
|
"user.read",
|
||||||
|
"user.write",
|
||||||
|
"company.fetch",
|
||||||
|
"credential.create"
|
||||||
|
],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Role by ID or Moniker
|
||||||
|
|
||||||
|
**GET** `/role/:identifier`
|
||||||
|
|
||||||
|
Fetch a single role by its ID or moniker.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Role Fetched Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": [
|
||||||
|
"user.read",
|
||||||
|
"user.write",
|
||||||
|
"company.fetch",
|
||||||
|
"credential.create"
|
||||||
|
],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get All Roles
|
||||||
|
|
||||||
|
**GET** `/role`
|
||||||
|
|
||||||
|
Fetch all roles in the system.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`, `role.list`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Roles Fetched Successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": ["user.read", "user.write"],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cky...",
|
||||||
|
"title": "Viewer",
|
||||||
|
"moniker": "viewer",
|
||||||
|
"permissions": ["user.read", "company.fetch"],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Update Role
|
||||||
|
|
||||||
|
**PATCH** `/role/:identifier`
|
||||||
|
|
||||||
|
Update a role's title, moniker, or permissions.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.modify`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Super Administrator",
|
||||||
|
"moniker": "super_admin",
|
||||||
|
"permissions": ["*"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Role Updated Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "Super Administrator",
|
||||||
|
"moniker": "super_admin",
|
||||||
|
"permissions": ["*"],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Delete Role
|
||||||
|
|
||||||
|
**DELETE** `/role/:identifier`
|
||||||
|
|
||||||
|
Delete a role. This will remove the role from all users that have it assigned.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.delete`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Role Deleted Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T12:00:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Add Permissions to Role
|
||||||
|
|
||||||
|
**POST** `/role/:identifier/permissions`
|
||||||
|
|
||||||
|
Add one or more permissions to an existing role. The new permissions will be merged with existing permissions.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.modify`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"permissions": ["credential.update", "credential.delete"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Permissions Added Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": [
|
||||||
|
"user.read",
|
||||||
|
"user.write",
|
||||||
|
"company.fetch",
|
||||||
|
"credential.create",
|
||||||
|
"credential.update",
|
||||||
|
"credential.delete"
|
||||||
|
],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T12:30:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Remove Permissions from Role
|
||||||
|
|
||||||
|
**DELETE** `/role/:identifier/permissions`
|
||||||
|
|
||||||
|
Remove one or more permissions from an existing role.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.modify`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"permissions": ["credential.delete"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Permissions Removed Successfully!",
|
||||||
|
"data": {
|
||||||
|
"id": "ckx...",
|
||||||
|
"title": "System Administrator",
|
||||||
|
"moniker": "system_admin",
|
||||||
|
"permissions": [
|
||||||
|
"user.read",
|
||||||
|
"user.write",
|
||||||
|
"company.fetch",
|
||||||
|
"credential.create",
|
||||||
|
"credential.update"
|
||||||
|
],
|
||||||
|
"createdAt": "2026-02-17T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-17T12:45:00.000Z"
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Users with Role
|
||||||
|
|
||||||
|
**GET** `/role/:identifier/users`
|
||||||
|
|
||||||
|
Fetch all users that have been assigned a specific role.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`, `user.read`
|
||||||
|
|
||||||
|
**URL Parameters:**
|
||||||
|
|
||||||
|
- `identifier` - Role ID or moniker
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Users Fetched Successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "cku...",
|
||||||
|
"name": "John Doe",
|
||||||
|
"login": "john.doe",
|
||||||
|
"roles": ["ckx..."],
|
||||||
|
"createdAt": "2026-01-15T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-10T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ckv...",
|
||||||
|
"name": "Jane Smith",
|
||||||
|
"login": "jane.smith",
|
||||||
|
"roles": ["ckx...", "cky..."],
|
||||||
|
"createdAt": "2026-01-20T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-02-12T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Permission Routes
|
||||||
|
|
||||||
|
### Get All Permission Nodes (Categorized)
|
||||||
|
|
||||||
|
**GET** `/permissions`
|
||||||
|
|
||||||
|
Fetch all permission nodes organized by category. Returns the full permission node definition object with categories as keys.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Permission Nodes Fetched Successfully!",
|
||||||
|
"data": {
|
||||||
|
"global": {
|
||||||
|
"name": "Global Permissions",
|
||||||
|
"description": "Global wildcard permissions that grant access to all resources",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"node": "*",
|
||||||
|
"description": "Full access to all resources and actions (administrator role)",
|
||||||
|
"usedIn": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"company": { "..." },
|
||||||
|
"credential": { "..." },
|
||||||
|
"...additional categories": { "..." }
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get All Permission Nodes (Flat)
|
||||||
|
|
||||||
|
**GET** `/permissions/nodes`
|
||||||
|
|
||||||
|
Fetch a flat array of all permission nodes across all categories.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "All Permission Nodes Fetched Successfully!",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"node": "*",
|
||||||
|
"description": "Full access to all resources and actions (administrator role)",
|
||||||
|
"usedIn": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "company.fetch",
|
||||||
|
"description": "Fetch a single company",
|
||||||
|
"usedIn": ["src/api/companies/[id]/fetch.ts"]
|
||||||
|
},
|
||||||
|
"...additional nodes"
|
||||||
|
],
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Permission Nodes by Category
|
||||||
|
|
||||||
|
**GET** `/permissions/:category`
|
||||||
|
|
||||||
|
Fetch all permission nodes for a specific category.
|
||||||
|
|
||||||
|
**Authentication Required:** Yes
|
||||||
|
|
||||||
|
**Required Permissions:** `role.read`
|
||||||
|
|
||||||
|
**Path Parameters:**
|
||||||
|
|
||||||
|
- `category` - The category key (e.g., `global`, `company`, `credential`, `credentialType`, `role`, `user`, `permission`, `uiNavigation`, `adminUI`)
|
||||||
|
|
||||||
|
**Response (Success):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 200,
|
||||||
|
"message": "Permission Category Fetched Successfully!",
|
||||||
|
"data": {
|
||||||
|
"name": "Company Permissions",
|
||||||
|
"description": "Permissions for accessing and managing company resources",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"node": "company.fetch",
|
||||||
|
"description": "Fetch a single company",
|
||||||
|
"usedIn": ["src/api/companies/[id]/fetch.ts"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "company.fetch.address",
|
||||||
|
"description": "View company address information",
|
||||||
|
"usedIn": ["src/api/companies/[id]/fetch.ts"],
|
||||||
|
"dependencies": ["company.fetch"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"successful": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (Not Found):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": 404,
|
||||||
|
"message": "Permission category \"invalidCategory\" not found",
|
||||||
|
"error": "NotFound",
|
||||||
|
"successful": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Utility Routes
|
## Utility Routes
|
||||||
|
|
||||||
### Teapot
|
### Teapot
|
||||||
|
|||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
# Permission Nodes
|
||||||
|
|
||||||
|
This document lists all known permission nodes in the ttscm-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.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) |
|
||||||
|
|
||||||
|
### 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) |
|
||||||
|
|
||||||
|
### 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 |
|
||||||
|
| --------------- | ----------------------- | -------------------------------------------------------- |
|
||||||
|
| `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) |
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
meta {
|
||||||
|
name: Check User Permission
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/v1/user/@me/check-permission
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"permissions": ["user.read", "company.create", "credential.write"]
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "ttscm-api",
|
"name": "tts-optima-api",
|
||||||
"homepage": "https://totaltech.net",
|
"homepage": "https://totaltech.net",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Jackson Roberts",
|
"name": "Jackson Roberts",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { companies } from "../../../managers/companies";
|
|||||||
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
import { ContentfulStatusCode } from "hono/utils/http-status";
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
import { authMiddleware } from "../../middleware/authorization";
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
|
||||||
/* /v1/company/companies/[id] */
|
/* /v1/company/companies/[id] */
|
||||||
export default createRoute(
|
export default createRoute(
|
||||||
@@ -12,10 +13,23 @@ export default createRoute(
|
|||||||
|
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const company = await companies.fetch(c.req.param("identifier"));
|
const company = await companies.fetch(c.req.param("identifier"));
|
||||||
|
const includeAddress = c.req.query("includeAddress") === "true";
|
||||||
|
|
||||||
|
// Check for address-specific permission if includeAddress is requested
|
||||||
|
if (includeAddress) {
|
||||||
|
const user = c.get("user");
|
||||||
|
if (!user || !(await user.hasPermission("company.fetch.address"))) {
|
||||||
|
throw new GenericError({
|
||||||
|
name: "InsufficientPermission",
|
||||||
|
message: "You do not have permission to view company addresses.",
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = apiResponse.successful(
|
const response = apiResponse.successful(
|
||||||
"Company Fetched Successfully!",
|
"Company Fetched Successfully!",
|
||||||
company,
|
company.toJson({ includeAddress }),
|
||||||
);
|
);
|
||||||
return c.json(response, response.status as ContentfulStatusCode);
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { companies } from "../../managers/companies";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/company/count */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/count"],
|
||||||
|
async (c) => {
|
||||||
|
const count = await companies.count();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company count fetched successfully!",
|
||||||
|
{
|
||||||
|
count,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["company.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { default as fetchAll } from "./fetchAll";
|
import { default as fetchAll } from "./fetchAll";
|
||||||
import { default as fetch } from "./[id]/fetch";
|
import { default as fetch } from "./[id]/fetch";
|
||||||
import { default as configurations } from "./[id]/configurations";
|
import { default as configurations } from "./[id]/configurations";
|
||||||
|
import { default as count } from "./count";
|
||||||
|
|
||||||
export { configurations, fetch, fetchAll };
|
export { configurations, count, fetch, fetchAll };
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { PERMISSION_NODES } from "../../types/PermissionNodes";
|
||||||
|
|
||||||
|
/* /v1/permissions */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permission Nodes Fetched Successfully!",
|
||||||
|
PERMISSION_NODES,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { PERMISSION_NODES } from "../../types/PermissionNodes";
|
||||||
|
import GenericError from "../../Errors/GenericError";
|
||||||
|
|
||||||
|
/* /v1/permissions/:category */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:category"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const categoryKey = c.req.param(
|
||||||
|
"category",
|
||||||
|
) as keyof typeof PERMISSION_NODES;
|
||||||
|
|
||||||
|
if (!(categoryKey in PERMISSION_NODES)) {
|
||||||
|
throw new GenericError({
|
||||||
|
name: "NotFound",
|
||||||
|
message: `Permission category "${categoryKey}" not found`,
|
||||||
|
status: 404,
|
||||||
|
cause: `Valid categories: ${Object.keys(PERMISSION_NODES).join(", ")}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permission Category Fetched Successfully!",
|
||||||
|
PERMISSION_NODES[categoryKey],
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { getAllPermissionNodes } from "../../types/PermissionNodes";
|
||||||
|
|
||||||
|
/* /v1/permissions/nodes */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/nodes"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const allNodes = getAllPermissionNodes();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"All Permission Nodes Fetched Successfully!",
|
||||||
|
allNodes,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as fetchByCategory } from "./fetchByCategory";
|
||||||
|
import { default as fetchNodes } from "./fetchNodes";
|
||||||
|
|
||||||
|
export { fetchAll, fetchByCategory, fetchNodes };
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/role/:identifier/permissions */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/:identifier/permissions"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
permissions: z
|
||||||
|
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||||
|
.min(1, "At least one permission is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
await role.addPermissions(...data.permissions);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permissions Added Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.update"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/role */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
title: z.string().min(1, "Title is required"),
|
||||||
|
moniker: z.string().min(1, "Moniker is required"),
|
||||||
|
permissions: z
|
||||||
|
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const role = await roles.create(data);
|
||||||
|
|
||||||
|
const response = apiResponse.created(
|
||||||
|
"Role Created Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.create"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* DELETE /v1/role/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"delete",
|
||||||
|
["/:identifier"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
await role.delete();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Role Deleted Successfully!",
|
||||||
|
role.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.delete"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/role/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:identifier"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Role Fetched Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/role */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const allRoles = await roles.fetchAllRoles();
|
||||||
|
|
||||||
|
const rolesArray = allRoles.map((role) =>
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Roles Fetched Successfully!",
|
||||||
|
rolesArray,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read", "role.list"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/role/:identifier/users */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:identifier/users"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
const users = role.getUsers();
|
||||||
|
|
||||||
|
const usersArray = users.map((user) => user.toJson());
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Users Fetched Successfully!",
|
||||||
|
usersArray,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read", "user.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export { default as create } from "./create";
|
||||||
|
export { default as fetch } from "./fetch";
|
||||||
|
export { default as fetchAll } from "./fetchAll";
|
||||||
|
export { default as update } from "./update";
|
||||||
|
export { default as deleteRole } from "./delete";
|
||||||
|
export { default as addPermissions } from "./addPermissions";
|
||||||
|
export { default as removePermissions } from "./removePermissions";
|
||||||
|
export { default as getUsers } from "./getUsers";
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* DELETE /v1/role/:identifier/permissions */
|
||||||
|
export default createRoute(
|
||||||
|
"delete",
|
||||||
|
["/:identifier/permissions"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
permissions: z
|
||||||
|
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||||
|
.min(1, "At least one permission is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
await role.removePermissions(...data.permissions);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permissions Removed Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.update"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* PATCH /v1/role/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"patch",
|
||||||
|
["/:identifier"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z
|
||||||
|
.object({
|
||||||
|
title: z.string().min(1, "Title cannot be empty"),
|
||||||
|
moniker: z.string().min(1, "Moniker cannot be empty"),
|
||||||
|
permissions: z.array(
|
||||||
|
z.string().min(1, "Permission node cannot be empty"),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
await role.update(data);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Role Updated Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.update"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import * as permissionRoutes from "../permissions";
|
||||||
|
|
||||||
|
const permissionRouter = new Hono();
|
||||||
|
Object.values(permissionRoutes).map((r) => permissionRouter.route("/", r));
|
||||||
|
|
||||||
|
export default permissionRouter;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Hono } from "hono";
|
||||||
|
import * as roleRoutes from "../roles";
|
||||||
|
|
||||||
|
const roleRouter = new Hono();
|
||||||
|
Object.values(roleRoutes).map((r) => roleRouter.route("/", r));
|
||||||
|
|
||||||
|
export default roleRouter;
|
||||||
@@ -52,6 +52,8 @@ v1.route("/user", require("./routers/user").default);
|
|||||||
v1.route("/company", require("./routers/companyRouter").default);
|
v1.route("/company", require("./routers/companyRouter").default);
|
||||||
v1.route("/credential", require("./routers/credentialRouter").default);
|
v1.route("/credential", require("./routers/credentialRouter").default);
|
||||||
v1.route("/credential-type", require("./routers/credentialTypeRouter").default);
|
v1.route("/credential-type", require("./routers/credentialTypeRouter").default);
|
||||||
|
v1.route("/role", require("./routers/roleRouter").default);
|
||||||
|
v1.route("/permissions", require("./routers/permissionRouter").default);
|
||||||
app.route("/v1", v1);
|
app.route("/v1", v1);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
const checkPermissionSchema = z.object({
|
||||||
|
permissions: z
|
||||||
|
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||||
|
.min(1, "At least one permission is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// /v1/user/@me/check-permission
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/@me/check-permission"],
|
||||||
|
async (c) => {
|
||||||
|
const user = c.get("user");
|
||||||
|
|
||||||
|
const body = await c.req.json();
|
||||||
|
const { permissions } = checkPermissionSchema.parse(body);
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
permissions.map(async (permission) => ({
|
||||||
|
permission,
|
||||||
|
hasPermission: await user.hasPermission(permission),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = apiResponse.successful("Permission check completed.", {
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ scopes: ["user.read"] }),
|
||||||
|
);
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as fetch } from "./fetch";
|
export { default as fetch } from "./fetch";
|
||||||
export { default as update } from "./update";
|
export { default as update } from "./update";
|
||||||
|
export { default as checkPermission } from "./checkPermission";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Company } from "../../generated/prisma/client";
|
import { Company } from "../../generated/prisma/client";
|
||||||
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
|
import { fetchCwCompanyById } from "../modules/cw-utils/fetchCompany";
|
||||||
import { fetchCompanyConfigurations } from "../modules/cw-utils/fetchCompanyConfigurations";
|
import { fetchCompanyConfigurations } from "../modules/cw-utils/configurations/fetchCompanyConfigurations";
|
||||||
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
|
import { updateCwInternalCompany } from "../modules/cw-utils/updateCompany";
|
||||||
import { Company as CWCompany } from "../types/ConnectWiseTypes";
|
import { Company as CWCompany } from "../types/ConnectWiseTypes";
|
||||||
|
|
||||||
@@ -16,12 +16,14 @@ export class CompanyController {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public readonly cw_Identifier: string;
|
public readonly cw_Identifier: string;
|
||||||
public readonly cw_CompanyId: number;
|
public readonly cw_CompanyId: number;
|
||||||
|
public readonly cw_Data?: CWCompany;
|
||||||
|
|
||||||
constructor(companyData: Company) {
|
constructor(companyData: Company, cwData?: CWCompany) {
|
||||||
this.id = companyData.id;
|
this.id = companyData.id;
|
||||||
this.name = companyData.name;
|
this.name = companyData.name;
|
||||||
this.cw_Identifier = companyData.cw_Identifier;
|
this.cw_Identifier = companyData.cw_Identifier;
|
||||||
this.cw_CompanyId = companyData.cw_CompanyId;
|
this.cw_CompanyId = companyData.cw_CompanyId;
|
||||||
|
this.cw_Data = cwData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,12 +67,22 @@ export class CompanyController {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJson() {
|
public toJson(opts?: { includeAddress: boolean }) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
cw_Identifier: this.cw_Identifier,
|
cw_Identifier: this.cw_Identifier,
|
||||||
cw_CompanyId: this.cw_CompanyId,
|
cw_CompanyId: this.cw_CompanyId,
|
||||||
|
cw_Data: {
|
||||||
|
address: {
|
||||||
|
line1: this.cw_Data?.addressLine1,
|
||||||
|
line2: this.cw_Data?.addressLine2 ?? null,
|
||||||
|
city: this.cw_Data?.city,
|
||||||
|
state: this.cw_Data?.state,
|
||||||
|
zip: this.cw_Data?.zip,
|
||||||
|
country: this.cw_Data?.country.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ export class RoleController {
|
|||||||
where: { moniker: data.moniker },
|
where: { moniker: data.moniker },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (checkMoniker)
|
if (checkMoniker && checkMoniker.moniker !== this.moniker)
|
||||||
throw new RoleError(
|
throw new RoleError(
|
||||||
"Moniker is already taken.",
|
"Moniker is already taken.",
|
||||||
"Another role with this moniker already exists in the databse.",
|
"Another role with this moniker already exists in the databse.",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from "../constants";
|
import { connectWiseApi, prisma } from "../constants";
|
||||||
import { CompanyController } from "../controllers/CompanyController";
|
import { CompanyController } from "../controllers/CompanyController";
|
||||||
|
import { Company } from "../types/ConnectWiseTypes";
|
||||||
|
|
||||||
export const companies = {
|
export const companies = {
|
||||||
async fetch(identifier: string | number): Promise<CompanyController> {
|
async fetch(identifier: string | number): Promise<CompanyController> {
|
||||||
@@ -11,7 +12,10 @@ export const companies = {
|
|||||||
|
|
||||||
if (!search) throw new Error("Unknown company.");
|
if (!search) throw new Error("Unknown company.");
|
||||||
|
|
||||||
return new CompanyController(search);
|
const freshCwData = await connectWiseApi.get(
|
||||||
|
`/company/companies/${search.cw_CompanyId}`,
|
||||||
|
);
|
||||||
|
return new CompanyController(search, freshCwData.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async count() {
|
async count() {
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
import { connectWiseApi } from "../../constants";
|
import { connectWiseApi } from "../../../constants";
|
||||||
import { ConfigurationResponse } from "../../types/ConnectWiseTypes";
|
import { ConfigurationResponse } from "../../../types/ConnectWiseTypes";
|
||||||
import {
|
import {
|
||||||
processConfigurationResponse,
|
processConfigurationResponse,
|
||||||
ProcessedConfiguration,
|
ProcessedConfiguration,
|
||||||
} from "./processConfigurationResponse";
|
} from "./processConfigurationResponse";
|
||||||
import GenericError from "../../Errors/GenericError";
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
|
||||||
export const fetchCompanyConfigurations = async (
|
export const fetchCompanyConfigurations = async (
|
||||||
cwCompanyId: number,
|
cwCompanyId: number,
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ConfigurationResponse } from "../../../types/ConnectWiseTypes";
|
||||||
|
|
||||||
|
export type ProcessedConfiguration = ReturnType<
|
||||||
|
typeof processConfigurationResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const processConfigurationResponse = (c: ConfigurationResponse) => {
|
||||||
|
return c.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
active: item.activeFlag,
|
||||||
|
serialNumber: item.serialNumber,
|
||||||
|
type: item.type,
|
||||||
|
notes: item.notes,
|
||||||
|
status: {
|
||||||
|
id: item.status.id,
|
||||||
|
name: item.status.name,
|
||||||
|
},
|
||||||
|
questions: !item.questions
|
||||||
|
? null
|
||||||
|
: item.questions.map((q) => ({
|
||||||
|
id: q.questionId,
|
||||||
|
question: q.question,
|
||||||
|
answer: q.answer,
|
||||||
|
fieldType: q.fieldType,
|
||||||
|
})),
|
||||||
|
info: item._info,
|
||||||
|
}));
|
||||||
|
};
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ConfigurationResponse } from "../../types/ConnectWiseTypes";
|
|
||||||
|
|
||||||
export type ProcessedConfiguration = ReturnType<
|
|
||||||
typeof processConfigurationResponse
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const processConfigurationResponse = (c: ConfigurationResponse) => {
|
|
||||||
return c.map((item) => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.name,
|
|
||||||
active: item.activeFlag,
|
|
||||||
serialNumber: item.serialNumber,
|
|
||||||
type: item.type,
|
|
||||||
questions: item.questions.map((q) => ({
|
|
||||||
id: q.questionId,
|
|
||||||
question: q.question,
|
|
||||||
answer: q.answer,
|
|
||||||
fieldType: q.fieldType,
|
|
||||||
})),
|
|
||||||
info: item._info,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
@@ -4,9 +4,11 @@ export interface Company {
|
|||||||
name: string;
|
name: string;
|
||||||
status: CompanyStatus;
|
status: CompanyStatus;
|
||||||
addressLine1: string;
|
addressLine1: string;
|
||||||
|
addressLine2?: string;
|
||||||
city: string;
|
city: string;
|
||||||
state: string;
|
state: string;
|
||||||
zip: string;
|
zip: string;
|
||||||
|
country: BasicEntity;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
faxNumber: string;
|
faxNumber: string;
|
||||||
website: string;
|
website: string;
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
/**
|
||||||
|
* Permission Nodes - Centralized definition of all permission nodes
|
||||||
|
* organized by category with descriptions and usage information.
|
||||||
|
*
|
||||||
|
* Format: resource.action[.modifier]
|
||||||
|
* Special tokens: * (matches all), ? (single char wildcard), [a,b,c] (inclusive), <a,b,c> (exclusive)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface PermissionNode {
|
||||||
|
/** The permission node identifier (e.g., "company.fetch") */
|
||||||
|
node: string;
|
||||||
|
/** Description of what this permission allows */
|
||||||
|
description: string;
|
||||||
|
/** File paths where this permission is used */
|
||||||
|
usedIn: string[];
|
||||||
|
/** Dependencies - other permissions that must be granted alongside this one */
|
||||||
|
dependencies?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PermissionCategory {
|
||||||
|
/** Category name */
|
||||||
|
name: string;
|
||||||
|
/** Category description */
|
||||||
|
description: string;
|
||||||
|
/** Permission nodes in this category */
|
||||||
|
permissions: PermissionNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PERMISSION_NODES = {
|
||||||
|
global: {
|
||||||
|
name: "Global Permissions",
|
||||||
|
description:
|
||||||
|
"Global wildcard permissions that grant access to all resources",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "*",
|
||||||
|
description:
|
||||||
|
"Full access to all resources and actions (administrator role)",
|
||||||
|
usedIn: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
company: {
|
||||||
|
name: "Company Permissions",
|
||||||
|
description: "Permissions for accessing and managing company resources",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "company.fetch",
|
||||||
|
description: "Fetch a single company",
|
||||||
|
usedIn: ["src/api/companies/[id]/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "company.fetch.address",
|
||||||
|
description: "View company address information",
|
||||||
|
usedIn: ["src/api/companies/[id]/fetch.ts"],
|
||||||
|
dependencies: ["company.fetch"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "company.fetch.many",
|
||||||
|
description: "Fetch multiple companies",
|
||||||
|
usedIn: ["src/api/companies/fetchAll.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "company.fetch.configurations",
|
||||||
|
description: "Fetch company configurations",
|
||||||
|
usedIn: ["src/api/companies/[id]/configurations.ts"],
|
||||||
|
dependencies: ["company.fetch"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
credential: {
|
||||||
|
name: "Credential Permissions",
|
||||||
|
description: "Permissions for managing credentials and their fields",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "credential.create",
|
||||||
|
description: "Create a new credential",
|
||||||
|
usedIn: ["src/api/credentials/create.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.fetch",
|
||||||
|
description: "Fetch a single credential",
|
||||||
|
usedIn: ["src/api/credentials/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.fetch.many",
|
||||||
|
description: "Fetch multiple credentials",
|
||||||
|
usedIn: ["src/api/credentials/fetchByCompany.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.update",
|
||||||
|
description: "Update a credential",
|
||||||
|
usedIn: ["src/api/credentials/update.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.delete",
|
||||||
|
description: "Delete a credential",
|
||||||
|
usedIn: ["src/api/credentials/delete.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.fields.fetch",
|
||||||
|
description: "Fetch credential fields",
|
||||||
|
usedIn: ["src/api/credentials/fetchFields.ts"],
|
||||||
|
dependencies: ["credential.fetch"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.fields.update",
|
||||||
|
description: "Update credential fields",
|
||||||
|
usedIn: ["src/api/credentials/updateFields.ts"],
|
||||||
|
dependencies: ["credential.update"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential.secure_values.read",
|
||||||
|
description: "Read secure values of a credential",
|
||||||
|
usedIn: ["src/api/credentials/readSecureValues.ts"],
|
||||||
|
dependencies: ["credential.fetch"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
credentialType: {
|
||||||
|
name: "Credential Type Permissions",
|
||||||
|
description: "Permissions for managing credential types and definitions",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "credential_type.create",
|
||||||
|
description: "Create a new credential type",
|
||||||
|
usedIn: ["src/api/credential-types/create.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential_type.fetch",
|
||||||
|
description: "Fetch a single credential type",
|
||||||
|
usedIn: ["src/api/credential-types/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential_type.fetch.many",
|
||||||
|
description: "Fetch multiple credential types",
|
||||||
|
usedIn: ["src/api/credential-types/fetchAll.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential_type.update",
|
||||||
|
description: "Update a credential type",
|
||||||
|
usedIn: ["src/api/credential-types/update.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "credential_type.delete",
|
||||||
|
description: "Delete a credential type",
|
||||||
|
usedIn: ["src/api/credential-types/delete.ts"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
permission: {
|
||||||
|
name: "Permission Permissions",
|
||||||
|
description: "Permissions for viewing permission node definitions",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "role.read",
|
||||||
|
description: "Fetch all permission nodes organized by category",
|
||||||
|
usedIn: ["src/api/permissions/fetchAll.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.read",
|
||||||
|
description: "Fetch a flat list of all permission nodes",
|
||||||
|
usedIn: ["src/api/permissions/fetchNodes.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.read",
|
||||||
|
description: "Fetch permission nodes by category",
|
||||||
|
usedIn: ["src/api/permissions/fetchByCategory.ts"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
role: {
|
||||||
|
name: "Role Permissions",
|
||||||
|
description: "Permissions for managing roles and role assignments",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "role.create",
|
||||||
|
description: "Create a new role",
|
||||||
|
usedIn: ["src/api/roles/create.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.read",
|
||||||
|
description: "Fetch a single role or view role information",
|
||||||
|
usedIn: ["src/api/roles/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.list",
|
||||||
|
description: "Fetch all roles",
|
||||||
|
usedIn: ["src/api/roles/fetchAll.ts"],
|
||||||
|
dependencies: ["role.read"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.update",
|
||||||
|
description: "Update role properties or manage role permissions",
|
||||||
|
usedIn: [
|
||||||
|
"src/api/roles/update.ts",
|
||||||
|
"src/api/roles/addPermissions.ts",
|
||||||
|
"src/api/roles/removePermissions.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "role.delete",
|
||||||
|
description: "Delete a role",
|
||||||
|
usedIn: ["src/api/roles/delete.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "user.read",
|
||||||
|
description: "View users assigned to a role",
|
||||||
|
usedIn: ["src/api/roles/getUsers.ts"],
|
||||||
|
dependencies: ["role.read"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
user: {
|
||||||
|
name: "User Permissions",
|
||||||
|
description: "Permissions for user profile and information management",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "user.read",
|
||||||
|
description: "Read user information",
|
||||||
|
usedIn: ["src/api/user/@me/fetch.ts"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "user.write",
|
||||||
|
description: "Update user information",
|
||||||
|
usedIn: ["src/api/user/@me/update.ts"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
uiNavigation: {
|
||||||
|
name: "UI Navigation Permissions",
|
||||||
|
description: "Permissions for controlling navigation visibility",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "ui.navigation.*.view",
|
||||||
|
description:
|
||||||
|
"View specific navigation sections (e.g., ui.navigation.admin.view)",
|
||||||
|
usedIn: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
adminUI: {
|
||||||
|
name: "Admin UI Permissions",
|
||||||
|
description:
|
||||||
|
"Admin-specific UI permissions that control visibility of admin sub-tabs",
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
node: "admin.users.view",
|
||||||
|
description: "Show the Users tab and load user data",
|
||||||
|
usedIn: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "admin.roles.view",
|
||||||
|
description: "Show the Roles tab and load role data",
|
||||||
|
usedIn: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: "admin.credential-types.view",
|
||||||
|
description: "Show the Credential Types tab and load type data",
|
||||||
|
usedIn: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, PermissionCategory>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get all permission nodes flattened into a single array
|
||||||
|
*/
|
||||||
|
export function getAllPermissionNodes(): PermissionNode[] {
|
||||||
|
return Object.values(PERMISSION_NODES).flatMap(
|
||||||
|
(category) => category.permissions as PermissionNode[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get all permission node strings
|
||||||
|
*/
|
||||||
|
export function getAllPermissionStrings(): string[] {
|
||||||
|
return getAllPermissionNodes().map((p) => p.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get a specific permission node by its identifier
|
||||||
|
*/
|
||||||
|
export function getPermissionNode(nodeId: string): PermissionNode | undefined {
|
||||||
|
return getAllPermissionNodes().find((p) => p.node === nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to get all permissions in a specific category
|
||||||
|
*/
|
||||||
|
export function getPermissionsByCategory(
|
||||||
|
categoryKey: keyof typeof PERMISSION_NODES,
|
||||||
|
): PermissionNode[] {
|
||||||
|
return PERMISSION_NODES[categoryKey].permissions;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user