@friggframework/core 2.0.0-next.6 → 2.0.0-next.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/CLAUDE.md +694 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/user-commands.js +283 -0
  8. package/application/index.js +69 -0
  9. package/core/CLAUDE.md +690 -0
  10. package/core/Worker.js +8 -21
  11. package/core/create-handler.js +14 -7
  12. package/credential/repositories/credential-repository-documentdb.js +304 -0
  13. package/credential/repositories/credential-repository-factory.js +54 -0
  14. package/credential/repositories/credential-repository-interface.js +98 -0
  15. package/credential/repositories/credential-repository-mongo.js +269 -0
  16. package/credential/repositories/credential-repository-postgres.js +291 -0
  17. package/credential/repositories/credential-repository.js +302 -0
  18. package/credential/use-cases/get-credential-for-user.js +25 -0
  19. package/credential/use-cases/update-authentication-status.js +15 -0
  20. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  21. package/database/adapters/lambda-invoker.js +97 -0
  22. package/database/config.js +154 -0
  23. package/database/documentdb-encryption-service.js +330 -0
  24. package/database/documentdb-utils.js +136 -0
  25. package/database/encryption/README.md +839 -0
  26. package/database/encryption/documentdb-encryption-service.md +3575 -0
  27. package/database/encryption/encryption-schema-registry.js +268 -0
  28. package/database/encryption/field-encryption-service.js +226 -0
  29. package/database/encryption/logger.js +79 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/index.js +61 -21
  32. package/database/models/WebsocketConnection.js +16 -10
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +134 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/health-check-repository.js +108 -0
  41. package/database/repositories/migration-status-repository-s3.js +137 -0
  42. package/database/use-cases/check-database-health-use-case.js +29 -0
  43. package/database/use-cases/check-database-state-use-case.js +81 -0
  44. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  45. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  46. package/database/use-cases/get-migration-status-use-case.js +93 -0
  47. package/database/use-cases/run-database-migration-use-case.js +139 -0
  48. package/database/use-cases/test-encryption-use-case.js +253 -0
  49. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  50. package/database/utils/mongodb-collection-utils.js +91 -0
  51. package/database/utils/mongodb-schema-init.js +106 -0
  52. package/database/utils/prisma-runner.js +477 -0
  53. package/database/utils/prisma-schema-parser.js +182 -0
  54. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  55. package/encrypt/Cryptor.js +34 -168
  56. package/encrypt/index.js +1 -2
  57. package/encrypt/test-encrypt.js +0 -2
  58. package/errors/client-safe-error.js +26 -0
  59. package/errors/fetch-error.js +2 -1
  60. package/errors/index.js +2 -0
  61. package/generated/prisma-mongodb/client.d.ts +1 -0
  62. package/generated/prisma-mongodb/client.js +4 -0
  63. package/generated/prisma-mongodb/default.d.ts +1 -0
  64. package/generated/prisma-mongodb/default.js +4 -0
  65. package/generated/prisma-mongodb/edge.d.ts +1 -0
  66. package/generated/prisma-mongodb/edge.js +334 -0
  67. package/generated/prisma-mongodb/index-browser.js +316 -0
  68. package/generated/prisma-mongodb/index.d.ts +22903 -0
  69. package/generated/prisma-mongodb/index.js +359 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  72. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  73. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  74. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  75. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  76. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  77. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  78. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  79. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  80. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  81. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  82. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  83. package/generated/prisma-mongodb/schema.prisma +360 -0
  84. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  85. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  86. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  87. package/generated/prisma-mongodb/wasm.js +341 -0
  88. package/generated/prisma-postgresql/client.d.ts +1 -0
  89. package/generated/prisma-postgresql/client.js +4 -0
  90. package/generated/prisma-postgresql/default.d.ts +1 -0
  91. package/generated/prisma-postgresql/default.js +4 -0
  92. package/generated/prisma-postgresql/edge.d.ts +1 -0
  93. package/generated/prisma-postgresql/edge.js +356 -0
  94. package/generated/prisma-postgresql/index-browser.js +338 -0
  95. package/generated/prisma-postgresql/index.d.ts +25077 -0
  96. package/generated/prisma-postgresql/index.js +381 -0
  97. package/generated/prisma-postgresql/package.json +183 -0
  98. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  99. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  100. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  101. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  102. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  103. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  104. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  105. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  106. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  107. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  108. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  109. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  110. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  111. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  112. package/generated/prisma-postgresql/schema.prisma +343 -0
  113. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  114. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  115. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  116. package/generated/prisma-postgresql/wasm.js +363 -0
  117. package/handlers/WEBHOOKS.md +653 -0
  118. package/handlers/app-definition-loader.js +38 -0
  119. package/handlers/app-handler-helpers.js +56 -0
  120. package/handlers/backend-utils.js +186 -0
  121. package/handlers/database-migration-handler.js +227 -0
  122. package/handlers/integration-event-dispatcher.js +54 -0
  123. package/handlers/routers/HEALTHCHECK.md +342 -0
  124. package/handlers/routers/auth.js +15 -0
  125. package/handlers/routers/db-migration.handler.js +29 -0
  126. package/handlers/routers/db-migration.js +326 -0
  127. package/handlers/routers/health.js +516 -0
  128. package/handlers/routers/integration-defined-routers.js +45 -0
  129. package/handlers/routers/integration-webhook-routers.js +67 -0
  130. package/handlers/routers/user.js +63 -0
  131. package/handlers/routers/websocket.js +57 -0
  132. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  133. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  134. package/handlers/workers/db-migration.js +352 -0
  135. package/handlers/workers/integration-defined-workers.js +27 -0
  136. package/index.js +77 -22
  137. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  138. package/integrations/index.js +12 -10
  139. package/integrations/integration-base.js +326 -55
  140. package/integrations/integration-router.js +374 -179
  141. package/integrations/options.js +1 -1
  142. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  143. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  144. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  145. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  146. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  147. package/integrations/repositories/integration-mapping-repository.js +156 -0
  148. package/integrations/repositories/integration-repository-documentdb.js +210 -0
  149. package/integrations/repositories/integration-repository-factory.js +51 -0
  150. package/integrations/repositories/integration-repository-interface.js +127 -0
  151. package/integrations/repositories/integration-repository-mongo.js +303 -0
  152. package/integrations/repositories/integration-repository-postgres.js +352 -0
  153. package/integrations/repositories/process-repository-documentdb.js +243 -0
  154. package/integrations/repositories/process-repository-factory.js +53 -0
  155. package/integrations/repositories/process-repository-interface.js +90 -0
  156. package/integrations/repositories/process-repository-mongo.js +190 -0
  157. package/integrations/repositories/process-repository-postgres.js +217 -0
  158. package/integrations/tests/doubles/dummy-integration-class.js +83 -0
  159. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  160. package/integrations/use-cases/create-integration.js +83 -0
  161. package/integrations/use-cases/create-process.js +128 -0
  162. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  163. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  164. package/integrations/use-cases/get-integration-for-user.js +78 -0
  165. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  166. package/integrations/use-cases/get-integration-instance.js +83 -0
  167. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  168. package/integrations/use-cases/get-possible-integrations.js +27 -0
  169. package/integrations/use-cases/get-process.js +87 -0
  170. package/integrations/use-cases/index.js +19 -0
  171. package/integrations/use-cases/load-integration-context.js +71 -0
  172. package/integrations/use-cases/update-integration-messages.js +44 -0
  173. package/integrations/use-cases/update-integration-status.js +32 -0
  174. package/integrations/use-cases/update-integration.js +93 -0
  175. package/integrations/use-cases/update-process-metrics.js +201 -0
  176. package/integrations/use-cases/update-process-state.js +119 -0
  177. package/integrations/utils/map-integration-dto.js +37 -0
  178. package/jest-global-setup-noop.js +3 -0
  179. package/jest-global-teardown-noop.js +3 -0
  180. package/logs/logger.js +0 -4
  181. package/{module-plugin → modules}/entity.js +1 -1
  182. package/{module-plugin → modules}/index.js +0 -8
  183. package/modules/module-factory.js +56 -0
  184. package/modules/module.js +221 -0
  185. package/modules/repositories/module-repository-documentdb.js +307 -0
  186. package/modules/repositories/module-repository-factory.js +40 -0
  187. package/modules/repositories/module-repository-interface.js +129 -0
  188. package/modules/repositories/module-repository-mongo.js +377 -0
  189. package/modules/repositories/module-repository-postgres.js +426 -0
  190. package/modules/repositories/module-repository.js +316 -0
  191. package/modules/requester/api-key.js +52 -0
  192. package/{module-plugin → modules}/requester/requester.js +1 -0
  193. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  194. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  195. package/modules/tests/doubles/test-module-factory.js +16 -0
  196. package/modules/tests/doubles/test-module-repository.js +39 -0
  197. package/modules/use-cases/get-entities-for-user.js +32 -0
  198. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  199. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  200. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  201. package/modules/use-cases/get-module.js +74 -0
  202. package/modules/use-cases/process-authorization-callback.js +133 -0
  203. package/modules/use-cases/refresh-entity-options.js +72 -0
  204. package/modules/use-cases/test-module-auth.js +72 -0
  205. package/modules/utils/map-module-dto.js +18 -0
  206. package/package.json +82 -50
  207. package/prisma-mongodb/schema.prisma +360 -0
  208. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  209. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  210. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  211. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  212. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  213. package/prisma-postgresql/schema.prisma +343 -0
  214. package/queues/queuer-util.js +27 -22
  215. package/syncs/manager.js +468 -443
  216. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  217. package/syncs/repositories/sync-repository-factory.js +43 -0
  218. package/syncs/repositories/sync-repository-interface.js +109 -0
  219. package/syncs/repositories/sync-repository-mongo.js +239 -0
  220. package/syncs/repositories/sync-repository-postgres.js +319 -0
  221. package/syncs/sync.js +0 -1
  222. package/token/repositories/token-repository-documentdb.js +137 -0
  223. package/token/repositories/token-repository-factory.js +40 -0
  224. package/token/repositories/token-repository-interface.js +131 -0
  225. package/token/repositories/token-repository-mongo.js +219 -0
  226. package/token/repositories/token-repository-postgres.js +264 -0
  227. package/token/repositories/token-repository.js +219 -0
  228. package/types/core/index.d.ts +2 -2
  229. package/types/integrations/index.d.ts +2 -6
  230. package/types/module-plugin/index.d.ts +5 -59
  231. package/types/syncs/index.d.ts +0 -2
  232. package/user/repositories/user-repository-documentdb.js +441 -0
  233. package/user/repositories/user-repository-factory.js +52 -0
  234. package/user/repositories/user-repository-interface.js +201 -0
  235. package/user/repositories/user-repository-mongo.js +308 -0
  236. package/user/repositories/user-repository-postgres.js +360 -0
  237. package/user/tests/doubles/test-user-repository.js +72 -0
  238. package/user/use-cases/authenticate-user.js +127 -0
  239. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  240. package/user/use-cases/create-individual-user.js +61 -0
  241. package/user/use-cases/create-organization-user.js +47 -0
  242. package/user/use-cases/create-token-for-user-id.js +30 -0
  243. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  244. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  245. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  246. package/user/use-cases/login-user.js +122 -0
  247. package/user/user.js +125 -0
  248. package/utils/backend-path.js +38 -0
  249. package/utils/index.js +6 -0
  250. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  251. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  252. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  253. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  254. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  255. package/websocket/repositories/websocket-connection-repository.js +161 -0
  256. package/database/models/State.js +0 -9
  257. package/database/models/Token.js +0 -70
  258. package/database/mongo.js +0 -45
  259. package/encrypt/Cryptor.test.js +0 -32
  260. package/encrypt/encrypt.js +0 -132
  261. package/encrypt/encrypt.test.js +0 -1069
  262. package/errors/base-error.test.js +0 -32
  263. package/errors/fetch-error.test.js +0 -79
  264. package/errors/halt-error.test.js +0 -11
  265. package/errors/validation-errors.test.js +0 -120
  266. package/integrations/create-frigg-backend.js +0 -31
  267. package/integrations/integration-factory.js +0 -251
  268. package/integrations/integration-mapping.js +0 -43
  269. package/integrations/integration-model.js +0 -46
  270. package/integrations/integration-user.js +0 -144
  271. package/integrations/test/integration-base.test.js +0 -144
  272. package/lambda/TimeoutCatcher.test.js +0 -68
  273. package/logs/logger.test.js +0 -76
  274. package/module-plugin/auther.js +0 -393
  275. package/module-plugin/credential.js +0 -22
  276. package/module-plugin/entity-manager.js +0 -70
  277. package/module-plugin/manager.js +0 -169
  278. package/module-plugin/module-factory.js +0 -61
  279. package/module-plugin/requester/api-key.js +0 -36
  280. package/module-plugin/requester/requester.test.js +0 -28
  281. package/module-plugin/test/auther.test.js +0 -97
  282. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  283. /package/{module-plugin → modules}/requester/basic.js +0 -0
  284. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  285. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,839 @@
