@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.0

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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -0,0 +1,474 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { createAppHandler } = require('./../app-handler-helpers');
4
+ const { requireAdmin } = require('./middleware/requireAdmin');
5
+ const catchAsyncError = require('express-async-handler');
6
+ const bcrypt = require('bcryptjs');
7
+ const {
8
+ createUserRepository,
9
+ } = require('../../user/repositories/user-repository-factory');
10
+ const { loadAppDefinition } = require('../app-definition-loader');
11
+ const {
12
+ createModuleRepository,
13
+ } = require('../../modules/repositories/module-repository-factory');
14
+ const {
15
+ GetModuleEntityById,
16
+ } = require('../../modules/use-cases/get-module-entity-by-id');
17
+ const {
18
+ UpdateModuleEntity,
19
+ } = require('../../modules/use-cases/update-module-entity');
20
+ const {
21
+ DeleteModuleEntity,
22
+ } = require('../../modules/use-cases/delete-module-entity');
23
+ const {
24
+ CreateTokenForUserId,
25
+ } = require('../../user/use-cases/create-token-for-user-id');
26
+ const { DeleteUser } = require('../../user/use-cases/delete-user');
27
+
28
+ // Lazy-initialized repositories and use cases (deferred until first request)
29
+ let _userRepository,
30
+ _moduleRepository,
31
+ _getModuleEntityById,
32
+ _updateModuleEntity,
33
+ _deleteModuleEntity,
34
+ _createTokenForUserId,
35
+ _deleteUser;
36
+
37
+ function ensureInitialized() {
38
+ if (!_userRepository) {
39
+ const { userConfig } = loadAppDefinition();
40
+ _userRepository = createUserRepository({ userConfig });
41
+ _moduleRepository = createModuleRepository();
42
+ _getModuleEntityById = new GetModuleEntityById({
43
+ moduleRepository: _moduleRepository,
44
+ });
45
+ _updateModuleEntity = new UpdateModuleEntity({
46
+ moduleRepository: _moduleRepository,
47
+ });
48
+ _deleteModuleEntity = new DeleteModuleEntity({
49
+ moduleRepository: _moduleRepository,
50
+ });
51
+ _createTokenForUserId = new CreateTokenForUserId({
52
+ userRepository: _userRepository,
53
+ });
54
+ _deleteUser = new DeleteUser({ userRepository: _userRepository });
55
+ }
56
+ }
57
+
58
+ // Lazy initialization middleware — runs once on first request
59
+ router.use((req, res, next) => {
60
+ ensureInitialized();
61
+ next();
62
+ });
63
+
64
+ // Debug logging
65
+ router.use((req, res, next) => {
66
+ console.log(
67
+ `[Admin Router] ${req.method} ${req.path} | Original URL: ${req.originalUrl}`
68
+ );
69
+ next();
70
+ });
71
+
72
+ // Apply admin API key auth middleware to all admin routes
73
+ router.use(requireAdmin);
74
+
75
+ /**
76
+ * USER MANAGEMENT ENDPOINTS
77
+ */
78
+
79
+ /**
80
+ * GET /api/admin/users
81
+ * List all users with pagination
82
+ */
83
+ router.get(
84
+ '/api/admin/users',
85
+ catchAsyncError(async (req, res) => {
86
+ const {
87
+ page = 1,
88
+ limit = 50,
89
+ sortBy = 'createdAt',
90
+ sortOrder = 'desc',
91
+ } = req.query;
92
+ const skip = (parseInt(page) - 1) * parseInt(limit);
93
+
94
+ // Build sort object
95
+ const sort = {};
96
+ sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
97
+
98
+ // Use repository to get users
99
+ const users = await _userRepository.findAllUsers({
100
+ skip,
101
+ limit: parseInt(limit),
102
+ sort,
103
+ excludeFields: ['-hashword'], // Exclude password hash
104
+ });
105
+
106
+ const totalCount = await _userRepository.countUsers();
107
+
108
+ res.json({
109
+ users,
110
+ pagination: {
111
+ page: parseInt(page),
112
+ limit: parseInt(limit),
113
+ total: totalCount,
114
+ pages: Math.ceil(totalCount / parseInt(limit)),
115
+ },
116
+ });
117
+ })
118
+ );
119
+
120
+ /**
121
+ * GET /api/admin/users/search
122
+ * Search users by username or email
123
+ */
124
+ router.get(
125
+ '/api/admin/users/search',
126
+ catchAsyncError(async (req, res) => {
127
+ const {
128
+ q,
129
+ page = 1,
130
+ limit = 50,
131
+ sortBy = 'createdAt',
132
+ sortOrder = 'desc',
133
+ } = req.query;
134
+
135
+ if (!q) {
136
+ return res.status(400).json({
137
+ status: 'error',
138
+ message: 'Search query parameter "q" is required',
139
+ });
140
+ }
141
+
142
+ const skip = (parseInt(page) - 1) * parseInt(limit);
143
+
144
+ // Build sort object
145
+ const sort = {};
146
+ sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
147
+
148
+ // Use repository to search users
149
+ const users = await _userRepository.searchUsers({
150
+ query: q,
151
+ skip,
152
+ limit: parseInt(limit),
153
+ sort,
154
+ excludeFields: ['-hashword'],
155
+ });
156
+
157
+ const totalCount = await _userRepository.countUsersBySearchQuery(q);
158
+
159
+ res.json({
160
+ users,
161
+ pagination: {
162
+ page: parseInt(page),
163
+ limit: parseInt(limit),
164
+ total: totalCount,
165
+ pages: Math.ceil(totalCount / parseInt(limit)),
166
+ },
167
+ });
168
+ })
169
+ );
170
+
171
+ /**
172
+ * POST /api/admin/users
173
+ * Create a new user (admin only)
174
+ * Admin-specific features:
175
+ * - Can create users with custom roles
176
+ * - Can set verified status
177
+ * - Can assign to organizations
178
+ * - No email verification required
179
+ */
180
+ router.post(
181
+ '/api/admin/users',
182
+ catchAsyncError(async (req, res) => {
183
+ const {
184
+ username,
185
+ email,
186
+ password,
187
+ type = 'INDIVIDUAL',
188
+ appUserId,
189
+ organizationId,
190
+ verified = true, // Admins can create pre-verified users
191
+ } = req.body;
192
+
193
+ // Validate required fields
194
+ if (!username || !email || !password) {
195
+ return res.status(400).json({
196
+ status: 'error',
197
+ message: 'Username, email, and password are required',
198
+ });
199
+ }
200
+
201
+ // Check if user already exists
202
+ const existingUser = await _userRepository.findIndividualUserByUsername(
203
+ username
204
+ );
205
+ if (existingUser) {
206
+ return res.status(409).json({
207
+ status: 'error',
208
+ message: 'User with this username already exists',
209
+ });
210
+ }
211
+
212
+ const existingEmail = await _userRepository.findIndividualUserByEmail(
213
+ email
214
+ );
215
+ if (existingEmail) {
216
+ return res.status(409).json({
217
+ status: 'error',
218
+ message: 'User with this email already exists',
219
+ });
220
+ }
221
+
222
+ // Hash password (using bcryptjs which is already imported)
223
+ const hashword = await bcrypt.hash(password, 10);
224
+
225
+ // Create user with admin-specified attributes
226
+ const userData = {
227
+ username,
228
+ email,
229
+ hashword,
230
+ type,
231
+ };
232
+
233
+ // Add optional fields if provided
234
+ if (appUserId) userData.appUserId = appUserId;
235
+ if (organizationId) userData.organizationId = organizationId;
236
+
237
+ const user = await _userRepository.createIndividualUser(userData);
238
+
239
+ // Remove sensitive fields
240
+ const userObj = user.toObject ? user.toObject() : user;
241
+ delete userObj.hashword;
242
+
243
+ res.status(201).json({
244
+ user: userObj,
245
+ message: 'User created successfully by admin',
246
+ });
247
+ })
248
+ );
249
+
250
+ /**
251
+ * GET /api/admin/users/:userId
252
+ * Get a specific user by ID
253
+ */
254
+ router.get(
255
+ '/api/admin/users/:userId',
256
+ catchAsyncError(async (req, res) => {
257
+ const { userId } = req.params;
258
+
259
+ const user = await _userRepository.findUserById(userId);
260
+
261
+ if (!user) {
262
+ return res.status(404).json({
263
+ status: 'error',
264
+ message: 'User not found',
265
+ });
266
+ }
267
+
268
+ // Remove sensitive fields
269
+ const userObj = user.toObject ? user.toObject() : user;
270
+ delete userObj.hashword;
271
+
272
+ res.json({ user: userObj });
273
+ })
274
+ );
275
+
276
+ /**
277
+ * POST /api/admin/users/:userId/impersonate
278
+ * Generate a token for a user without requiring password (admin impersonation)
279
+ * Allows admins to login as any user for support/testing purposes
280
+ */
281
+ router.post(
282
+ '/api/admin/users/:userId/impersonate',
283
+ catchAsyncError(async (req, res) => {
284
+ const { userId } = req.params;
285
+ const { expiresInMinutes = 120 } = req.body;
286
+
287
+ // Find the user
288
+ const user = await _userRepository.findUserById(userId);
289
+
290
+ if (!user) {
291
+ return res.status(404).json({
292
+ status: 'error',
293
+ message: 'User not found',
294
+ });
295
+ }
296
+
297
+ // Generate token without password verification
298
+ const token = await _createTokenForUserId.execute(
299
+ userId,
300
+ expiresInMinutes
301
+ );
302
+
303
+ res.json({
304
+ token,
305
+ message: `Impersonating user: ${user.username || user.email}`,
306
+ expiresInMinutes,
307
+ });
308
+ })
309
+ );
310
+
311
+ /**
312
+ * DELETE /api/admin/users/:userId
313
+ * Delete a user by ID (admin only)
314
+ * IMPORTANT: This is a destructive operation - use with caution
315
+ */
316
+ router.delete(
317
+ '/api/admin/users/:userId',
318
+ catchAsyncError(async (req, res) => {
319
+ const { userId } = req.params;
320
+
321
+ // Execute delete user use case
322
+ await _deleteUser.execute(userId);
323
+
324
+ res.status(204).send();
325
+ })
326
+ );
327
+
328
+ /**
329
+ * GLOBAL ENTITY MANAGEMENT ENDPOINTS
330
+ */
331
+
332
+ /**
333
+ * GET /api/admin/entities
334
+ * List all global entities
335
+ */
336
+ router.get(
337
+ '/api/admin/entities',
338
+ catchAsyncError(async (req, res) => {
339
+ const { type, status } = req.query;
340
+
341
+ const query = { isGlobal: true };
342
+ if (type) query.type = type;
343
+ if (status) query.status = status;
344
+
345
+ const entities = await _moduleRepository.findEntitiesBy(query);
346
+
347
+ res.json({ entities });
348
+ })
349
+ );
350
+
351
+ /**
352
+ * GET /api/admin/entities/:entityId
353
+ * Get a specific global entity
354
+ */
355
+ router.get(
356
+ '/api/admin/entities/:entityId',
357
+ catchAsyncError(async (req, res) => {
358
+ const { entityId } = req.params;
359
+
360
+ const entity = await _getModuleEntityById.execute(entityId);
361
+
362
+ if (!entity || !entity.isGlobal) {
363
+ return res.status(404).json({
364
+ status: 'error',
365
+ message: 'Global entity not found',
366
+ });
367
+ }
368
+
369
+ res.json({ entity });
370
+ })
371
+ );
372
+
373
+ /**
374
+ * POST /api/admin/entities
375
+ * Create a new global entity
376
+ */
377
+ router.post(
378
+ '/api/admin/entities',
379
+ catchAsyncError(async (req, res) => {
380
+ const { type, ...entityData } = req.body;
381
+
382
+ if (!type) {
383
+ return res.status(400).json({
384
+ status: 'error',
385
+ message: 'Entity type is required',
386
+ });
387
+ }
388
+
389
+ // Create entity with isGlobal flag
390
+ const entity = await _moduleRepository.createEntity({
391
+ ...entityData,
392
+ type,
393
+ isGlobal: true,
394
+ status: 'connected',
395
+ });
396
+
397
+ res.status(201).json({ entity });
398
+ })
399
+ );
400
+
401
+ /**
402
+ * PUT /api/admin/entities/:entityId
403
+ * Update a global entity
404
+ */
405
+ router.put(
406
+ '/api/admin/entities/:entityId',
407
+ catchAsyncError(async (req, res) => {
408
+ const { entityId } = req.params;
409
+
410
+ const entity = await _updateModuleEntity.execute(entityId, req.body);
411
+
412
+ if (!entity) {
413
+ return res.status(404).json({
414
+ status: 'error',
415
+ message: 'Global entity not found',
416
+ });
417
+ }
418
+
419
+ res.json({ entity });
420
+ })
421
+ );
422
+
423
+ /**
424
+ * DELETE /api/admin/entities/:entityId
425
+ * Delete a global entity
426
+ */
427
+ router.delete(
428
+ '/api/admin/entities/:entityId',
429
+ catchAsyncError(async (req, res) => {
430
+ const { entityId } = req.params;
431
+
432
+ await _deleteModuleEntity.execute(entityId);
433
+
434
+ res.status(204).send();
435
+ })
436
+ );
437
+
438
+ /**
439
+ * POST /api/admin/entities/:entityId/test
440
+ * Test connection for a global entity
441
+ */
442
+ router.post(
443
+ '/api/admin/entities/:entityId/test',
444
+ catchAsyncError(async (req, res) => {
445
+ const { entityId } = req.params;
446
+
447
+ const entity = await _getModuleEntityById.execute(entityId);
448
+
449
+ if (!entity || !entity.isGlobal) {
450
+ return res.status(404).json({
451
+ status: 'error',
452
+ message: 'Global entity not found',
453
+ });
454
+ }
455
+
456
+ // Test the entity connection
457
+ try {
458
+ // This would use a TestModuleAuth use case
459
+ res.json({
460
+ status: 'success',
461
+ message: 'Entity connection test successful',
462
+ });
463
+ } catch (error) {
464
+ res.status(500).json({
465
+ status: 'error',
466
+ message: `Entity connection test failed: ${error.message}`,
467
+ });
468
+ }
469
+ })
470
+ );
471
+
472
+ const handler = createAppHandler('HTTP Event: Admin', router);
473
+
474
+ module.exports = { handler, router };
@@ -1,15 +1,57 @@
1
1
  const { createIntegrationRouter } = require('@friggframework/core');
