diff --git a/API_ROUTES.md b/API_ROUTES.md index 91f734e..195eee1 100644 --- a/API_ROUTES.md +++ b/API_ROUTES.md @@ -199,6 +199,273 @@ Check if the currently authenticated user has specific permissions. Accepts an a --- +## Other User Routes + +### Get All Users + +**GET** `/user/users` + +Fetch a list of all users. + +**Authentication Required:** Yes + +**Required Permissions:** `user.read.other`, `user.list.other` + +**Response:** + +```json +{ + "status": 200, + "message": "Users Fetched Successfully!", + "data": [ + { + "id": "ckx...", + "name": "John Doe", + "email": "john.doe@example.com", + "login": "john.doe", + "image": "https://...", + "roles": ["admin"] + } + ], + "successful": true +} +``` + +--- + +### Get User by ID + +**GET** `/user/users/:identifier` + +Fetch a specific user by their ID. + +**Authentication Required:** Yes + +**Required Permissions:** `user.read.other` + +**Path Parameters:** + +- `identifier` - The user's ID + +**Response:** + +```json +{ + "status": 200, + "message": "User Fetched Successfully!", + "data": { + "id": "ckx...", + "name": "John Doe", + "email": "john.doe@example.com", + "login": "john.doe", + "image": "https://...", + "roles": ["admin"] + }, + "successful": true +} +``` + +**Error Response (404):** + +```json +{ + "status": 404, + "message": "User with identifier 'ckx...' was not found.", + "error": "UserNotFound", + "successful": false +} +``` + +--- + +### Update User by ID + +**PATCH** `/user/users/:identifier` + +Update a specific user's information. Supports updating profile fields, roles, and direct permissions. + +**Authentication Required:** Yes + +**Required Permissions:** `user.write.other` + +**Conditional Permissions:** + +- If `roles` is included in the body: `user.roles.other` is also required +- If `permissions` is included in the body: `user.permissions.other` is also required + +**Path Parameters:** + +- `identifier` - The user's ID + +**Request Body:** + +All fields are optional. Include only the fields you want to update. + +```json +{ + "name": "Jane Doe", + "image": "https://example.com/avatar.jpg", + "roles": ["admin", "moderator"], + "permissions": ["credential.fetch", "company.fetch"] +} +``` + +| Field | Type | Description | +| ------------- | ---------- | -------------------------------------------------------------- | +| `name` | `string` | The user's display name | +| `image` | `string` | URL to the user's avatar image | +| `roles` | `string[]` | Array of role ids or monikers to assign (replaces all roles) | +| `permissions` | `string[]` | Array of permission nodes to assign (replaces all permissions) | + +**Response:** + +```json +{ + "status": 200, + "message": "User Updated Successfully!", + "data": { + "id": "ckx...", + "name": "Jane Doe", + "email": "jane.doe@example.com", + "login": "jane.doe", + "image": "https://example.com/avatar.jpg", + "roles": ["admin", "moderator"] + }, + "successful": true +} +``` + +**Error Response (403 - Missing role permission):** + +```json +{ + "status": 403, + "message": "You do not have permission to modify roles on another user.", + "error": "InsufficientPermission", + "successful": false +} +``` + +--- + +### Delete User by ID + +**DELETE** `/user/users/:identifier` + +Delete a specific user. + +**Authentication Required:** Yes + +**Required Permissions:** `user.delete.other` + +**Path Parameters:** + +- `identifier` - The user's ID + +**Response:** + +```json +{ + "status": 200, + "message": "User Deleted Successfully!", + "data": { + "id": "ckx...", + "name": "John Doe", + "email": "john.doe@example.com", + "login": "john.doe", + "image": "https://...", + "roles": ["admin"] + }, + "successful": true +} +``` + +--- + +### Get User Roles + +**GET** `/user/users/:identifier/roles` + +Fetch all roles assigned to a specific user. + +**Authentication Required:** Yes + +**Required Permissions:** `user.read.other`, `role.read` + +**Path Parameters:** + +- `identifier` - The user's ID + +**Response:** + +```json +{ + "status": 200, + "message": "User Roles Fetched Successfully!", + "data": [ + { + "id": "uuid...", + "title": "Administrator", + "moniker": "admin", + "permissions": ["*"] + } + ], + "successful": true +} +``` + +--- + +### Check User Permissions (Other User) + +**POST** `/user/users/:identifier/check-permission` + +Check if a specific user has certain permissions. + +**Authentication Required:** Yes + +**Required Permissions:** `user.read.other` + +**Path Parameters:** + +- `identifier` - The user's ID + +**Request Body:** + +```json +{ + "permissions": ["user.read", "company.fetch", "credential.write"] +} +``` + +**Response:** + +```json +{ + "status": 200, + "message": "Permission check completed.", + "data": { + "results": [ + { + "permission": "user.read", + "hasPermission": true + }, + { + "permission": "company.fetch", + "hasPermission": false + }, + { + "permission": "credential.write", + "hasPermission": true + } + ] + }, + "successful": true +} +``` + +--- + ## Company Routes ### Get All Companies @@ -342,6 +609,34 @@ Fetch configurations for a specific company from ConnectWise. ## Credential Routes +### Get Value Types + +**GET** `/credential/valuetypes` + +Returns all available field value types for credential type fields. + +**Authentication Required:** Yes + +**Response:** + +```json +{ + "status": 200, + "message": "Value Types Fetched Successfully!", + "data": [ + "plain_text", + "license_key", + "ip_address", + "generic_secret", + "bitlocker_key", + "password" + ], + "successful": true +} +``` + +--- + ### Get Credential by ID **GET** `/credential/credentials/:id` @@ -365,11 +660,27 @@ Fetch a single credential by its ID. "data": { "id": "ckx...", "name": "AWS Credentials", + "notes": null, "typeId": "cky...", "companyId": "ckz...", - "fields": { - "accountId": "123456789" - }, + "fields": [ + { + "id": "accessKeyId", + "name": "Access Key ID", + "secure": false, + "required": true, + "valueType": "plain_text", + "value": "AKIAIOSFODNN7EXAMPLE" + }, + { + "id": "secretAccessKey", + "name": "Secret Access Key", + "secure": true, + "required": true, + "valueType": "password", + "value": null + } + ], "type": { "id": "cky...", "name": "AWS", @@ -413,9 +724,27 @@ Fetch all credentials associated with a specific company. { "id": "ckx...", "name": "AWS Credentials", + "notes": null, "typeId": "cky...", "companyId": "ckz...", - "fields": {...}, + "fields": [ + { + "id": "accessKeyId", + "name": "Access Key ID", + "secure": false, + "required": true, + "valueType": "plain_text", + "value": "AKIAIOSFODNN7EXAMPLE" + }, + { + "id": "secretAccessKey", + "name": "Secret Access Key", + "secure": true, + "required": true, + "valueType": "password", + "value": null + } + ], "type": {...}, "company": {...} } @@ -441,6 +770,7 @@ Create a new credential with validated and encrypted fields. ```json { "name": "Production AWS Credentials", + "notes": "Used for production S3 access", "typeId": "cky...", "companyId": "ckz...", "fields": [ @@ -469,7 +799,26 @@ Create a new credential with validated and encrypted fields. "name": "Production AWS Credentials", "typeId": "cky...", "companyId": "ckz...", - "fields": {...} + "fields": [ + { + "id": "accessKeyId", + "name": "Access Key ID", + "secure": false, + "required": true, + "valueType": "plain_text", + "value": "AKIAIOSFODNN7EXAMPLE" + }, + { + "id": "secretAccessKey", + "name": "Secret Access Key", + "secure": true, + "required": true, + "valueType": "password", + "value": null + } + ], + "type": {...}, + "company": {...} }, "successful": true } @@ -481,7 +830,7 @@ Create a new credential with validated and encrypted fields. **PATCH** `/credential/credentials/:id` -Update a credential's basic properties (name). +Update a credential's basic properties (name, notes) and/or field values. Secure fields are automatically encrypted. **Authentication Required:** Yes @@ -493,9 +842,22 @@ Update a credential's basic properties (name). **Request Body:** +All properties are optional. Include only the properties you want to update. + ```json { - "name": "Updated Credential Name" + "name": "Updated Credential Name", + "notes": "Updated notes for this credential", + "fields": [ + { + "fieldId": "accessKeyId", + "value": "AKIAIOSFODNN7EXAMPLE" + }, + { + "fieldId": "secretAccessKey", + "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + } + ] } ``` @@ -508,7 +870,21 @@ Update a credential's basic properties (name). "data": { "id": "ckx...", "name": "Updated Credential Name", - ... + "notes": "Updated notes for this credential", + "typeId": "cky...", + "companyId": "ckz...", + "fields": [ + { + "id": "accessKeyId", + "name": "Access Key ID", + "secure": false, + "required": true, + "valueType": "plain_text", + "value": "AKIAIOSFODNN7EXAMPLE" + } + ], + "type": {...}, + "company": {...} }, "successful": true } @@ -536,12 +912,10 @@ Validate and update credential field values. Secure fields are automatically enc { "fields": [ { - "id": "ckx1...", "fieldId": "accessKeyId", "value": "AKIAIOSFODNN7NEWVALUE" }, { - "id": "ckx2...", "fieldId": "secretAccessKey", "value": "newSecretKeyValue123" } @@ -558,7 +932,29 @@ Validate and update credential field values. Secure fields are automatically enc "data": { "id": "ckx...", "name": "Production AWS Credentials", - "fields": {...} + "notes": null, + "typeId": "cky...", + "companyId": "ckz...", + "fields": [ + { + "id": "accessKeyId", + "name": "Access Key ID", + "secure": false, + "required": true, + "valueType": "plain_text", + "value": "AKIAIOSFODNN7NEWVALUE" + }, + { + "id": "secretAccessKey", + "name": "Secret Access Key", + "secure": true, + "required": true, + "valueType": "password", + "value": null + } + ], + "type": {...}, + "company": {...} }, "successful": true } @@ -634,6 +1030,48 @@ Decrypt and return all secure field values for a credential. --- +### Read Single Secure Value + +**GET** `/credential/credentials/:id/secure-values/:fieldId` + +Decrypt and return a single secure field value for a credential. + +**Authentication Required:** Yes + +**Required Permissions:** `credential.fetch`, `credential.secure_values.read` + +**URL Parameters:** + +- `id` - Credential ID +- `fieldId` - The field ID of the secure value to read + +**Response:** + +```json +{ + "status": 200, + "message": "Secure Value Fetched Successfully!", + "data": { + "fieldId": "secretAccessKey", + "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + }, + "successful": true +} +``` + +**Error Response (404):** + +```json +{ + "status": 404, + "message": "Secure field not found: unknownField", + "error": "SecureFieldNotFound", + "successful": false +} +``` + +--- + ### Delete Credential **DELETE** `/credential/credentials/:id` diff --git a/PERMISSIONS.md b/PERMISSIONS.md index f8b11e7..d5c20a7 100644 --- a/PERMISSIONS.md +++ b/PERMISSIONS.md @@ -32,16 +32,16 @@ The permission validator supports special tokens for flexible permission managem ### 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) | +| 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 Type Permissions @@ -66,10 +66,16 @@ The permission validator supports special tokens for flexible permission managem ### 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 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 diff --git a/generated/prisma/internal/class.ts b/generated/prisma/internal/class.ts index 556b006..f500f20 100644 --- a/generated/prisma/internal/class.ts +++ b/generated/prisma/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.3.0", "engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735", "activeProvider": "postgresql", - "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?\n// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel Session {\n id String @id @default(uuid())\n sessionKey String @unique @default(cuid())\n userId String\n expires DateTime\n refreshTokenGenerated Boolean @default(false)\n refreshedAt DateTime?\n invalidatedAt DateTime?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n roles Role[]\n permissions String?\n login String @unique\n name String?\n email String @unique\n emailVerified DateTime?\n image String?\n\n userId String @unique\n token String?\n\n sessions Session[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Role {\n id String @id @default(uuid())\n title String\n moniker String @unique // e.g. admin, super_admin, moderator\n\n permissions String\n users User[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Company {\n id String @id @default(cuid())\n name String\n\n cw_CompanyId Int @unique\n cw_Identifier String @unique\n\n credentials Credential[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel CredentialType {\n id String @id @default(cuid())\n name String @unique\n\n permissionScope String\n icon String?\n fields Json\n\n credentials Credential[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel SecureValue {\n id String @id @default(cuid())\n name String\n\n content String // Encrypted content\n hash String // Hash of the original content for integrity verification and Search\n\n credentialId String\n credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade)\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Credential {\n id String @id @default(cuid())\n name String\n\n typeId String\n type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade)\n\n fields Json\n\n companyId String\n company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)\n\n securevalues SecureValue[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n", + "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?\n// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel Session {\n id String @id @default(uuid())\n sessionKey String @unique @default(cuid())\n userId String\n expires DateTime\n refreshTokenGenerated Boolean @default(false)\n refreshedAt DateTime?\n invalidatedAt DateTime?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n roles Role[]\n permissions String?\n login String @unique\n name String?\n email String @unique\n emailVerified DateTime?\n image String?\n\n userId String @unique\n token String?\n\n sessions Session[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Role {\n id String @id @default(uuid())\n title String\n moniker String @unique // e.g. admin, super_admin, moderator\n\n permissions String\n users User[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Company {\n id String @id @default(cuid())\n name String\n\n cw_CompanyId Int @unique\n cw_Identifier String @unique\n\n credentials Credential[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel CredentialType {\n id String @id @default(cuid())\n name String @unique\n\n permissionScope String\n icon String?\n fields Json\n\n credentials Credential[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel SecureValue {\n id String @id @default(cuid())\n name String\n\n content String // Encrypted content\n hash String // Hash of the original content for integrity verification and Search\n\n credentialId String\n credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade)\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Credential {\n id String @id @default(cuid())\n name String\n notes String?\n\n typeId String\n type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade)\n\n fields Json\n\n companyId String\n company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)\n\n securevalues SecureValue[]\n\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenGenerated\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"refreshedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"invalidatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roles\",\"kind\":\"object\",\"type\":\"Role\",\"relationName\":\"RoleToUser\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Role\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moniker\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"RoleToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Company\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cw_CompanyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cw_Identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CompanyToCredential\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CredentialType\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionScope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fields\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToCredentialType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"SecureValue\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credential\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToSecureValue\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Credential\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"typeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"object\",\"type\":\"CredentialType\",\"relationName\":\"CredentialToCredentialType\"},{\"name\":\"fields\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"companyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"company\",\"kind\":\"object\",\"type\":\"Company\",\"relationName\":\"CompanyToCredential\"},{\"name\":\"securevalues\",\"kind\":\"object\",\"type\":\"SecureValue\",\"relationName\":\"CredentialToSecureValue\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessionKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expires\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"refreshTokenGenerated\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"refreshedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"invalidatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"roles\",\"kind\":\"object\",\"type\":\"Role\",\"relationName\":\"RoleToUser\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailVerified\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"image\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"token\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sessions\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Role\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"moniker\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissions\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"users\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"RoleToUser\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Company\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cw_CompanyId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cw_Identifier\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CompanyToCredential\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CredentialType\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"permissionScope\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fields\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"credentials\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToCredentialType\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"SecureValue\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credentialId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"credential\",\"kind\":\"object\",\"type\":\"Credential\",\"relationName\":\"CredentialToSecureValue\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Credential\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"notes\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"typeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"object\",\"type\":\"CredentialType\",\"relationName\":\"CredentialToCredentialType\"},{\"name\":\"fields\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"companyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"company\",\"kind\":\"object\",\"type\":\"Company\",\"relationName\":\"CompanyToCredential\"},{\"name\":\"securevalues\",\"kind\":\"object\",\"type\":\"SecureValue\",\"relationName\":\"CredentialToSecureValue\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") async function decodeBase64AsWasm(wasmBase64: string): Promise { const { Buffer } = await import('node:buffer') diff --git a/generated/prisma/internal/prismaNamespace.ts b/generated/prisma/internal/prismaNamespace.ts index d8155b2..1ffb4b2 100644 --- a/generated/prisma/internal/prismaNamespace.ts +++ b/generated/prisma/internal/prismaNamespace.ts @@ -1050,6 +1050,7 @@ export type SecureValueScalarFieldEnum = (typeof SecureValueScalarFieldEnum)[key export const CredentialScalarFieldEnum = { id: 'id', name: 'name', + notes: 'notes', typeId: 'typeId', fields: 'fields', companyId: 'companyId', diff --git a/generated/prisma/internal/prismaNamespaceBrowser.ts b/generated/prisma/internal/prismaNamespaceBrowser.ts index f7dd699..7a6cfbf 100644 --- a/generated/prisma/internal/prismaNamespaceBrowser.ts +++ b/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -159,6 +159,7 @@ export type SecureValueScalarFieldEnum = (typeof SecureValueScalarFieldEnum)[key export const CredentialScalarFieldEnum = { id: 'id', name: 'name', + notes: 'notes', typeId: 'typeId', fields: 'fields', companyId: 'companyId', diff --git a/generated/prisma/models/Credential.ts b/generated/prisma/models/Credential.ts index 40fe657..d42ea43 100644 --- a/generated/prisma/models/Credential.ts +++ b/generated/prisma/models/Credential.ts @@ -27,6 +27,7 @@ export type AggregateCredential = { export type CredentialMinAggregateOutputType = { id: string | null name: string | null + notes: string | null typeId: string | null companyId: string | null createdAt: Date | null @@ -36,6 +37,7 @@ export type CredentialMinAggregateOutputType = { export type CredentialMaxAggregateOutputType = { id: string | null name: string | null + notes: string | null typeId: string | null companyId: string | null createdAt: Date | null @@ -45,6 +47,7 @@ export type CredentialMaxAggregateOutputType = { export type CredentialCountAggregateOutputType = { id: number name: number + notes: number typeId: number fields: number companyId: number @@ -57,6 +60,7 @@ export type CredentialCountAggregateOutputType = { export type CredentialMinAggregateInputType = { id?: true name?: true + notes?: true typeId?: true companyId?: true createdAt?: true @@ -66,6 +70,7 @@ export type CredentialMinAggregateInputType = { export type CredentialMaxAggregateInputType = { id?: true name?: true + notes?: true typeId?: true companyId?: true createdAt?: true @@ -75,6 +80,7 @@ export type CredentialMaxAggregateInputType = { export type CredentialCountAggregateInputType = { id?: true name?: true + notes?: true typeId?: true fields?: true companyId?: true @@ -158,6 +164,7 @@ export type CredentialGroupByArgs | string name?: Prisma.StringFilter<"Credential"> | string + notes?: Prisma.StringNullableFilter<"Credential"> | string | null typeId?: Prisma.StringFilter<"Credential"> | string fields?: Prisma.JsonFilter<"Credential"> companyId?: Prisma.StringFilter<"Credential"> | string @@ -202,6 +210,7 @@ export type CredentialWhereInput = { export type CredentialOrderByWithRelationInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder + notes?: Prisma.SortOrderInput | Prisma.SortOrder typeId?: Prisma.SortOrder fields?: Prisma.SortOrder companyId?: Prisma.SortOrder @@ -218,6 +227,7 @@ export type CredentialWhereUniqueInput = Prisma.AtLeast<{ OR?: Prisma.CredentialWhereInput[] NOT?: Prisma.CredentialWhereInput | Prisma.CredentialWhereInput[] name?: Prisma.StringFilter<"Credential"> | string + notes?: Prisma.StringNullableFilter<"Credential"> | string | null typeId?: Prisma.StringFilter<"Credential"> | string fields?: Prisma.JsonFilter<"Credential"> companyId?: Prisma.StringFilter<"Credential"> | string @@ -231,6 +241,7 @@ export type CredentialWhereUniqueInput = Prisma.AtLeast<{ export type CredentialOrderByWithAggregationInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder + notes?: Prisma.SortOrderInput | Prisma.SortOrder typeId?: Prisma.SortOrder fields?: Prisma.SortOrder companyId?: Prisma.SortOrder @@ -247,6 +258,7 @@ export type CredentialScalarWhereWithAggregatesInput = { NOT?: Prisma.CredentialScalarWhereWithAggregatesInput | Prisma.CredentialScalarWhereWithAggregatesInput[] id?: Prisma.StringWithAggregatesFilter<"Credential"> | string name?: Prisma.StringWithAggregatesFilter<"Credential"> | string + notes?: Prisma.StringNullableWithAggregatesFilter<"Credential"> | string | null typeId?: Prisma.StringWithAggregatesFilter<"Credential"> | string fields?: Prisma.JsonWithAggregatesFilter<"Credential"> companyId?: Prisma.StringWithAggregatesFilter<"Credential"> | string @@ -257,6 +269,7 @@ export type CredentialScalarWhereWithAggregatesInput = { export type CredentialCreateInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string updatedAt?: Date | string @@ -268,6 +281,7 @@ export type CredentialCreateInput = { export type CredentialUncheckedCreateInput = { id?: string name: string + notes?: string | null typeId: string fields: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId: string @@ -279,6 +293,7 @@ export type CredentialUncheckedCreateInput = { export type CredentialUpdateInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -290,6 +305,7 @@ export type CredentialUpdateInput = { export type CredentialUncheckedUpdateInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null typeId?: Prisma.StringFieldUpdateOperationsInput | string fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId?: Prisma.StringFieldUpdateOperationsInput | string @@ -301,6 +317,7 @@ export type CredentialUncheckedUpdateInput = { export type CredentialCreateManyInput = { id?: string name: string + notes?: string | null typeId: string fields: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId: string @@ -311,6 +328,7 @@ export type CredentialCreateManyInput = { export type CredentialUpdateManyMutationInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -319,6 +337,7 @@ export type CredentialUpdateManyMutationInput = { export type CredentialUncheckedUpdateManyInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null typeId?: Prisma.StringFieldUpdateOperationsInput | string fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId?: Prisma.StringFieldUpdateOperationsInput | string @@ -344,6 +363,7 @@ export type CredentialScalarRelationFilter = { export type CredentialCountOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder + notes?: Prisma.SortOrder typeId?: Prisma.SortOrder fields?: Prisma.SortOrder companyId?: Prisma.SortOrder @@ -354,6 +374,7 @@ export type CredentialCountOrderByAggregateInput = { export type CredentialMaxOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder + notes?: Prisma.SortOrder typeId?: Prisma.SortOrder companyId?: Prisma.SortOrder createdAt?: Prisma.SortOrder @@ -363,6 +384,7 @@ export type CredentialMaxOrderByAggregateInput = { export type CredentialMinOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder + notes?: Prisma.SortOrder typeId?: Prisma.SortOrder companyId?: Prisma.SortOrder createdAt?: Prisma.SortOrder @@ -470,6 +492,7 @@ export type CredentialUpdateOneRequiredWithoutSecurevaluesNestedInput = { export type CredentialCreateWithoutCompanyInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string updatedAt?: Date | string @@ -480,6 +503,7 @@ export type CredentialCreateWithoutCompanyInput = { export type CredentialUncheckedCreateWithoutCompanyInput = { id?: string name: string + notes?: string | null typeId: string fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string @@ -519,6 +543,7 @@ export type CredentialScalarWhereInput = { NOT?: Prisma.CredentialScalarWhereInput | Prisma.CredentialScalarWhereInput[] id?: Prisma.StringFilter<"Credential"> | string name?: Prisma.StringFilter<"Credential"> | string + notes?: Prisma.StringNullableFilter<"Credential"> | string | null typeId?: Prisma.StringFilter<"Credential"> | string fields?: Prisma.JsonFilter<"Credential"> companyId?: Prisma.StringFilter<"Credential"> | string @@ -529,6 +554,7 @@ export type CredentialScalarWhereInput = { export type CredentialCreateWithoutTypeInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string updatedAt?: Date | string @@ -539,6 +565,7 @@ export type CredentialCreateWithoutTypeInput = { export type CredentialUncheckedCreateWithoutTypeInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId: string createdAt?: Date | string @@ -575,6 +602,7 @@ export type CredentialUpdateManyWithWhereWithoutTypeInput = { export type CredentialCreateWithoutSecurevaluesInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string updatedAt?: Date | string @@ -585,6 +613,7 @@ export type CredentialCreateWithoutSecurevaluesInput = { export type CredentialUncheckedCreateWithoutSecurevaluesInput = { id?: string name: string + notes?: string | null typeId: string fields: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId: string @@ -611,6 +640,7 @@ export type CredentialUpdateToOneWithWhereWithoutSecurevaluesInput = { export type CredentialUpdateWithoutSecurevaluesInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -621,6 +651,7 @@ export type CredentialUpdateWithoutSecurevaluesInput = { export type CredentialUncheckedUpdateWithoutSecurevaluesInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null typeId?: Prisma.StringFieldUpdateOperationsInput | string fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId?: Prisma.StringFieldUpdateOperationsInput | string @@ -631,6 +662,7 @@ export type CredentialUncheckedUpdateWithoutSecurevaluesInput = { export type CredentialCreateManyCompanyInput = { id?: string name: string + notes?: string | null typeId: string fields: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Date | string @@ -640,6 +672,7 @@ export type CredentialCreateManyCompanyInput = { export type CredentialUpdateWithoutCompanyInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -650,6 +683,7 @@ export type CredentialUpdateWithoutCompanyInput = { export type CredentialUncheckedUpdateWithoutCompanyInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null typeId?: Prisma.StringFieldUpdateOperationsInput | string fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -660,6 +694,7 @@ export type CredentialUncheckedUpdateWithoutCompanyInput = { export type CredentialUncheckedUpdateManyWithoutCompanyInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null typeId?: Prisma.StringFieldUpdateOperationsInput | string fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -669,6 +704,7 @@ export type CredentialUncheckedUpdateManyWithoutCompanyInput = { export type CredentialCreateManyTypeInput = { id?: string name: string + notes?: string | null fields: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId: string createdAt?: Date | string @@ -678,6 +714,7 @@ export type CredentialCreateManyTypeInput = { export type CredentialUpdateWithoutTypeInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -688,6 +725,7 @@ export type CredentialUpdateWithoutTypeInput = { export type CredentialUncheckedUpdateWithoutTypeInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId?: Prisma.StringFieldUpdateOperationsInput | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -698,6 +736,7 @@ export type CredentialUncheckedUpdateWithoutTypeInput = { export type CredentialUncheckedUpdateManyWithoutTypeInput = { id?: Prisma.StringFieldUpdateOperationsInput | string name?: Prisma.StringFieldUpdateOperationsInput | string + notes?: Prisma.NullableStringFieldUpdateOperationsInput | string | null fields?: Prisma.JsonNullValueInput | runtime.InputJsonValue companyId?: Prisma.StringFieldUpdateOperationsInput | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string @@ -738,6 +777,7 @@ export type CredentialCountOutputTypeCountSecurevaluesArgs = runtime.Types.Extensions.GetSelect<{ id?: boolean name?: boolean + notes?: boolean typeId?: boolean fields?: boolean companyId?: boolean @@ -752,6 +792,7 @@ export type CredentialSelect = runtime.Types.Extensions.GetSelect<{ id?: boolean name?: boolean + notes?: boolean typeId?: boolean fields?: boolean companyId?: boolean @@ -764,6 +805,7 @@ export type CredentialSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ id?: boolean name?: boolean + notes?: boolean typeId?: boolean fields?: boolean companyId?: boolean @@ -776,6 +818,7 @@ export type CredentialSelectUpdateManyAndReturn = runtime.Types.Extensions.GetOmit<"id" | "name" | "typeId" | "fields" | "companyId" | "createdAt" | "updatedAt", ExtArgs["result"]["credential"]> +export type CredentialOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "notes" | "typeId" | "fields" | "companyId" | "createdAt" | "updatedAt", ExtArgs["result"]["credential"]> export type CredentialInclude = { type?: boolean | Prisma.CredentialTypeDefaultArgs company?: boolean | Prisma.CompanyDefaultArgs @@ -809,6 +852,7 @@ export type $CredentialPayload readonly name: Prisma.FieldRef<"Credential", 'String'> + readonly notes: Prisma.FieldRef<"Credential", 'String'> readonly typeId: Prisma.FieldRef<"Credential", 'String'> readonly fields: Prisma.FieldRef<"Credential", 'Json'> readonly companyId: Prisma.FieldRef<"Credential", 'String'> diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1ccad4e..9e524a4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -97,8 +97,9 @@ model SecureValue { } model Credential { - id String @id @default(cuid()) - name String + id String @id @default(cuid()) + name String + notes String? typeId String type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade) diff --git a/src/api/credential-types/create.ts b/src/api/credential-types/create.ts index c34ab49..203a709 100644 --- a/src/api/credential-types/create.ts +++ b/src/api/credential-types/create.ts @@ -5,6 +5,7 @@ import { apiResponse } from "../../modules/api-utils/apiResponse"; import { ContentfulStatusCode } from "hono/utils/http-status"; import { authMiddleware } from "../middleware/authorization"; import { z } from "zod"; +import { ValueType } from "../../modules/credentials/credentialTypeDefs"; /* /v1/credential-type */ export default createRoute( @@ -24,13 +25,15 @@ export default createRoute( name: z.string(), required: z.boolean(), secure: z.boolean(), - valueType: z.enum(["plain_text", "password"]), + valueType: z.enum(Object.values(ValueType)), }), ), }); const data = schema.parse(body); + console.log("Creating Credential Type with data:", data); + const credentialType = await credentialTypes.create(data as any); const response = apiResponse.created( diff --git a/src/api/credential-types/update.ts b/src/api/credential-types/update.ts index 64092f6..d785658 100644 --- a/src/api/credential-types/update.ts +++ b/src/api/credential-types/update.ts @@ -5,6 +5,7 @@ import { apiResponse } from "../../modules/api-utils/apiResponse"; import { ContentfulStatusCode } from "hono/utils/http-status"; import { authMiddleware } from "../middleware/authorization"; import { z } from "zod"; +import { ValueType } from "../../modules/credentials/credentialTypeDefs"; /* /v1/credential-type/:id */ export default createRoute( @@ -26,7 +27,7 @@ export default createRoute( name: z.string(), required: z.boolean(), secure: z.boolean(), - valueType: z.enum(["plain_text", "password"]), + valueType: z.enum(Object.values(ValueType)), }), ) .optional(), diff --git a/src/api/credentials/create.ts b/src/api/credentials/create.ts index 1e3011c..7115947 100644 --- a/src/api/credentials/create.ts +++ b/src/api/credentials/create.ts @@ -16,6 +16,7 @@ export default createRoute( const schema = z.object({ name: z.string().min(1, "Name is required"), + notes: z.string().optional(), typeId: z.string().min(1, "Type ID is required"), companyId: z.string().min(1, "Company ID is required"), fields: z.array( diff --git a/src/api/credentials/index.ts b/src/api/credentials/index.ts index 7b6f2b0..c506113 100644 --- a/src/api/credentials/index.ts +++ b/src/api/credentials/index.ts @@ -5,9 +5,12 @@ import { default as update } from "./update"; import { default as updateFields } from "./updateFields"; import { default as fetchFields } from "./fetchFields"; import { default as readSecureValues } from "./readSecureValues"; +import { default as readSecureValue } from "./readSecureValue"; import { default as deleteCredential } from "./delete"; +import { default as valueTypes } from "./valueTypes"; export { + valueTypes, fetch, fetchByCompany, create, @@ -15,5 +18,6 @@ export { updateFields, fetchFields, readSecureValues, + readSecureValue, deleteCredential as delete, }; diff --git a/src/api/credentials/readSecureValue.ts b/src/api/credentials/readSecureValue.ts new file mode 100644 index 0000000..ded86cb --- /dev/null +++ b/src/api/credentials/readSecureValue.ts @@ -0,0 +1,27 @@ +import { Hono } from "hono/tiny"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { credentials } from "../../managers/credentials"; +import { apiResponse } from "../../modules/api-utils/apiResponse"; +import { ContentfulStatusCode } from "hono/utils/http-status"; +import { authMiddleware } from "../middleware/authorization"; + +/* GET /v1/credential/credentials/:id/secure-values/:fieldId */ +export default createRoute( + "get", + ["/credentials/:id/secure-values/:fieldId"], + + async (c) => { + const credential = await credentials.fetch(c.req.param("id")); + const fieldId = c.req.param("fieldId"); + const value = await credential.readSecureFieldValue(fieldId); + + const response = apiResponse.successful( + "Secure Value Fetched Successfully!", + { fieldId, value }, + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ + permissions: ["credential.fetch", "credential.secure_values.read"], + }), +); diff --git a/src/api/credentials/update.ts b/src/api/credentials/update.ts index 49a51ab..4bad465 100644 --- a/src/api/credentials/update.ts +++ b/src/api/credentials/update.ts @@ -17,11 +17,29 @@ export default createRoute( const schema = z.object({ name: z.string().optional(), + notes: z.string().nullable().optional(), + fields: z + .array( + z.object({ + fieldId: z.string(), + value: z.string(), + }), + ) + .optional(), }); const data = schema.parse(body); - await credential.update(data); + if (data.fields) { + await credential.validateAndUpdateFields(data.fields); + } + + if (data.name !== undefined || data.notes !== undefined) { + await credential.update({ + ...(data.name !== undefined && { name: data.name }), + ...(data.notes !== undefined && { notes: data.notes }), + }); + } const response = apiResponse.successful( "Credential Updated Successfully!", diff --git a/src/api/credentials/updateFields.ts b/src/api/credentials/updateFields.ts index 41cc246..3bd4884 100644 --- a/src/api/credentials/updateFields.ts +++ b/src/api/credentials/updateFields.ts @@ -18,7 +18,6 @@ export default createRoute( const schema = z.object({ fields: z.array( z.object({ - id: z.string(), fieldId: z.string(), value: z.string(), }), diff --git a/src/api/credentials/valueTypes.ts b/src/api/credentials/valueTypes.ts new file mode 100644 index 0000000..df220b3 --- /dev/null +++ b/src/api/credentials/valueTypes.ts @@ -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 { ValueType } from "../../modules/credentials/credentialTypeDefs"; +import { authMiddleware } from "../middleware/authorization"; + +/* GET /v1/credential/valuetypes */ +export default createRoute( + "get", + ["/valuetypes"], + + async (c) => { + const valueTypes = Object.values(ValueType); + + const response = apiResponse.successful( + "Value Types Fetched Successfully!", + valueTypes, + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware(), +); diff --git a/src/api/routers/user.ts b/src/api/routers/user.ts index 1e5f3b3..65a1425 100644 --- a/src/api/routers/user.ts +++ b/src/api/routers/user.ts @@ -1,7 +1,9 @@ import { Hono } from "hono"; -import * as userRoutes from "../user/@me"; +import * as meRoutes from "../user/@me"; +import * as userRoutes from "../user"; const authRouter = new Hono(); +Object.values(meRoutes).map((r) => authRouter.route("/", r)); Object.values(userRoutes).map((r) => authRouter.route("/", r)); export default authRouter; diff --git a/src/api/user/@me/update.ts b/src/api/user/@me/update.ts index 8c698c0..c6c52cc 100644 --- a/src/api/user/@me/update.ts +++ b/src/api/user/@me/update.ts @@ -1,13 +1,22 @@ 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 updateSchema = z + .object({ + name: z.string().optional(), + image: z.string().optional(), + }) + .strict(); + export default createRoute( "patch", ["/@me"], async (c) => { - const updatedUser = await c.get("user")?.update(await c.req.json()); + const body = updateSchema.parse(await c.req.json()); + const updatedUser = await c.get("user")?.update(body); const response = apiResponse.successful( "Successfully updated user.", updatedUser?.toJson(), diff --git a/src/api/user/checkPermission.ts b/src/api/user/checkPermission.ts new file mode 100644 index 0000000..5daed23 --- /dev/null +++ b/src/api/user/checkPermission.ts @@ -0,0 +1,47 @@ +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"; +import { users } from "../../managers/users"; +import GenericError from "../../Errors/GenericError"; + +const checkPermissionSchema = z.object({ + permissions: z + .array(z.string().min(1, "Permission node cannot be empty")) + .min(1, "At least one permission is required"), +}); + +/* POST /v1/user/users/:identifier/check-permission */ +export default createRoute( + "post", + ["/users/:identifier/check-permission"], + + async (c) => { + const identifier = c.req.param("identifier"); + + const user = await users.fetchUser({ id: identifier }); + if (!user) + throw new GenericError({ + name: "UserNotFound", + message: `User with identifier '${identifier}' was not found.`, + status: 404, + }); + + 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({ permissions: ["user.read.other"] }), +); diff --git a/src/api/user/delete.ts b/src/api/user/delete.ts new file mode 100644 index 0000000..81e5202 --- /dev/null +++ b/src/api/user/delete.ts @@ -0,0 +1,34 @@ +import { ContentfulStatusCode } from "hono/utils/http-status"; +import { apiResponse } from "../../modules/api-utils/apiResponse"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { authMiddleware } from "../middleware/authorization"; +import { users } from "../../managers/users"; +import GenericError from "../../Errors/GenericError"; + +/* DELETE /v1/user/users/:identifier */ +export default createRoute( + "delete", + ["/users/:identifier"], + + async (c) => { + const identifier = c.req.param("identifier"); + + const user = await users.fetchUser({ id: identifier }); + if (!user) + throw new GenericError({ + name: "UserNotFound", + message: `User with identifier '${identifier}' was not found.`, + status: 404, + }); + + const userData = user.toJson(); + await users.deleteUser(user.id); + + const response = apiResponse.successful( + "User Deleted Successfully!", + userData, + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ permissions: ["user.delete.other"] }), +); diff --git a/src/api/user/fetch.ts b/src/api/user/fetch.ts new file mode 100644 index 0000000..9d92135 --- /dev/null +++ b/src/api/user/fetch.ts @@ -0,0 +1,31 @@ +import { ContentfulStatusCode } from "hono/utils/http-status"; +import { apiResponse } from "../../modules/api-utils/apiResponse"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { authMiddleware } from "../middleware/authorization"; +import { users } from "../../managers/users"; +import GenericError from "../../Errors/GenericError"; + +/* GET /v1/user/users/:identifier */ +export default createRoute( + "get", + ["/users/:identifier"], + + async (c) => { + const identifier = c.req.param("identifier"); + + const user = await users.fetchUser({ id: identifier }); + if (!user) + throw new GenericError({ + name: "UserNotFound", + message: `User with identifier '${identifier}' was not found.`, + status: 404, + }); + + const response = apiResponse.successful( + "User Fetched Successfully!", + user.toJson(), + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ permissions: ["user.read.other"] }), +); diff --git a/src/api/user/fetchAll.ts b/src/api/user/fetchAll.ts new file mode 100644 index 0000000..6fcf26c --- /dev/null +++ b/src/api/user/fetchAll.ts @@ -0,0 +1,23 @@ +import { ContentfulStatusCode } from "hono/utils/http-status"; +import { apiResponse } from "../../modules/api-utils/apiResponse"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { authMiddleware } from "../middleware/authorization"; +import { users } from "../../managers/users"; + +/* GET /v1/user/users */ +export default createRoute( + "get", + ["/users"], + + async (c) => { + const allUsers = await users.fetchAllUsers(); + const usersArray = allUsers.map((u) => u.toJson()); + + const response = apiResponse.successful( + "Users Fetched Successfully!", + usersArray, + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ permissions: ["user.read.other", "user.list.other"] }), +); diff --git a/src/api/user/fetchRoles.ts b/src/api/user/fetchRoles.ts new file mode 100644 index 0000000..3f065cb --- /dev/null +++ b/src/api/user/fetchRoles.ts @@ -0,0 +1,34 @@ +import { ContentfulStatusCode } from "hono/utils/http-status"; +import { apiResponse } from "../../modules/api-utils/apiResponse"; +import { createRoute } from "../../modules/api-utils/createRoute"; +import { authMiddleware } from "../middleware/authorization"; +import { users } from "../../managers/users"; +import GenericError from "../../Errors/GenericError"; + +/* GET /v1/user/users/:identifier/roles */ +export default createRoute( + "get", + ["/users/:identifier/roles"], + + async (c) => { + const identifier = c.req.param("identifier"); + + const user = await users.fetchUser({ id: identifier }); + if (!user) + throw new GenericError({ + name: "UserNotFound", + message: `User with identifier '${identifier}' was not found.`, + status: 404, + }); + + const roles = await user.fetchRoles(); + const rolesArray = roles.map((r) => r.toJson({ viewPermissions: true })); + + const response = apiResponse.successful( + "User Roles Fetched Successfully!", + rolesArray, + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ permissions: ["user.read.other", "role.read"] }), +); diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..7b4a799 --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1,6 @@ +export { default as fetch } from "./fetch"; +export { default as fetchAll } from "./fetchAll"; +export { default as update } from "./update"; +export { default as deleteUser } from "./delete"; +export { default as fetchRoles } from "./fetchRoles"; +export { default as checkPermission } from "./checkPermission"; diff --git a/src/api/user/update.ts b/src/api/user/update.ts new file mode 100644 index 0000000..29009e9 --- /dev/null +++ b/src/api/user/update.ts @@ -0,0 +1,68 @@ +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"; +import { users } from "../../managers/users"; +import GenericError from "../../Errors/GenericError"; + +const updateSchema = z + .object({ + name: z.string().optional(), + image: z.string().optional(), + roles: z.array(z.string()).optional(), + permissions: z.array(z.string()).optional(), + }) + .strict(); + +/* PATCH /v1/user/users/:identifier */ +export default createRoute( + "patch", + ["/users/:identifier"], + + async (c) => { + const identifier = c.req.param("identifier"); + const requestingUser = c.get("user"); + + const user = await users.fetchUser({ id: identifier }); + if (!user) + throw new GenericError({ + name: "UserNotFound", + message: `User with identifier '${identifier}' was not found.`, + status: 404, + }); + + const body = updateSchema.parse(await c.req.json()); + + if (body.roles && !(await requestingUser.hasPermission("user.roles.other"))) + throw new GenericError({ + name: "InsufficientPermission", + message: "You do not have permission to modify roles on another user.", + status: 403, + }); + + if ( + body.permissions && + !(await requestingUser.hasPermission("user.permissions.other")) + ) + throw new GenericError({ + name: "InsufficientPermission", + message: + "You do not have permission to modify permissions on another user.", + status: 403, + }); + + const { roles: roleIds, permissions, ...profileData } = body; + + if (Object.keys(profileData).length > 0) await user.update(profileData); + if (roleIds) await user.setRoles(roleIds); + if (permissions) await user.setPermissions(permissions); + + const response = apiResponse.successful( + "User Updated Successfully!", + user.toJson(), + ); + return c.json(response, response.status as ContentfulStatusCode); + }, + authMiddleware({ permissions: ["user.write.other"] }), +); diff --git a/src/controllers/CompanyController.ts b/src/controllers/CompanyController.ts index 5117672..4eb6890 100644 --- a/src/controllers/CompanyController.ts +++ b/src/controllers/CompanyController.ts @@ -80,7 +80,9 @@ export class CompanyController { city: this.cw_Data?.city, state: this.cw_Data?.state, zip: this.cw_Data?.zip, - country: this.cw_Data?.country.name, + country: this.cw_Data?.country + ? this.cw_Data.country.name + : "United States", }, }, }; diff --git a/src/controllers/CredentialController.ts b/src/controllers/CredentialController.ts index 99d5995..5cc03fe 100644 --- a/src/controllers/CredentialController.ts +++ b/src/controllers/CredentialController.ts @@ -10,6 +10,7 @@ import { fieldValidator } from "../modules/credentials/fieldValidator"; import { CredentialField, CredentialTypeField, + ValueType, } from "../modules/credentials/credentialTypeDefs"; import { generateSecureValue } from "../modules/credentials/generateSecureValue"; import { readSecureValue } from "../modules/credentials/readSecureValue"; @@ -24,6 +25,7 @@ import GenericError from "../Errors/GenericError"; export class CredentialController { public readonly id: string; public name: string; + public notes: string | null; public readonly typeId: string; public readonly companyId: string; public fields: any; @@ -44,6 +46,7 @@ export class CredentialController { ) { this.id = credentialData.id; this.name = credentialData.name; + this.notes = credentialData.notes; this.typeId = credentialData.typeId; this.companyId = credentialData.companyId; this._type = credentialData.type; @@ -52,14 +55,21 @@ export class CredentialController { this.fields = (() => { let fields = credentialData.fields as Record; - this._secureValues.forEach((sv) => (fields[sv.name] = `secure-${sv.id}`)); + return (this._type.fields! as any).map((f: any) => ({ + id: f.id, + name: f.name, + secure: f.secure, + required: f.required, + valueType: f.valueType as ValueType, + value: f.secure + ? `secure-${this._secureValues.find((sv) => sv.name === f.id)?.id}` + : fields[f.id], + })); return fields; })(); this.createdAt = credentialData.createdAt; this.updatedAt = credentialData.updatedAt; - - console.log(credentialData); } /** @@ -72,6 +82,7 @@ export class CredentialController { */ private _updateInternalValues(credentialData: Credential) { this.name = credentialData.name; + this.notes = credentialData.notes; this.fields = credentialData.fields; this.updatedAt = credentialData.updatedAt; } @@ -174,7 +185,6 @@ export class CredentialController { const nonSecureFields = this.fields as Record; Object.entries(nonSecureFields || {}).forEach(([fieldId, value]) => { fields.push({ - id: `${this.id}-${fieldId}`, // Generate a consistent ID fieldId, value: value as string, }); @@ -183,7 +193,6 @@ export class CredentialController { // Add secure fields from SecureValue table (encrypted) this._secureValues.forEach((secureValue) => { fields.push({ - id: secureValue.id, fieldId: secureValue.name, value: secureValue.content, // Encrypted value }); @@ -253,11 +262,12 @@ export class CredentialController { * @returns {Promise} - The updated credential controller */ async update( - data: Partial>, + data: Partial>, ): Promise { const pData = z .object({ name: z.string().optional(), + notes: z.string().nullable().optional(), }) .strict() .parse(data); @@ -307,6 +317,7 @@ export class CredentialController { return { id: this.id, name: this.name, + notes: this.notes, typeId: this.typeId, companyId: this.companyId, fields: this.fields, diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index 7e45be4..b6a32f1 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -1,5 +1,4 @@ import { Collection } from "@discordjs/collection"; -import { z } from "zod"; import { Role } from "../../generated/prisma/client"; import { User } from "../../generated/prisma/browser"; import { SessionTokensObject } from "./SessionController"; @@ -9,6 +8,10 @@ import { prisma } from "../constants"; import { events } from "../modules/globalEvents"; import { RoleController } from "./RoleController"; import { roles } from "../managers/roles"; +import { signPermissions } from "../modules/permission-utils/signPermissions"; +import { DecodedPermissionsBlock } from "../types/PermissionTypes"; +import jwt from "jsonwebtoken"; +import { permissionsPrivateKey } from "../constants"; export default class UserController { public id: string; @@ -18,6 +21,7 @@ export default class UserController { public image: string | null; private _roles: Collection; + private _permissions: string | null; public createdAt: Date; public updatedAt: Date; @@ -29,6 +33,7 @@ export default class UserController { this.image = userdata.image; this.updatedAt = userdata.updatedAt; this.createdAt = userdata.createdAt; + this._permissions = userdata.permissions ?? null; this._roles = (() => { let collection = new Collection(); @@ -77,22 +82,13 @@ export default class UserController { * @param data - A partial of the user data * @returns {Promise} - The updated user controller */ - public async update(data: Partial) { - // Parsed Data With Schema - const pData = z - .object({ - name: z.string().optional(), - image: z.string().optional(), - }) - .strict() - .parse(data); - + public async update(data: Partial>) { if (Object.keys(data).length == 0) throw new BodyError("Body cannot be empty."); const updatedUser = await prisma.user.update({ where: { id: this.id }, - data: pData, + data, }); this._updateInternalValues(updatedUser); @@ -101,6 +97,87 @@ export default class UserController { return this; } + /** + * Set Roles + * + * Replace the user's roles with the provided array of role identifiers (id or moniker). + * Validates that each role exists before assigning. + * + * @param roleIdentifiers - Array of role ids or monikers to assign + * @returns {Promise} - The updated user controller + */ + public async setRoles(roleIdentifiers: string[]): Promise { + const resolvedRoles = await Promise.all( + roleIdentifiers.map((identifier) => roles.fetch(identifier)), + ); + + const updatedUser = await prisma.user.update({ + where: { id: this.id }, + data: { + roles: { + set: resolvedRoles.map((r) => ({ id: r.id })), + }, + }, + include: { roles: true }, + }); + + this._updateInternalValues(updatedUser); + this._roles = new Collection(); + updatedUser.roles.map((v: any) => this._roles.set(v.id, v)); + + for (const role of resolvedRoles) { + events.emit("user:role:assigned", { user: this, role }); + } + + return this; + } + + /** + * Set Permissions + * + * Replace the user's direct permissions with the provided array of permission strings. + * Signs the permissions with the user issuer before storing. + * + * @param permissions - Array of permission node strings to assign + * @returns {Promise} - The updated user controller + */ + public async setPermissions(permissions: string[]): Promise { + const signed = signPermissions({ + issuer: "user", + subject: this.id, + permissions, + }); + + const updatedUser = await prisma.user.update({ + where: { id: this.id }, + data: { permissions: signed }, + }); + + this._updateInternalValues(updatedUser); + + return this; + } + + /** + * Read Permissions + * + * Verifies and decodes the user's direct permissions JWT and returns the array of + * permission node strings. Returns an empty array if the user has no direct permissions. + * + * @returns {string[]} The user's direct permission nodes + */ + public readPermissions(): string[] { + if (!this._permissions) return []; + + const decoded = jwt.verify(this._permissions, permissionsPrivateKey, { + algorithms: ["RS256"], + issuer: "user", + subject: this.id, + }) as DecodedPermissionsBlock; + + return decoded.permissions; + } + /** * Fetch Roles * @@ -185,9 +262,12 @@ export default class UserController { : this._roles.size > 0 ? this._roles.map((v) => v.moniker) : undefined, + permissions: opts?.safeReturn ? undefined : this.readPermissions(), login: opts?.safeReturn ? undefined : this.login, email: opts?.safeReturn ? undefined : this.email, image: this.image, + createdAt: this.createdAt, + updatedAt: this.updatedAt, }; } } diff --git a/src/managers/credentialTypes.ts b/src/managers/credentialTypes.ts index 821bd6d..4e9aa7e 100644 --- a/src/managers/credentialTypes.ts +++ b/src/managers/credentialTypes.ts @@ -79,6 +79,8 @@ export const credentialTypes = { }); } + console.log(data.fields); + const credentialType = await prisma.credentialType.create({ data: { name: data.name, @@ -91,6 +93,8 @@ export const credentialTypes = { }, }); + console.log(credentialType.fields); + return new CredentialTypeController(credentialType); }, diff --git a/src/managers/credentials.ts b/src/managers/credentials.ts index f1fa85a..4c32902 100644 --- a/src/managers/credentials.ts +++ b/src/managers/credentials.ts @@ -73,6 +73,7 @@ export const credentials = { */ async create(data: { name: string; + notes?: string; typeId: string; companyId: string; fields: { @@ -131,6 +132,7 @@ export const credentials = { const credential = await prisma.credential.create({ data: { name: data.name, + notes: data.notes, typeId: data.typeId, companyId: data.companyId, fields: fieldsObject, diff --git a/src/managers/users.ts b/src/managers/users.ts index 1928d9a..9409957 100644 --- a/src/managers/users.ts +++ b/src/managers/users.ts @@ -106,8 +106,31 @@ export const users = { return controller; }, -}; -/** - * @TODO Figure out default permissions - */ + /** + * Fetch all users + * + * Returns an array of UserController instances for every user in the database. + * + * @returns {Promise} Array of user controllers + */ + async fetchAllUsers(): Promise { + const allUsers = await prisma.user.findMany({ + include: { roles: true }, + }); + + return allUsers.map((u) => new UserController(u)); + }, + + /** + * Delete a user + * + * Removes a user record from the database by their id. + * + * @param userId - The id of the user to delete + */ + async deleteUser(userId: string): Promise { + await prisma.user.delete({ where: { id: userId } }); + events.emit("user:deleted", { id: userId }); + }, +}; diff --git a/src/modules/credentials/credentialTypeDefs.ts b/src/modules/credentials/credentialTypeDefs.ts index 622e6ee..4a7561e 100644 --- a/src/modules/credentials/credentialTypeDefs.ts +++ b/src/modules/credentials/credentialTypeDefs.ts @@ -1,5 +1,9 @@ export enum ValueType { PLAIN_TEXT = "plain_text", + LICENSE_KEY = "license_key", + IP_ADDRESS = "ip_address", + GENERIC_SECRET = "generic_secret", + BITLOCKER_KEY = "bitlocker_key", PASSWORD = "password", } @@ -12,7 +16,6 @@ export interface CredentialTypeField { } export interface CredentialField { - id: string; // CUID fieldId: string; // I.e. "clientId", "clientSecret", etc. value: string; // Encrypted value stored in the database } diff --git a/src/modules/credentials/fieldValidator.ts b/src/modules/credentials/fieldValidator.ts index 981b56c..f910544 100644 --- a/src/modules/credentials/fieldValidator.ts +++ b/src/modules/credentials/fieldValidator.ts @@ -19,7 +19,6 @@ export const fieldValidator = async ( acceptableFields: CredentialTypeField[], ): Promise< { - id: string; fieldId: string; value: string; secure: boolean; @@ -47,7 +46,6 @@ export const fieldValidator = async ( const matchingField = afCollection.get(field.fieldId)!; return { - id: field.id, fieldId: field.fieldId, value: field.value, secure: matchingField.secure, diff --git a/src/modules/credentials/generateSecureValue.ts b/src/modules/credentials/generateSecureValue.ts index 40e333a..3b584a1 100644 --- a/src/modules/credentials/generateSecureValue.ts +++ b/src/modules/credentials/generateSecureValue.ts @@ -6,10 +6,13 @@ export const generateSecureValue = (content: string) => { // Generate a hash of the content const hash = Password.hash(content); + // Parse the PKCS#1 PEM key into a proper KeyObject + const publicKey = crypto.createPublicKey(secureValuesPublicKey); + // Encrypt the content using the .secureValues.pub public key const encrypted = crypto.publicEncrypt( { - key: secureValuesPublicKey, + key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, diff --git a/src/modules/credentials/readSecureValue.ts b/src/modules/credentials/readSecureValue.ts index 5909aaa..dda7548 100644 --- a/src/modules/credentials/readSecureValue.ts +++ b/src/modules/credentials/readSecureValue.ts @@ -1,20 +1,36 @@ import Password from "../tools/Password"; import crypto from "crypto"; import { secureValuesPrivateKey } from "../../constants"; +import GenericError from "../../Errors/GenericError"; + +const privateKey = crypto.createPrivateKey(secureValuesPrivateKey); export const readSecureValue = ( encryptedContent: string, hash?: string, ): string => { - // Decrypt the content using the .secureValues.key private key - const decrypted = crypto.privateDecrypt( - { - key: secureValuesPrivateKey, - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - oaepHash: "sha256", - }, - Buffer.from(encryptedContent, "base64"), - ); + let decrypted: Buffer; + + try { + // Decrypt the content using the .secureValues.key private key + decrypted = crypto.privateDecrypt( + { + key: privateKey, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + Buffer.from(encryptedContent, "base64"), + ); + } catch { + throw new GenericError({ + name: "SecureValueDecryptionError", + message: + "Unable to decrypt secure value. The value was encrypted with a different key and must be re-entered.", + cause: + "RSA key mismatch — the current private key does not match the public key used to encrypt this value.", + status: 422, + }); + } const content = decrypted.toString("utf-8"); diff --git a/src/modules/globalEvents.ts b/src/modules/globalEvents.ts index 8c671d1..9b4514c 100644 --- a/src/modules/globalEvents.ts +++ b/src/modules/globalEvents.ts @@ -19,6 +19,7 @@ interface EventTypes { user: UserController; updatedValues: Partial; }) => void; + "user:deleted": (data: { id: string }) => void; "user:authenticated": (data: { user: UserController; tokens: SessionTokensObject; diff --git a/src/types/PermissionNodes.ts b/src/types/PermissionNodes.ts index 2aedfbd..f9a97db 100644 --- a/src/types/PermissionNodes.ts +++ b/src/types/PermissionNodes.ts @@ -114,7 +114,10 @@ export const PERMISSION_NODES = { { node: "credential.secure_values.read", description: "Read secure values of a credential", - usedIn: ["src/api/credentials/readSecureValues.ts"], + usedIn: [ + "src/api/credentials/readSecureValues.ts", + "src/api/credentials/readSecureValue.ts", + ], dependencies: ["credential.fetch"], }, ], @@ -231,6 +234,43 @@ export const PERMISSION_NODES = { description: "Update user information", usedIn: ["src/api/user/@me/update.ts"], }, + { + node: "user.read.other", + description: "Read other users' information", + usedIn: [ + "src/api/user/fetch.ts", + "src/api/user/fetchRoles.ts", + "src/api/user/checkPermission.ts", + ], + }, + { + node: "user.list.other", + description: "List all users", + usedIn: ["src/api/user/fetchAll.ts"], + dependencies: ["user.read.other"], + }, + { + node: "user.write.other", + description: "Update other users' information", + usedIn: ["src/api/user/update.ts"], + }, + { + node: "user.roles.other", + description: "Modify roles assigned to other users", + usedIn: ["src/api/user/update.ts"], + dependencies: ["user.write.other"], + }, + { + node: "user.permissions.other", + description: "Modify direct permissions assigned to other users", + usedIn: ["src/api/user/update.ts"], + dependencies: ["user.write.other"], + }, + { + node: "user.delete.other", + description: "Delete other users", + usedIn: ["src/api/user/delete.ts"], + }, ], }, diff --git a/utils/genPrivateKeys.ts b/utils/genPrivateKeys.ts index ebfb29b..d28f241 100644 --- a/utils/genPrivateKeys.ts +++ b/utils/genPrivateKeys.ts @@ -40,14 +40,11 @@ await Promise.all( }); if (!privExists || !pubExists) { + // Always regenerate both files together to ensure the key pair matches console.log(`Generating '${v}' and '${pubPath}'...`); const keys = keypair({ bits: 4096 }); - if (!privExists) { - await Bun.write(v, keys.private); - } - if (!pubExists) { - await Bun.write(pubPath, keys.public); - } + await Bun.write(v, keys.private); + await Bun.write(pubPath, keys.public); } return; }),