1
+ # Frigg Field-Level Encryption
2
+
3
+ Database-agnostic field-level encryption for Frigg using Prisma Client Extensions and AWS KMS/AES.
4
+
5
+ ## Overview
6
+
7
+ This module provides **transparent field-level encryption** for sensitive data in Frigg integrations. It works identically for MongoDB and PostgreSQL, using Prisma Client Extensions to automatically encrypt data on write and decrypt on read.
8
+
9
+ ### Key Features
10
+
11
+ - ✅ **Database-agnostic**: Works with MongoDB, PostgreSQL, and future databases
12
+ - ✅ **Transparent**: Repositories and use cases work with plain data
13
+ - ✅ **Hexagonal architecture**: Clean separation of concerns
14
+ - ✅ **AWS KMS support**: Enterprise-grade encryption with AWS Key Management Service
15
+ - ✅ **Local AES fallback**: Development mode using local encryption keys
16
+ - ✅ **Environment-based**: Automatic bypass in dev/test/local environments
17
+ - ✅ **Envelope encryption**: Secure key management pattern
18
+
19
+ ## Architecture
20
+
21
+ ### Hexagonal Layers
22
+
23
+ ```
24
+ Application Layer (Use Cases)
25
+ ↓ works with plain data
26
+ Infrastructure Layer (Repositories)
27
+ ↓ works with plain data
28
+ Infrastructure Layer (Prisma Extension)
29
+ ↓ transparent encrypt/decrypt
30
+ Infrastructure Layer (Cryptor)
31
+ ↓ calls AWS KMS or crypto library
32
+ External Systems (AWS KMS, Database)
33
+ ```
34
+
35
+ ### Components
36
+
37
+ 1. **encryption-schema-registry.js** - Defines which fields are encrypted
38
+ 2. **field-encryption-service.js** - Orchestrates field-level encryption
39
+ 3. **prisma-encryption-extension.js** - Prisma Client Extension for transparent encryption
40
+ 4. **Cryptor.js** (`../encrypt/`) - Adapter for AWS KMS and AES encryption
41
+
42
+ ## Configuration
43
+
44
+ ### Database Selection
45
+
46
+ Database type is configured in `backend/index.js` app definition:
47
+
48
+ ```javascript
49
+ const appDefinition = {
50
+ database: {
51
+ mongoDB: {
52
+ enable: true, // Use MongoDB
53
+ },
54
+ documentDB: {
55
+ enable: false, // Use DocumentDB (MongoDB-compatible)
56
+ tlsCAFile: './security/global-bundle.pem',
57
+ },
58
+ postgres: {
59
+ enable: false, // Use PostgreSQL
60
+ },
61
+ },
62
+ // ... other config
63
+ };
64
+ ```
65
+
66
+ **Important**: Only enable ONE database at a time. The framework will use the first enabled database in this priority order:
67
+
68
+ 1. PostgreSQL (`postgres.enable = true`)
69
+ 2. MongoDB (`mongoDB.enable = true`)
70
+ 3. DocumentDB (`documentDB.enable = true`)
71
+
72
+ ### Encryption Configuration
73
+
74
+ In `backend/index.js`:
75
+
76
+ ```javascript
77
+ const appDefinition = {
78
+ encryption: {
79
+ fieldLevelEncryptionMethod: 'kms', // or 'aes'
80
+ createResourceIfNoneFound: true, // Auto-create KMS key if missing
81
+ },
82
+ // ... other config
83
+ };
84
+ ```
85
+
86
+ ### Environment Variables
87
+
88
+ #### Production (AWS KMS)
89
+
90
+ ```bash
91
+ # AWS KMS encryption (recommended for production)
92
+ KMS_KEY_ARN=arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
93
+ STAGE=production
94
+ ```
95
+
96
+ The `KMS_KEY_ARN` is usually auto-discovered by Frigg infrastructure:
97
+
98
+ - Set by AWS discovery: `AWS_DISCOVERY_KMS_KEY_ARN`
99
+ - Copied to `KMS_KEY_ARN` during deployment
100
+
101
+ #### AES Encryption
102
+
103
+ ```bash
104
+ # AES encryption (can be used in any environment including production)
105
+ AES_KEY_ID=local-dev-key
106
+ AES_KEY=your-32-character-secret-key-here
107
+ STAGE=production # or development, staging, etc.
108
+ ```
109
+
110
+ **⚠️ Important**: Encryption is automatically **disabled** when `STAGE` is set to `dev`, `test`, or `local`, regardless of key configuration.
111
+
112
+ ### Bypass Encryption
113
+
114
+ To explicitly disable encryption:
115
+
116
+ ```bash
117
+ # Disable encryption (development only)
118
+ STAGE=development # or dev, test, local
119
+ ```
120
+
121
+ Or simply don't configure any encryption keys. In Production field level encryption **must** be enabled.
122
+
123
+ ## Encrypted Fields
124
+
125
+ Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
126
+
127
+ **Core fields include**:
128
+ - OAuth tokens: `access_token`, `refresh_token`, `id_token`
129
+ - API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
130
+ - Basic auth: `password`
131
+ - OAuth client credentials: `client_secret`
132
+
133
+ **Note**: API modules should use `api_key` (snake_case) in their `apiPropertiesToPersist.credential` arrays for consistency with OAuth2Requester and BasicAuthRequester conventions.
134
+
135
+ ### API Module Credential Naming Conventions
136
+
137
+ When creating API module definitions, use **snake_case** for credential property names to ensure automatic encryption:
138
+
139
+ **✅ Recommended (automatically encrypted):**
140
+ ```javascript
141
+ // API Module Definition
142
+ const Definition = {
143
+ requiredAuthMethods: {
144
+ apiPropertiesToPersist: {
145
+ // For API key authentication
146
+ credential: ['api_key'], // ✅ Automatically encrypted
147
+ // or for OAuth authentication
148
+ credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
149
+ // or for Basic authentication
150
+ credential: ['username', 'password'], // ✅ Basic auth - encrypted
151
+ }
152
+ }
153
+ };
154
+
155
+ // API class (extends ApiKeyRequester)
156
+ class MyApi extends ApiKeyRequester {
157
+ constructor(params) {
158
+ super(params);
159
+ this.api_key = params.api_key; // ✅ snake_case convention
160
+ }
161
+ }
162
+ ```
163
+
164
+ **❌ Avoid (requires manual encryption schema):**
165
+ ```javascript
166
+ apiPropertiesToPersist: {
167
+ credential: ['customToken', 'proprietaryKey'] // ❌ Not in core schema
168
+ }
169
+ ```
170
+
171
+ For custom credential fields not in the core schema, use the custom encryption schema feature (see below).
172
+
173
+ ### Extending Encryption Schema
174
+
175
+ #### Option 1: Module-Level Encryption (API Module Developers)
176
+
177
+ **NEW**: API modules can now declare their encryption requirements directly in the module definition:
178
+
179
+ ```javascript
180
+ // api-module-library/my-service/definition.js
181
+ const Definition = {
182
+ moduleName: 'myService',
183
+ API: MyServiceApi,
184
+
185
+ // Declare which credential fields need encryption
186
+ encryption: {
187
+ credentialFields: ['api_key', 'webhook_secret']
188
+ },
189
+
190
+ requiredAuthMethods: {
191
+ apiPropertiesToPersist: {
192
+ credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
193
+ entity: []
194
+ },
195
+ // ... other methods
196
+ }
197
+ };
198
+ ```
199
+
200
+ **How it works**:
201
+ 1. Module declares `encryption.credentialFields` array
202
+ 2. Framework automatically adds `data.` prefix: `['api_key']` → `['data.api_key']`
203
+ 3. Fields are merged with core encryption schema on app startup
204
+ 4. All modules across all integrations are scanned and combined
205
+
206
+ **Benefits**:
207
+ - ✅ Module authors control their own security requirements
208
+ - ✅ No need to modify core framework or app configuration
209
+ - ✅ Automatic encryption for API key-based integrations
210
+ - ✅ Works seamlessly with `apiPropertiesToPersist`
211
+
212
+ **Example - API Key Module**:
213
+ ```javascript
214
+ // API Module Definition
215
+ const Definition = {
216
+ moduleName: 'axiscare',
217
+ API: AxisCareApi,
218
+ encryption: {
219
+ credentialFields: ['api_key'] // Auto-encrypted as 'data.api_key'
220
+ },
221
+ requiredAuthMethods: {
222
+ apiPropertiesToPersist: {
223
+ credential: ['api_key'] // Will be encrypted automatically
224
+ }
225
+ }
226
+ };
227
+
228
+ // API Class (extends ApiKeyRequester)
229
+ class AxisCareApi extends ApiKeyRequester {
230
+ constructor(params) {
231
+ super(params);
232
+ this.api_key = params.api_key; // snake_case convention
233
+ }
234
+ }
235
+ ```
236
+
237
+ **Example - Custom Authentication**:
238
+ ```javascript
239
+ const Definition = {
240
+ moduleName: 'customService',
241
+ encryption: {
242
+ credentialFields: [
243
+ 'signing_key',
244
+ 'webhook_secret',
245
+ 'data.custom_nested_field' // Can specify data. prefix explicitly
246
+ ]
247
+ }
248
+ };
249
+ ```
250
+
251
+ **Limitations**:
252
+ - Only supports Credential model fields (stored in `credential.data`)
253
+ - Cannot encrypt entity fields or custom models (use app-level schema for those)
254
+ - Applied globally once - module schemas loaded at app startup
255
+
256
+ #### Option 2: App-Level Custom Schema (Integration Developers)
257
+
258
+ Integration developers can extend encryption without modifying core framework files.
259
+
260
+ **In `backend/index.js`:**
261
+
262
+ ```javascript
263
+ const appDefinition = {
264
+ encryption: {
265
+ fieldLevelEncryptionMethod: 'kms',
266
+ createResourceIfNoneFound: true,
267
+
268
+ // Custom encryption schema
269
+ schema: {
270
+ // Your custom models
271
+ MyCustomModel: {
272
+ fields: ['secretData', 'data.apiKey'],
273
+ },
274
+
275
+ // Extend core models with additional fields
276
+ Credential: {
277
+ fields: ['data.customToken'], // Merged with core fields
278
+ },
279
+ },
280
+ },
281
+ integrations: [MyIntegration],
282
+ // ... rest of config
283
+ };
284
+ ```
285
+
286
+ **Features:**
287
+
288
+ - ✅ No framework file modifications needed
289
+ - ✅ Encryption for custom Prisma models
290
+ - ✅ Extends core models with additional fields
291
+ - ✅ Automatic validation on startup
292
+ - ✅ Protects against overriding core encrypted fields
293
+
294
+ **Example with Custom Model:**
295
+
296
+ ```javascript
297
+ // 1. Define custom Prisma model (in your backend prisma schema)
298
+ model AsanaTaskMapping {
299
+ id Int @id @default(autoincrement())
300
+ taskGid String
301
+ webhookToken String // Sensitive!
302
+ customApiSecret String // Sensitive!
303
+ metadata Json
304
+ }
305
+
306
+ // 2. Add to encryption schema in backend/index.js
307
+ const appDefinition = {
308
+ encryption: {
309
+ fieldLevelEncryptionMethod: 'kms',
310
+ schema: {
311
+ AsanaTaskMapping: {
312
+ fields: [
313
+ 'webhookToken',
314
+ 'customApiSecret'
315
+ ]
316
+ }
317
+ }
318
+ }
319
+ };
320
+
321
+ // 3. Use normally in your repositories - encryption is automatic!
322
+ await prisma.asanaTaskMapping.create({
323
+ data: {
324
+ webhookToken: 'secret123', // Auto-encrypted
325
+ customApiSecret: 'api-key' // Auto-encrypted
326
+ }
327
+ });
328
+ ```
329
+
330
+ **Validation:**
331
+
332
+ - Invalid field paths → Error on startup with clear message
333
+ - Attempting to override core fields → Error on startup
334
+ - Empty/null schema → Silently ignored
335
+
336
+ **Debug:**
337
+
338
+ ```bash
339
+ # Enable debug logging to see custom schema loading
340
+ FRIGG_DEBUG=1 npm run frigg:start
341
+ ```
342
+
343
+ #### Option 3: Modifying Core Schema (Framework Developers)
344
+
345
+ Framework developers maintaining core models can modify `encryption-schema-registry.js`:
346
+
347
+ 1. Open `encryption-schema-registry.js`
348
+ 2. Add field to `CORE_ENCRYPTION_SCHEMA`:
349
+
350
+ ```javascript
351
+ const CORE_ENCRYPTION_SCHEMA = {
352
+ Credential: {
353
+ fields: [
354
+ 'data.access_token',
355
+ 'data.refresh_token',
356
+ 'data.new_core_field', // New core field
357
+ ],
358
+ },
359
+ };
360
+ ```
361
+
362
+ 3. Deploy - encryption applied automatically to all integrations
363
+
364
+ **When to use:**
365
+
366
+ - Adding encryption for new framework-level sensitive fields
367
+ - Adding new core models (User, Token, etc.)
368
+ - Security baseline changes affecting all integrations
369
+
370
+ **When NOT to use:**
371
+
372
+ - Integration-specific sensitive data (use custom schema instead)
373
+ - Temporary/experimental encryption (use custom schema instead)
374
+
375
+ #### After Adding Encrypted Fields
376
+
377
+ After adding fields to `encryption-schema-registry.js`:
378
+
379
+ 1. **For MongoDB/PostgreSQL**: No code changes needed (automatic via Prisma Extension)
380
+ 2. **For DocumentDB**: Encryption is automatic via DocumentDBEncryptionService
381
+ (service reads from same registry)
382
+
383
+ ## How It Works
384
+
385
+ ### Write Operation (Create/Update)
386
+
387
+ ```javascript
388
+ // Application code (use case or repository)
389
+ await prisma.credential.create({
390
+ data: {
391
+ data: { access_token: 'secret123' },
392
+ },
393
+ });
394
+
395
+ // What happens:
396
+ // 1. Prisma extension intercepts query
397
+ // 2. FieldEncryptionService encrypts matching fields
398
+ // 3. Cryptor generates data key via KMS
399
+ // 4. Cryptor encrypts value with data key
400
+ // 5. Database stores: { data: { access_token: 'keyId:iv:cipher:encKey' }}
401
+ // 6. Extension decrypts return value
402
+ // 7. Application receives: { data: { access_token: 'secret123' }}
403
+ ```
404
+
405
+ ### Read Operation (Find)
406
+
407
+ ```javascript
408
+ // Application code
409
+ const credential = await prisma.credential.findUnique({
410
+ where: { id: credentialId },
411
+ });
412
+
413
+ // What happens:
414
+ // 1. Prisma queries database
415
+ // 2. Database returns encrypted data
416
+ // 3. Extension intercepts result
417
+ // 4. FieldEncryptionService decrypts matching fields
418
+ // 5. Cryptor decrypts with KMS
419
+ // 6. Application receives plain data
420
+ ```
421
+
422
+ ### Encryption Format
423
+
424
+ Encrypted values use **envelope encryption**:
425
+
426
+ ```
427
+ Format: "keyId:encryptedText:encryptedKey"
428
+ Example: "base64KeyId:iv:ciphertext:base64EncryptedDataKey"
429
+ ```
430
+
431
+ **Why Envelope Encryption?**
432
+
433
+ - Reduces KMS API calls (one DEK per field, cached)
434
+ - Master key never leaves KMS
435
+ - Enables key rotation without re-encrypting all data
436
+ - Better performance at scale
437
+
438
+ ### Known Limitations
439
+
440
+ #### Prisma Relations with `include` Bypass Decryption
441
+
442
+ **⚠️ Critical**: When using Prisma's `include` option to fetch related models, the encryption extension **cannot decrypt** nested relation data.
443
+
444
+ **Problem:**
445
+
446
+ ```javascript
447
+ // ❌ WRONG: Credential will NOT be decrypted
448
+ const entity = await prisma.entity.findUnique({
449
+ where: { id: entityId },
450
+ include: { credential: true }, // Nested credential stays encrypted!
451
+ });
452
+
453
+ // entity.credential.data.access_token will be encrypted:
454
+ // "keyId:iv:ciphertext:encKey" instead of plain text
455
+ ```
456
+
457
+ **Root Cause:**
458
+
459
+ The Prisma encryption extension hooks into top-level model queries via `$allModels`. When you use `include`, Prisma internally fetches the nested relation, but the extension only sees the parent model name (`Entity`), not the nested model (`Credential`). Therefore, the `Credential` data bypasses the decryption logic.
460
+
461
+ **Solution:**
462
+
463
+ Always fetch relations with **separate queries**:
464
+
465
+ ```javascript
466
+ // ✅ CORRECT: Fetch entity and credential separately
467
+ const entity = await prisma.entity.findUnique({
468
+ where: { id: entityId },
469
+ });
470
+
471
+ // Separate query ensures decryption
472
+ const credential = await prisma.credential.findUnique({
473
+ where: { id: entity.credentialId },
474
+ });
475
+
476
+ // Combine in application layer
477
+ return {
478
+ ...entity,
479
+ credential, // Now properly decrypted
480
+ };
481
+ ```
482
+
483
+ **Best Practice (Bulk Operations):**
484
+
485
+ For fetching multiple entities with credentials, use bulk fetching to avoid N+1 queries:
486
+
487
+ ```javascript
488
+ // Fetch all entities
489
+ const entities = await prisma.entity.findMany({
490
+ where: { userId },
491
+ });
492
+
493
+ // Bulk fetch credentials (single query)
494
+ const credentialIds = entities.map((e) => e.credentialId).filter(Boolean);
495
+ const credentials = await prisma.credential.findMany({
496
+ where: { id: { in: credentialIds } },
497
+ });
498
+
499
+ // Create lookup map
500
+ const credentialMap = new Map(credentials.map((c) => [c.id, c]));
501
+
502
+ // Combine in application layer
503
+ return entities.map((e) => ({
504
+ ...e,
505
+ credential: credentialMap.get(e.credentialId) || null,
506
+ }));
507
+ ```
508
+
509
+ **Verified:**
510
+
511
+ - ✅ `postgres-relation-decryption.test.js` - Proves the bug exists
512
+ - ✅ `postgres-decryption-fix-verification.test.js` - Verifies separate queries work
513
+ - ✅ `mongo-decryption-fix-verification.test.js` - Verifies fix for MongoDB
514
+
515
+ **Implementation Examples:**
516
+
517
+ See `modules/repositories/module-repository-postgres.js` and `module-repository-mongo.js` for complete implementation examples using `_fetchCredential()` and `_fetchCredentialsBulk()` helper methods.
518
+
519
+ ## DocumentDB Encryption
520
+
521
+ ### Why DocumentDB Needs Manual Encryption
522
+
523
+ DocumentDB repositories use `$runCommandRaw()` for MongoDB protocol compatibility, which bypasses Prisma Client Extensions. This means the automatic encryption extension does not apply.
524
+
525
+ ### DocumentDBEncryptionService
526
+
527
+ For DocumentDB repositories, use `DocumentDBEncryptionService` to manually encrypt/decrypt documents before/after database operations.
528
+
529
+ #### Usage Example
530
+
531
+ ```javascript
532
+ const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
533
+ const { insertOne, findOne } = require('../documentdb-utils');
534
+
535
+ class MyRepositoryDocumentDB {
536
+ constructor() {
537
+ this.encryptionService = new DocumentDBEncryptionService();
538
+ }
539
+
540
+ async create(data) {
541
+ // Encrypt before write
542
+ const encrypted = await this.encryptionService.encryptFields('ModelName', data);
543
+ const id = await insertOne(this.prisma, 'CollectionName', encrypted);
544
+
545
+ // Decrypt after read
546
+ const doc = await findOne(this.prisma, 'CollectionName', { _id: id });
547
+ const decrypted = await this.encryptionService.decryptFields('ModelName', doc);
548
+
549
+ return decrypted;
550
+ }
551
+ }
552
+ ```
553
+
554
+ #### Configuration
555
+
556
+ Uses the same environment variables and Cryptor as the Prisma Extension:
557
+ - `STAGE`: Bypasses encryption for dev/test/local
558
+ - `KMS_KEY_ARN`: AWS KMS encryption (production)
559
+ - `AES_KEY_ID` + `AES_KEY`: AES encryption (fallback)
560
+
561
+ ## Usage Examples
562
+
563
+ ### Repository Code (No Changes Needed!)
564
+
565
+ ```javascript
566
+ // Repositories work with plain data - encryption is transparent
567
+ class CredentialRepository {
568
+ async upsertCredential({ identifiers, details }) {
569
+ // details.data.access_token is plain text here
570
+ const credential = await prisma.credential.upsert({
571
+ where: identifiers,
572
+ create: details,
573
+ update: details,
574
+ });
575
+
576
+ // credential.data.access_token is plain text here (auto-decrypted)
577
+ return credential;
578
+ }
579
+ }
580
+ ```
581
+
582
+ ### Use Case Code (No Changes Needed!)
583
+
584
+ ```javascript
585
+ // Use cases work with plain data - encryption is transparent
586
+ class AuthenticateUserUseCase {
587
+ async execute({ userId, accessToken }) {
588
+ // accessToken is plain text
589
+ await this.credentialRepo.upsertCredential({
590
+ identifiers: { userId },
591
+ details: {
592
+ data: {
593
+ access_token: accessToken, // Plain text
594
+ },
595
+ },
596
+ });
597
+
598
+ // Stored as encrypted, but we work with plain text
599
+ }
600
+ }
601
+ ```
602
+
603
+ ### Testing Encryption
604
+
605
+ Use the health check endpoint to verify encryption:
606
+
607
+ ```bash
608
+ # Check if encryption is working
609
+ curl http://localhost:3000/health/test-encryption
610
+
611
+ # Response when encryption enabled:
612
+ {
613
+ "status": "enabled",
614
+ "testResult": "Encryption and decryption verified successfully",
615
+ "encryptionWorks": true
616
+ }
617
+
618
+ # Response when encryption disabled:
619
+ {
620
+ "status": "disabled",
621
+ "reason": "Encryption bypassed for stage: development"
622
+ }
623
+ ```
624
+
625
+ ## Testing
626
+
627
+ ### Unit Tests
628
+
629
+ ```bash
630
+ # Test encryption schema registry
631
+ npm test -- database/encryption/encryption-schema-registry.test.js
632
+
633
+ # Test field encryption service
634
+ npm test -- database/encryption/field-encryption-service.test.js
635
+
636
+ # Test Prisma extension
637
+ npm test -- database/encryption/prisma-encryption-extension.test.js
638
+
639
+ # Test all encryption
640
+ npm test -- database/encryption/
641
+ ```
642
+
643
+ ### Integration Tests
644
+
645
+ Database type is determined from your app definition in `backend/index.js`:
646
+
647
+ ```javascript
648
+ // backend/index.js
649
+ database: {
650
+ mongoDB: { enable: true }, // For MongoDB tests
651
+ postgres: { enable: false }
652
+ }
653
+ ```
654
+
655
+ ```bash
656
+ # Run encryption tests
657
+ npm test -- database/encryption/
658
+
659
+ # Tests use explicit prismaClient injection:
660
+ # const { prisma } = require('../prisma');
661
+ # const repository = createHealthCheckRepository({ prismaClient: prisma });
662
+ ```
663
+
664
+ ## Error Handling & Logging
665
+
666
+ ### Error Handling Strategy
667
+
668
+ The encryption system uses **fail-fast error handling**:
669
+
670
+ - **Encryption failures**: Throw errors immediately (don't save corrupted/unencrypted sensitive data)
671
+ - **Decryption failures**: Throw errors immediately (prevents exposing invalid data)
672
+ - **Configuration errors**: Warn and disable encryption (graceful degradation for development)
673
+ - **Validation errors**: Throw errors on startup (catch issues before production)
674
+
675
+ **Why fail-fast?**
676
+
677
+ - Security-critical operations must not silently fail
678
+ - Better to expose issues during development than risk data breaches
679
+ - Prevents inconsistent database state (partially encrypted data)
680
+
681
+ ### Logging Configuration
682
+
683
+ Configure log verbosity with `FRIGG_LOG_LEVEL`:
684
+
685
+ ```bash
686
+ # Production (minimal logging)
687
+ FRIGG_LOG_LEVEL=WARN
688
+
689
+ # Development (detailed logging)
690
+ FRIGG_LOG_LEVEL=DEBUG
691
+
692
+ # Default
693
+ FRIGG_LOG_LEVEL=INFO
694
+ ```
695
+
696
+ **Log Levels:**
697
+
698
+ - `DEBUG`: Detailed encryption operations (includes schema loading, key checks)
699
+ - `INFO`: High-level status (encryption enabled/disabled, custom schema registration)
700
+ - `WARN`: Configuration issues (missing keys, bypassed encryption)
701
+ - `ERROR`: Operation failures (encryption/decryption errors)
702
+
703
+ **Production Safety:**
704
+
705
+ - Sensitive data automatically sanitized in logs
706
+ - Long base64 strings truncated (prevents key leakage)
707
+ - Stack traces omitted in production (`STAGE=production`)
708
+ - Key IDs never logged
709
+
710
+ ### Performance Optimizations
711
+
712
+ **Parallel field encryption:**
713
+
714
+ - Multiple fields encrypted concurrently using `Promise.all()`
715
+ - Significantly faster for models with many encrypted fields
716
+ - Example: 3 fields encrypted in ~30ms vs ~90ms (3x speedup)
717
+
718
+ **Deep cloning:**
719
+
720
+ - Uses native `structuredClone()` on Node.js 17+ (2-5x faster)
721
+ - Falls back to custom implementation for compatibility
722
+ - No external dependencies required
723
+
724
+ ## Troubleshooting
725
+
726
+ ### Encryption Not Working
727
+
728
+ **Check environment variables:**
729
+
730
+ ```bash
731
+ echo $STAGE # Should be 'production' (not dev/test/local)
732
+ echo $KMS_KEY_ARN # Should be set (for KMS)
733
+ echo $AES_KEY_ID # Should be set (for AES)
734
+ ```
735
+
736
+ **Check console logs:**
737
+
738
+ ```
739
+ [Frigg] Field-level encryption enabled using KMS
740
+ ```
741
+
742
+ or
743
+
744
+ ```
745
+ [Frigg] Field-level encryption disabled
746
+ ```
747
+
748
+ ### AWS KMS Errors
749
+
750
+ **Error: "User is not authorized to perform: kms:GenerateDataKey"**
751
+
752
+ Solution: Add KMS permissions to Lambda execution role:
753
+
754
+ ```json
755
+ {
756
+ "Effect": "Allow",
757
+ "Action": ["kms:GenerateDataKey", "kms:Decrypt"],
758
+ "Resource": "arn:aws:kms:*:*:key/*"
759
+ }
760
+ ```
761
+
762
+ **Error: "KMS key not found"**
763
+
764
+ Solution: Check `KMS_KEY_ARN` environment variable:
765
+
766
+ ```bash
767
+ aws kms describe-key --key-id $KMS_KEY_ARN
768
+ ```
769
+
770
+ ### Local AES Errors
771
+
772
+ **Error: "No encryption key found with ID"**
773
+
774
+ Solution: Set both `AES_KEY_ID` and `AES_KEY`:
775
+
776
+ ```bash
777
+ export AES_KEY_ID=local-dev-key
778
+ export AES_KEY=$(openssl rand -hex 16) # Generate 32-char key
779
+ ```
780
+
781
+ ### Performance Issues
782
+
783
+ **Symptom: Slow queries with encryption**
784
+
785
+ - Check KMS API throttling (CloudWatch metrics)
786
+ - Consider data key caching (future enhancement)
787
+ - Verify proper field selection (don't encrypt unnecessary fields)
788
+
789
+ ### Data Migration
790
+
791
+ **Migrating from Mongoose encryption:**
792
+
793
+ 1. Export data with old encryption
794
+ 2. Decrypt using old Mongoose plugin
795
+ 3. Re-import with new Prisma encryption
796
+ 4. Verify with `/health/test-encryption`
797
+
798
+ ## Security Best Practices
799
+
800
+ ### DO
801
+
802
+ ✅ Use AWS KMS for production (recommended) or AES encryption (valid alternative)
803
+ ✅ Rotate KMS keys regularly (AWS handles automatically)
804
+ ✅ Restrict KMS key access to Lambda execution role only
805
+ ✅ Use VPC endpoints for KMS (reduce NAT costs)
806
+ ✅ Monitor KMS API usage (CloudWatch)
807
+ ✅ Test encryption with health check endpoint
808
+
809
+ ### DON'T
810
+
811
+ ❌ Store AES keys in code or git (use environment variables)
812
+ ❌ Disable encryption in production
813
+ ❌ Skip encryption for PII data
814
+ ❌ Query on encrypted fields (not supported)
815
+ ❌ Manually decrypt data (use extension)
816
+
817
+ ## Future Enhancements
818
+
819
+ ### Planned
820
+
821
+ - [ ] Data key caching (reduce KMS API calls)
822
+ - [ ] Key rotation automation
823
+ - [ ] Encryption metrics (CloudWatch)
824
+ - [ ] Field-level audit logging
825
+ - [ ] Support for queryable encryption (MongoDB CSFLE)
826
+
827
+ ### Under Consideration
828
+
829
+ - [ ] Multi-region KMS replication
830
+ - [ ] Client-side field level encryption
831
+ - [ ] Encryption at rest + in transit
832
+ - [ ] Compliance reporting (GDPR, HIPAA)
833
+
834
+ ## Related Documentation
835
+
836
+ - [Prisma Client Extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions)
837
+ - [AWS KMS Envelope Encryption](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping)
838
+ - [Frigg Infrastructure](../../../devtools/infrastructure/CLAUDE.md)
839
+ - [Hexagonal Architecture](../../CLAUDE.md#dddhexagonal-architecture-patterns)