2
2
  const { createAppHandler } = require('./../app-handler-helpers');
3
+ const { requireLoggedInUser } = require('./middleware/requireLoggedInUser');
4
+ const { loadAppDefinition } = require('../app-definition-loader');
3
5
 
4
- const router = createIntegrationRouter();
6
+ let _router;
7
+ let _handler;
5
8
 
6
- router.route('/api/integrations/redirect/:appId').get((req, res) => {
7
- res.redirect(
8
- `${process.env.FRONTEND_URI}/redirect/${req.params.appId
9
- }?${new URLSearchParams(req.query)}`
10
- );
11
- });
9
+ function ensureRouter() {
10
+ if (!_router) {
11
+ _router = createIntegrationRouter();
12
12
 
13
- const handler = createAppHandler('HTTP Event: Auth', router);
13
+ _router
14
+ .route('/api/integrations/redirect/:appId')
15
+ .get((req, res) => {
16
+ res.redirect(
17
+ `${process.env.FRONTEND_URI}/redirect/${
18
+ req.params.appId
19
+ }?${new URLSearchParams(req.query)}`
20
+ );
21
+ });
14
22
 
15
- module.exports = { handler };
23
+ // Integration settings endpoint
24
+ _router
25
+ .route('/config/integration-settings')
26
+ .get(requireLoggedInUser, (req, res) => {
27
+ const appDefinition = loadAppDefinition();
28
+
29
+ const settings = {
30
+ autoProvisioningEnabled:
31
+ appDefinition.integration
32
+ ?.autoProvisioningEnabled ?? true,
33
+ credentialReuseStrategy:
34
+ appDefinition.integration
35
+ ?.credentialReuseStrategy ?? 'shared',
36
+ allowUserManagedEntities:
37
+ appDefinition.integration
38
+ ?.allowUserManagedEntities ?? true,
39
+ };
40
+
41
+ res.json(settings);
42
+ });
43
+ }
44
+ return _router;
45
+ }
46
+
47
+ module.exports = {
48
+ get router() {
49
+ return ensureRouter();
50
+ },
51
+ get handler() {
52
+ if (!_handler) {
53
+ _handler = createAppHandler('HTTP Event: Auth', ensureRouter());
54
+ }
55
+ return _handler;
56
+ },
57
+ };
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * Minimal Lambda wrapper that avoids loading core/index.js
5
5
  * (which would try to load user/** modules excluded from migration packages)
6
- *
6
+ *
7
7
  * This handler is intentionally simpler than health.handler.js to avoid dependencies.
8
8
  */
9
9
 
10
10
  const serverlessHttp = require('serverless-http');
11
11
  const express = require('express');
12
12
  const cors = require('cors');
13
- const dbMigrationRouter = require('./db-migration');
13
+ const { router: dbMigrationRouter } = require('./db-migration');
14
14
 
15
15
  // Create minimal Express app
16
16
  const app = express();
@@ -26,4 +26,3 @@ app.use((err, req, res, next) => {
26
26
 
27
27
  // Export as .handler property (Lambda config: db-migration.handler)
28
28
  module.exports.handler = serverlessHttp(app);
29
-