@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,219 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const bcrypt = require('bcryptjs');
3
+ const { TokenRepositoryInterface } = require('./token-repository-interface');
4
+ const { ClientSafeError } = require('../../errors');
5
+
6
+ const BCRYPT_ROUNDS = 10;
7
+
8
+ /**
9
+ * MongoDB Token Repository Adapter
10
+ * Handles persistence of authentication tokens with bcrypt hashing
11
+ *
12
+ * MongoDB-specific characteristics:
13
+ * - Uses String IDs (ObjectId)
14
+ * - No ID conversion needed (IDs are already strings)
15
+ * - Bcrypt hashing handled in repository layer
16
+ */
17
+ class TokenRepositoryMongo extends TokenRepositoryInterface {
18
+ constructor() {
19
+ super();
20
+ this.prisma = prisma;
21
+ }
22
+
23
+ /**
24
+ * Create a token with expiration
25
+ * Replaces: Token.createTokenWithExpire(userId, rawToken, minutes)
26
+ *
27
+ * @param {string} userId - The user ID
28
+ * @param {string} rawToken - The raw (unhashed) token string
29
+ * @param {number} minutes - Minutes until expiration
30
+ * @returns {Promise<Object>} The created token record with string IDs
31
+ */
32
+ async createTokenWithExpire(userId, rawToken, minutes) {
33
+ // Hash the token with bcrypt
34
+ const tokenHash = await bcrypt.hash(rawToken, BCRYPT_ROUNDS);
35
+
36
+ // Calculate expiration time
37
+ const expires = new Date(Date.now() + minutes * 60000);
38
+
39
+ return await this.prisma.token.create({
40
+ data: {
41
+ token: tokenHash,
42
+ expires,
43
+ userId,
44
+ },
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Validate and retrieve token from JSON token object
50
+ * Replaces: Token.validateAndGetTokenFromJSONToken(tokenObj)
51
+ *
52
+ * @param {Object} tokenObj - Object with id and token properties
53
+ * @returns {Promise<Object>} The validated token record with string IDs
54
+ * @throws {Error} If token is invalid, expired, or doesn't exist
55
+ */
56
+ async validateAndGetToken(tokenObj) {
57
+ const sessionToken = await this.prisma.token.findUnique({
58
+ where: { id: tokenObj.id },
59
+ });
60
+
61
+ if (!sessionToken) {
62
+ throw new ClientSafeError(
63
+ 'Invalid Token: Token does not exist',
64
+ 401
65
+ );
66
+ }
67
+
68
+ // Verify token hash matches
69
+ const isValid = await bcrypt.compare(
70
+ tokenObj.token,
71
+ sessionToken.token
72
+ );
73
+ if (!isValid) {
74
+ throw new ClientSafeError(
75
+ 'Invalid Token: Token does not match',
76
+ 401
77
+ );
78
+ }
79
+
80
+ // Check if token is expired
81
+ if (
82
+ sessionToken.expires &&
83
+ new Date(sessionToken.expires) < new Date()
84
+ ) {
85
+ throw new ClientSafeError('Invalid Token: Token is expired', 401);
86
+ }
87
+
88
+ return sessionToken;
89
+ }
90
+
91
+ /**
92
+ * Find a token by ID
93
+ * Replaces: Token.findById(tokenId)
94
+ *
95
+ * @param {string} tokenId - The token ID
96
+ * @returns {Promise<Object|null>} The token record with string IDs or null
97
+ */
98
+ async findTokenById(tokenId) {
99
+ return await this.prisma.token.findUnique({
100
+ where: { id: tokenId },
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Find tokens by user ID
106
+ * Replaces: Token.find({ user: userId })
107
+ *
108
+ * @param {string} userId - The user ID
109
+ * @returns {Promise<Array>} Array of token records with string IDs
110
+ */
111
+ async findTokensByUserId(userId) {
112
+ return await this.prisma.token.findMany({
113
+ where: { userId },
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Delete a token by ID
119
+ * Replaces: Token.deleteOne({ _id: tokenId })
120
+ *
121
+ * @param {string} tokenId - The token ID
122
+ * @returns {Promise<Object>} The deletion result
123
+ */
124
+ async deleteToken(tokenId) {
125
+ try {
126
+ await this.prisma.token.delete({
127
+ where: { id: tokenId },
128
+ });
129
+ return { acknowledged: true, deletedCount: 1 };
130
+ } catch (error) {
131
+ if (error.code === 'P2025') {
132
+ // Record not found
133
+ return { acknowledged: true, deletedCount: 0 };
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Delete expired tokens
141
+ * Replaces: Token.deleteMany({ expires: { $lt: new Date() } })
142
+ *
143
+ * @returns {Promise<Object>} The deletion result with count
144
+ */
145
+ async deleteExpiredTokens() {
146
+ const result = await this.prisma.token.deleteMany({
147
+ where: {
148
+ expires: {
149
+ lt: new Date(),
150
+ },
151
+ },
152
+ });
153
+
154
+ return {
155
+ acknowledged: true,
156
+ deletedCount: result.count,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Delete all tokens for a user
162
+ * Replaces: Token.deleteMany({ user: userId })
163
+ *
164
+ * @param {string} userId - The user ID
165
+ * @returns {Promise<Object>} The deletion result
166
+ */
167
+ async deleteTokensByUserId(userId) {
168
+ const result = await this.prisma.token.deleteMany({
169
+ where: { userId },
170
+ });
171
+
172
+ return {
173
+ acknowledged: true,
174
+ deletedCount: result.count,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Create JSON token string from token object and raw token
180
+ * Replaces: Token.createJSONToken(token, rawToken)
181
+ *
182
+ * @param {Object} token - The token record
183
+ * @param {string} rawToken - The raw token string
184
+ * @returns {string} JSON string with id and token
185
+ */
186
+ createJSONToken(token, rawToken) {
187
+ return JSON.stringify({
188
+ id: token.id,
189
+ token: rawToken,
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Create base64 encoded buffer token
195
+ * Replaces: Token.createBase64BufferToken(token, rawToken)
196
+ *
197
+ * @param {Object} token - The token record
198
+ * @param {string} rawToken - The raw token string
199
+ * @returns {string} Base64 encoded token
200
+ */
201
+ createBase64BufferToken(token, rawToken) {
202
+ const jsonVal = this.createJSONToken(token, rawToken);
203
+ return Buffer.from(jsonVal).toString('base64');
204
+ }
205
+
206
+ /**
207
+ * Parse JSON token from base64 buffer
208
+ * Replaces: Token.getJSONTokenFromBase64BufferToken(buffer)
209
+ *
210
+ * @param {string} buffer - Base64 encoded token string
211
+ * @returns {Object} Parsed token object with id and token
212
+ */
213
+ getJSONTokenFromBase64BufferToken(buffer) {
214
+ const tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
215
+ return JSON.parse(tokenStr);
216
+ }
217
+ }
218
+
219
+ module.exports = { TokenRepositoryMongo };
@@ -0,0 +1,264 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const bcrypt = require('bcryptjs');
3
+ const { TokenRepositoryInterface } = require('./token-repository-interface');
4
+ const { ClientSafeError } = require('../../errors');
5
+
6
+ const BCRYPT_ROUNDS = 10;
7
+
8
+ /**
9
+ * PostgreSQL Token Repository Adapter
10
+ * Handles persistence of authentication tokens with bcrypt hashing
11
+ *
12
+ * PostgreSQL-specific characteristics:
13
+ * - Uses Int IDs with autoincrement
14
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
15
+ * - All returned IDs are converted to strings for application layer consistency
16
+ */
17
+ class TokenRepositoryPostgres extends TokenRepositoryInterface {
18
+ constructor() {
19
+ super();
20
+ this.prisma = prisma;
21
+ }
22
+
23
+ /**
24
+ * Convert string ID to integer for PostgreSQL queries
25
+ * @private
26
+ * @param {string|number|null|undefined} id - ID to convert
27
+ * @returns {number|null|undefined} Integer ID or null/undefined
28
+ * @throws {Error} If ID cannot be converted to integer
29
+ */
30
+ _convertId(id) {
31
+ if (id === null || id === undefined) return id;
32
+ const parsed = parseInt(id, 10);
33
+ if (isNaN(parsed)) {
34
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
35
+ }
36
+ return parsed;
37
+ }
38
+
39
+ /**
40
+ * Convert token object IDs to strings
41
+ * @private
42
+ * @param {Object|null} token - Token object from database
43
+ * @returns {Object|null} Token with string IDs
44
+ */
45
+ _convertTokenIds(token) {
46
+ if (!token) return token;
47
+ return {
48
+ ...token,
49
+ id: token.id?.toString(),
50
+ userId: token.userId?.toString(),
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Create a token with expiration
56
+ * Replaces: Token.createTokenWithExpire(userId, rawToken, minutes)
57
+ *
58
+ * @param {string} userId - The user ID (string from application layer)
59
+ * @param {string} rawToken - The raw (unhashed) token string
60
+ * @param {number} minutes - Minutes until expiration
61
+ * @returns {Promise<Object>} The created token record with string IDs
62
+ */
63
+ async createTokenWithExpire(userId, rawToken, minutes) {
64
+ // Hash the token with bcrypt
65
+ const tokenHash = await bcrypt.hash(rawToken, BCRYPT_ROUNDS);
66
+
67
+ // Calculate expiration time
68
+ const expires = new Date(Date.now() + minutes * 60000);
69
+
70
+ const token = await this.prisma.token.create({
71
+ data: {
72
+ token: tokenHash,
73
+ expires,
74
+ userId: this._convertId(userId),
75
+ },
76
+ });
77
+
78
+ return this._convertTokenIds(token);
79
+ }
80
+
81
+ /**
82
+ * Validate and retrieve token from JSON token object
83
+ * Replaces: Token.validateAndGetTokenFromJSONToken(tokenObj)
84
+ *
85
+ * @param {Object} tokenObj - Object with id and token properties (id as string from app layer)
86
+ * @returns {Promise<Object>} The validated token record with string IDs
87
+ * @throws {Error} If token is invalid, expired, or doesn't exist
88
+ */
89
+ async validateAndGetToken(tokenObj) {
90
+ const intId = this._convertId(tokenObj.id);
91
+ const sessionToken = await this.prisma.token.findUnique({
92
+ where: { id: intId },
93
+ });
94
+
95
+ if (!sessionToken) {
96
+ throw new ClientSafeError(
97
+ 'Invalid Token: Token does not exist',
98
+ 401
99
+ );
100
+ }
101
+
102
+ // Verify token hash matches
103
+ const isValid = await bcrypt.compare(
104
+ tokenObj.token,
105
+ sessionToken.token
106
+ );
107
+ if (!isValid) {
108
+ throw new ClientSafeError(
109
+ 'Invalid Token: Token does not match',
110
+ 401
111
+ );
112
+ }
113
+
114
+ // Check if token is expired
115
+ if (
116
+ sessionToken.expires &&
117
+ new Date(sessionToken.expires) < new Date()
118
+ ) {
119
+ throw new ClientSafeError('Invalid Token: Token is expired', 401);
120
+ }
121
+
122
+ return this._convertTokenIds(sessionToken);
123
+ }
124
+
125
+ /**
126
+ * Find a token by ID
127
+ * Replaces: Token.findById(tokenId)
128
+ *
129
+ * @param {string} tokenId - The token ID (string from application layer)
130
+ * @returns {Promise<Object|null>} The token record with string IDs or null
131
+ */
132
+ async findTokenById(tokenId) {
133
+ const intId = this._convertId(tokenId);
134
+ const token = await this.prisma.token.findUnique({
135
+ where: { id: intId },
136
+ });
137
+ return this._convertTokenIds(token);
138
+ }
139
+
140
+ /**
141
+ * Find tokens by user ID
142
+ * Replaces: Token.find({ user: userId })
143
+ *
144
+ * @param {string} userId - The user ID (string from application layer)
145
+ * @returns {Promise<Array>} Array of token records with string IDs
146
+ */
147
+ async findTokensByUserId(userId) {
148
+ const intUserId = this._convertId(userId);
149
+ const tokens = await this.prisma.token.findMany({
150
+ where: { userId: intUserId },
151
+ });
152
+ return tokens.map((token) => this._convertTokenIds(token));
153
+ }
154
+
155
+ /**
156
+ * Delete a token by ID
157
+ * Replaces: Token.deleteOne({ _id: tokenId })
158
+ *
159
+ * @param {string} tokenId - The token ID (string from application layer)
160
+ * @returns {Promise<Object>} The deletion result
161
+ */
162
+ async deleteToken(tokenId) {
163
+ try {
164
+ const intId = this._convertId(tokenId);
165
+ await this.prisma.token.delete({
166
+ where: { id: intId },
167
+ });
168
+ return { acknowledged: true, deletedCount: 1 };
169
+ } catch (error) {
170
+ if (error.code === 'P2025') {
171
+ // Record not found
172
+ return { acknowledged: true, deletedCount: 0 };
173
+ }
174
+ throw error;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Delete expired tokens
180
+ * Replaces: Token.deleteMany({ expires: { $lt: new Date() } })
181
+ *
182
+ * @returns {Promise<Object>} The deletion result with count
183
+ */
184
+ async deleteExpiredTokens() {
185
+ const result = await this.prisma.token.deleteMany({
186
+ where: {
187
+ expires: {
188
+ lt: new Date(),
189
+ },
190
+ },
191
+ });
192
+
193
+ return {
194
+ acknowledged: true,
195
+ deletedCount: result.count,
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Delete all tokens for a user
201
+ * Replaces: Token.deleteMany({ user: userId })
202
+ *
203
+ * @param {string} userId - The user ID (string from application layer)
204
+ * @returns {Promise<Object>} The deletion result
205
+ */
206
+ async deleteTokensByUserId(userId) {
207
+ const intUserId = this._convertId(userId);
208
+ const result = await this.prisma.token.deleteMany({
209
+ where: { userId: intUserId },
210
+ });
211
+
212
+ return {
213
+ acknowledged: true,
214
+ deletedCount: result.count,
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Create JSON token string from token object and raw token
220
+ * Replaces: Token.createJSONToken(token, rawToken)
221
+ *
222
+ * Note: Token ID is already a string at this point (from _convertTokenIds),
223
+ * so no conversion needed here.
224
+ *
225
+ * @param {Object} token - The token record (with string IDs)
226
+ * @param {string} rawToken - The raw token string
227
+ * @returns {string} JSON string with id and token
228
+ */
229
+ createJSONToken(token, rawToken) {
230
+ return JSON.stringify({
231
+ id: token.id,
232
+ token: rawToken,
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Create base64 encoded buffer token
238
+ * Replaces: Token.createBase64BufferToken(token, rawToken)
239
+ *
240
+ * @param {Object} token - The token record (with string IDs)
241
+ * @param {string} rawToken - The raw token string
242
+ * @returns {string} Base64 encoded token
243
+ */
244
+ createBase64BufferToken(token, rawToken) {
245
+ const jsonVal = this.createJSONToken(token, rawToken);
246
+ return Buffer.from(jsonVal).toString('base64');
247
+ }
248
+
249
+ /**
250
+ * Parse JSON token from base64 buffer
251
+ * Replaces: Token.getJSONTokenFromBase64BufferToken(buffer)
252
+ *
253
+ * Note: Parsed token ID will be a string, which is correct for application layer
254
+ *
255
+ * @param {string} buffer - Base64 encoded token string
256
+ * @returns {Object} Parsed token object with id and token (id as string)
257
+ */
258
+ getJSONTokenFromBase64BufferToken(buffer) {
259
+ const tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
260
+ return JSON.parse(tokenStr);
261
+ }
262
+ }
263
+
264
+ module.exports = { TokenRepositoryPostgres };
@@ -0,0 +1,219 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const bcrypt = require('bcryptjs');
3
+ const { TokenRepositoryInterface } = require('./token-repository-interface');
4
+
5
+ const BCRYPT_ROUNDS = 10;
6
+
7
+ /**
8
+ * Prisma-based Token Repository
9
+ * Handles persistence of authentication tokens with bcrypt hashing
10
+ *
11
+ * Works identically for both MongoDB and PostgreSQL:
12
+ * - MongoDB: String IDs with @db.ObjectId
13
+ * - PostgreSQL: Integer IDs with auto-increment
14
+ * - Both use same query patterns (no many-to-many differences)
15
+ *
16
+ * Migration from Mongoose:
17
+ * - Constructor injection of Prisma client
18
+ * - Static methods → Instance methods
19
+ * - Token.createTokenWithExpire() → createTokenWithExpire()
20
+ * - Token.validateAndGetTokenFromJSONToken() → validateAndGetToken()
21
+ * - Bcrypt hashing handled in repository layer
22
+ */
23
+ class TokenRepository extends TokenRepositoryInterface {
24
+ constructor(prismaClient = prisma) {
25
+ super();
26
+ this.prisma = prismaClient; // Allow injection for testing
27
+ }
28
+
29
+ /**
30
+ * Create a token with expiration
31
+ * Replaces: Token.createTokenWithExpire(userId, rawToken, minutes)
32
+ *
33
+ * @param {string} userId - The user ID
34
+ * @param {string} rawToken - The raw (unhashed) token string
35
+ * @param {number} minutes - Minutes until expiration
36
+ * @returns {Promise<Object>} The created token record
37
+ */
38
+ async createTokenWithExpire(userId, rawToken, minutes) {
39
+ // Hash the token with bcrypt
40
+ const tokenHash = await bcrypt.hash(rawToken, BCRYPT_ROUNDS);
41
+
42
+ // Calculate expiration time
43
+ const expires = new Date(Date.now() + minutes * 60000);
44
+
45
+ return await this.prisma.token.create({
46
+ data: {
47
+ token: tokenHash,
48
+ expires,
49
+ userId,
50
+ },
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Validate and retrieve token from JSON token object
56
+ * Replaces: Token.validateAndGetTokenFromJSONToken(tokenObj)
57
+ *
58
+ * @param {Object} tokenObj - Object with id and token properties
59
+ * @returns {Promise<Object>} The validated token record
60
+ * @throws {Error} If token is invalid, expired, or doesn't exist
61
+ */
62
+ async validateAndGetToken(tokenObj) {
63
+ const sessionToken = await this.prisma.token.findUnique({
64
+ where: { id: tokenObj.id },
65
+ });
66
+
67
+ if (!sessionToken) {
68
+ throw new Error('Invalid Token: Token does not exist');
69
+ }
70
+
71
+ // Verify token hash matches
72
+ const isValid = await bcrypt.compare(
73
+ tokenObj.token,
74
+ sessionToken.token
75
+ );
76
+ if (!isValid) {
77
+ throw new Error('Invalid Token: Token does not match');
78
+ }
79
+
80
+ // Check if token is expired
81
+ if (
82
+ sessionToken.expires &&
83
+ new Date(sessionToken.expires) < new Date()
84
+ ) {
85
+ throw new Error('Invalid Token: Token is expired');
86
+ }
87
+
88
+ return sessionToken;
89
+ }
90
+
91
+ /**
92
+ * Find a token by ID
93
+ * Replaces: Token.findById(tokenId)
94
+ *
95
+ * @param {string} tokenId - The token ID
96
+ * @returns {Promise<Object|null>} The token record or null
97
+ */
98
+ async findTokenById(tokenId) {
99
+ return await this.prisma.token.findUnique({
100
+ where: { id: tokenId },
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Find tokens by user ID
106
+ * Replaces: Token.find({ user: userId })
107
+ *
108
+ * @param {string} userId - The user ID
109
+ * @returns {Promise<Array>} Array of token records
110
+ */
111
+ async findTokensByUserId(userId) {
112
+ return await this.prisma.token.findMany({
113
+ where: { userId },
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Delete a token by ID
119
+ * Replaces: Token.deleteOne({ _id: tokenId })
120
+ *
121
+ * @param {string} tokenId - The token ID
122
+ * @returns {Promise<Object>} The deletion result
123
+ */
124
+ async deleteToken(tokenId) {
125
+ try {
126
+ await this.prisma.token.delete({
127
+ where: { id: tokenId },
128
+ });
129
+ return { acknowledged: true, deletedCount: 1 };
130
+ } catch (error) {
131
+ if (error.code === 'P2025') {
132
+ // Record not found
133
+ return { acknowledged: true, deletedCount: 0 };
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Delete expired tokens
141
+ * Replaces: Token.deleteMany({ expires: { $lt: new Date() } })
142
+ *
143
+ * @returns {Promise<Object>} The deletion result with count
144
+ */
145
+ async deleteExpiredTokens() {
146
+ const result = await this.prisma.token.deleteMany({
147
+ where: {
148
+ expires: {
149
+ lt: new Date(),
150
+ },
151
+ },
152
+ });
153
+
154
+ return {
155
+ acknowledged: true,
156
+ deletedCount: result.count,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Delete all tokens for a user
162
+ * Replaces: Token.deleteMany({ user: userId })
163
+ *
164
+ * @param {string} userId - The user ID
165
+ * @returns {Promise<Object>} The deletion result
166
+ */
167
+ async deleteTokensByUserId(userId) {
168
+ const result = await this.prisma.token.deleteMany({
169
+ where: { userId },
170
+ });
171
+
172
+ return {
173
+ acknowledged: true,
174
+ deletedCount: result.count,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Create JSON token string from token object and raw token
180
+ * Replaces: Token.createJSONToken(token, rawToken)
181
+ *
182
+ * @param {Object} token - The token record
183
+ * @param {string} rawToken - The raw token string
184
+ * @returns {string} JSON string with id and token
185
+ */
186
+ createJSONToken(token, rawToken) {
187
+ return JSON.stringify({
188
+ id: token.id,
189
+ token: rawToken,
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Create base64 encoded buffer token
195
+ * Replaces: Token.createBase64BufferToken(token, rawToken)
196
+ *
197
+ * @param {Object} token - The token record
198
+ * @param {string} rawToken - The raw token string
199
+ * @returns {string} Base64 encoded token
200
+ */
201
+ createBase64BufferToken(token, rawToken) {
202
+ const jsonVal = this.createJSONToken(token, rawToken);
203
+ return Buffer.from(jsonVal).toString('base64');
204
+ }
205
+
206
+ /**
207
+ * Parse JSON token from base64 buffer
208
+ * Replaces: Token.getJSONTokenFromBase64BufferToken(buffer)
209
+ *
210
+ * @param {string} buffer - Base64 encoded token string
211
+ * @returns {Object} Parsed token object with id and token
212
+ */
213
+ getJSONTokenFromBase64BufferToken(buffer) {
214
+ const tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
215
+ return JSON.parse(tokenStr);
216
+ }
217
+ }
218
+
219
+ module.exports = { TokenRepository };
@@ -1,5 +1,5 @@
1
1
  declare module "@friggframework/core" {
2
- import { SQS } from "aws-sdk";
2
+ import type { SendMessageCommandInput } from "@aws-sdk/client-sqs";
3
3
 
4
4
  export class Delegate implements IFriggDelegate {
5
5
  delegate: any;
@@ -50,5 +50,5 @@ declare module "@friggframework/core" {
50
50
  QueueOwnerAWSAccountId?: string;
51
51
  };
52
52
 
53
- type SendSQSMessageParams = SQS.SendMessageRequest;
53
+ type SendSQSMessageParams = SendMessageCommandInput;
54
54
  }