@app-connect/core 1.7.25 → 1.7.27

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 (138) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -31
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -164
  6. package/connector/proxy/index.js +500 -500
  7. package/connector/registry.js +252 -252
  8. package/docs/README.md +50 -50
  9. package/docs/architecture.md +93 -93
  10. package/docs/connectors.md +116 -116
  11. package/docs/handlers.md +125 -125
  12. package/docs/libraries.md +101 -101
  13. package/docs/models.md +144 -144
  14. package/docs/routes.md +115 -115
  15. package/docs/tests.md +73 -73
  16. package/handlers/admin.js +523 -523
  17. package/handlers/appointment.js +193 -0
  18. package/handlers/auth.js +320 -296
  19. package/handlers/calldown.js +99 -99
  20. package/handlers/contact.js +280 -280
  21. package/handlers/disposition.js +82 -80
  22. package/handlers/log.js +990 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/managedOAuth.js +247 -0
  25. package/handlers/plugin.js +208 -208
  26. package/handlers/user.js +142 -142
  27. package/index.js +3162 -2652
  28. package/jest.config.js +56 -56
  29. package/lib/analytics.js +54 -54
  30. package/lib/authSession.js +109 -109
  31. package/lib/cacheCleanup.js +21 -0
  32. package/lib/callLogComposer.js +898 -898
  33. package/lib/callLogLookup.js +34 -0
  34. package/lib/constants.js +8 -8
  35. package/lib/debugTracer.js +177 -177
  36. package/lib/encode.js +30 -30
  37. package/lib/errorHandler.js +218 -206
  38. package/lib/generalErrorMessage.js +41 -41
  39. package/lib/jwt.js +18 -18
  40. package/lib/logger.js +190 -190
  41. package/lib/migrateCallLogsSchema.js +116 -0
  42. package/lib/ringcentral.js +266 -266
  43. package/lib/s3ErrorLogReport.js +65 -65
  44. package/lib/sharedSMSComposer.js +471 -471
  45. package/lib/util.js +67 -67
  46. package/mcp/README.md +412 -395
  47. package/mcp/lib/validator.js +91 -91
  48. package/mcp/mcpHandler.js +425 -425
  49. package/mcp/tools/cancelAppointment.js +101 -0
  50. package/mcp/tools/checkAuthStatus.js +105 -105
  51. package/mcp/tools/confirmAppointment.js +101 -0
  52. package/mcp/tools/createAppointment.js +157 -0
  53. package/mcp/tools/createCallLog.js +327 -316
  54. package/mcp/tools/createContact.js +117 -117
  55. package/mcp/tools/createMessageLog.js +287 -287
  56. package/mcp/tools/doAuth.js +60 -60
  57. package/mcp/tools/findContactByName.js +93 -93
  58. package/mcp/tools/findContactByPhone.js +101 -101
  59. package/mcp/tools/getCallLog.js +111 -102
  60. package/mcp/tools/getGoogleFilePicker.js +99 -99
  61. package/mcp/tools/getHelp.js +43 -43
  62. package/mcp/tools/getPublicConnectors.js +94 -94
  63. package/mcp/tools/getSessionInfo.js +90 -90
  64. package/mcp/tools/index.js +51 -41
  65. package/mcp/tools/listAppointments.js +163 -0
  66. package/mcp/tools/logout.js +99 -96
  67. package/mcp/tools/rcGetCallLogs.js +65 -65
  68. package/mcp/tools/updateAppointment.js +154 -0
  69. package/mcp/tools/updateCallLog.js +130 -126
  70. package/mcp/ui/App/App.tsx +358 -358
  71. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  72. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  73. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  74. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  75. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  76. package/mcp/ui/App/lib/callTool.ts +130 -130
  77. package/mcp/ui/App/lib/debugLog.ts +41 -41
  78. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  79. package/mcp/ui/App/main.css +5 -5
  80. package/mcp/ui/App/root.tsx +13 -13
  81. package/mcp/ui/index.html +13 -13
  82. package/mcp/ui/package-lock.json +6356 -6356
  83. package/mcp/ui/package.json +25 -25
  84. package/mcp/ui/tsconfig.json +26 -26
  85. package/mcp/ui/vite.config.ts +16 -16
  86. package/models/accountDataModel.js +33 -33
  87. package/models/adminConfigModel.js +35 -35
  88. package/models/cacheModel.js +30 -26
  89. package/models/callDownListModel.js +34 -34
  90. package/models/callLogModel.js +33 -27
  91. package/models/dynamo/connectorSchema.js +146 -146
  92. package/models/dynamo/lockSchema.js +24 -24
  93. package/models/dynamo/noteCacheSchema.js +29 -29
  94. package/models/llmSessionModel.js +17 -17
  95. package/models/messageLogModel.js +25 -25
  96. package/models/sequelize.js +16 -16
  97. package/models/userModel.js +45 -45
  98. package/package.json +1 -1
  99. package/releaseNotes.json +1101 -1081
  100. package/test/connector/proxy/engine.test.js +126 -126
  101. package/test/connector/proxy/index.test.js +279 -279
  102. package/test/connector/proxy/sample.json +161 -161
  103. package/test/connector/registry.test.js +415 -415
  104. package/test/handlers/admin.test.js +616 -616
  105. package/test/handlers/auth.test.js +1092 -1018
  106. package/test/handlers/contact.test.js +1014 -1014
  107. package/test/handlers/log.test.js +1298 -1160
  108. package/test/handlers/managedAuth.test.js +457 -457
  109. package/test/handlers/plugin.test.js +380 -380
  110. package/test/index.test.js +105 -105
  111. package/test/lib/cacheCleanup.test.js +42 -0
  112. package/test/lib/callLogComposer.test.js +1231 -1231
  113. package/test/lib/debugTracer.test.js +328 -328
  114. package/test/lib/jwt.test.js +176 -176
  115. package/test/lib/logger.test.js +206 -206
  116. package/test/lib/oauth.test.js +359 -359
  117. package/test/lib/ringcentral.test.js +467 -467
  118. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  119. package/test/lib/util.test.js +329 -329
  120. package/test/mcp/tools/checkAuthStatus.test.js +83 -83
  121. package/test/mcp/tools/createCallLog.test.js +436 -436
  122. package/test/mcp/tools/createContact.test.js +58 -58
  123. package/test/mcp/tools/createMessageLog.test.js +595 -595
  124. package/test/mcp/tools/doAuth.test.js +113 -113
  125. package/test/mcp/tools/findContactByName.test.js +275 -275
  126. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  127. package/test/mcp/tools/getCallLog.test.js +298 -298
  128. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  129. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  130. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  131. package/test/mcp/tools/logout.test.js +233 -233
  132. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  133. package/test/mcp/tools/updateCallLog.test.js +360 -360
  134. package/test/models/accountDataModel.test.js +98 -98
  135. package/test/models/dynamo/connectorSchema.test.js +189 -189
  136. package/test/models/models.test.js +568 -539
  137. package/test/routes/managedAuthRoutes.test.js +235 -104
  138. package/test/setup.js +178 -178
@@ -1,446 +1,446 @@
1
- const connectorRegistry = require('../connector/registry');
2
- const developerPortal = require('../connector/developerPortal');
3
- const { AccountDataModel } = require('../models/accountDataModel');
4
- const { Op } = require('sequelize');
5
- const { encode, decoded } = require('../lib/encode');
6
-
7
- const MANAGED_AUTH_ORG_DATA_KEY = 'managed-auth-org';
8
- const MANAGED_AUTH_USER_DATA_KEY = 'managed-auth-user';
9
- const MANAGED_AUTH_LOGIN_FAILURE_DATA_KEY = 'managed-auth-login-failure';
10
-
11
- function getUserManagedAuthDataKey({ rcExtensionId }) {
12
- return `${MANAGED_AUTH_USER_DATA_KEY}:${rcExtensionId}`;
13
- }
14
-
15
- function getManagedAuthLoginFailureDataKey({ rcExtensionId }) {
16
- return `${MANAGED_AUTH_LOGIN_FAILURE_DATA_KEY}:${rcExtensionId}`;
17
- }
18
-
19
- function isFilled(value) {
20
- return value !== undefined && value !== null && value !== '';
21
- }
22
-
23
- async function getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
24
- if (!platform) {
25
- return [];
26
- }
27
- if (connectorId) {
28
- const manifest = await developerPortal.getConnectorManifest({ rcAccountId, connectorId, isPrivate });
29
- if (manifest?.platforms?.[platform]?.auth?.apiKey?.page?.content) {
30
- return manifest.platforms[platform].auth.apiKey.page.content;
31
- }
32
- }
33
- try {
34
- const manifest = connectorRegistry.getManifest(platform, true);
35
- return manifest?.platforms?.[platform]?.auth?.apiKey?.page?.content ?? [];
36
- }
37
- catch (error) {
38
- return [];
39
- }
40
- }
41
-
42
- async function getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
43
- const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
44
- return fieldDefinitions.filter(field => field?.managed);
45
- }
46
-
47
- function encryptStoredValue(value) {
48
- return {
49
- version: 1,
50
- encrypted: true,
51
- value: encode(JSON.stringify(value))
52
- };
53
- }
54
-
55
- function decryptStoredValue(value) {
56
- if (!value) {
57
- return undefined;
58
- }
59
- if (value?.encrypted && value?.value) {
60
- return JSON.parse(decoded(value.value));
61
- }
62
- return value;
63
- }
64
-
65
- async function getManagedAuthRecord({ rcAccountId, platform, dataKey }) {
66
- if (!rcAccountId || !platform) {
67
- return null;
68
- }
69
- return AccountDataModel.findOne({
70
- where: {
71
- rcAccountId,
72
- platformName: platform,
73
- dataKey
74
- }
75
- });
76
- }
77
-
78
- async function getOrgManagedAuthValues({ rcAccountId, platform }) {
79
- const record = await getManagedAuthRecord({
80
- rcAccountId,
81
- platform,
82
- dataKey: MANAGED_AUTH_ORG_DATA_KEY
83
- });
84
- const fields = record?.data?.fields ?? {};
85
- const decryptedFields = {};
86
- Object.keys(fields).forEach(key => {
87
- decryptedFields[key] = decryptStoredValue(fields[key]);
88
- });
89
- return decryptedFields;
90
- }
91
-
92
- async function getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId }) {
93
- if (!rcExtensionId) {
94
- return {};
95
- }
96
- const userDataKey = getUserManagedAuthDataKey({ rcExtensionId });
97
- const scopedRecord = await getManagedAuthRecord({
98
- rcAccountId,
99
- platform,
100
- dataKey: userDataKey
101
- });
102
- const fields = scopedRecord?.data?.fields ?? {};
103
- const decryptedFields = {};
104
- Object.keys(fields).forEach(key => {
105
- decryptedFields[key] = decryptStoredValue(fields[key]);
106
- });
107
- return decryptedFields;
108
- }
109
-
110
- async function hasManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
111
- if (!rcExtensionId) {
112
- return false;
113
- }
114
- const record = await getManagedAuthRecord({
115
- rcAccountId,
116
- platform,
117
- dataKey: getManagedAuthLoginFailureDataKey({ rcExtensionId })
118
- });
119
- return !!record;
120
- }
121
-
122
- async function markManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
123
- if (!rcAccountId || !platform || !rcExtensionId) {
124
- return;
125
- }
126
- const dataKey = getManagedAuthLoginFailureDataKey({ rcExtensionId });
127
- const existingRecord = await getManagedAuthRecord({
128
- rcAccountId,
129
- platform,
130
- dataKey
131
- });
132
- const data = {
133
- failedAt: new Date().toISOString()
134
- };
135
- if (existingRecord) {
136
- await existingRecord.update({ data });
137
- return existingRecord;
138
- }
139
- return AccountDataModel.create({
140
- rcAccountId,
141
- platformName: platform,
142
- dataKey,
143
- data
144
- });
145
- }
146
-
147
- async function clearManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
148
- if (!rcAccountId || !platform || !rcExtensionId) {
149
- return 0;
150
- }
151
- return AccountDataModel.destroy({
152
- where: {
153
- rcAccountId,
154
- platformName: platform,
155
- dataKey: getManagedAuthLoginFailureDataKey({ rcExtensionId })
156
- }
157
- });
158
- }
159
-
160
- async function upsertOrgManagedAuthValues({ rcAccountId, platform, values = {}, fieldsToRemove = [] }) {
161
- const existingRecord = await getManagedAuthRecord({
162
- rcAccountId,
163
- platform,
164
- dataKey: MANAGED_AUTH_ORG_DATA_KEY
165
- });
166
- const nextFields = {
167
- ...(existingRecord?.data?.fields ?? {})
168
- };
169
-
170
- Object.keys(values).forEach(key => {
171
- if (isFilled(values[key])) {
172
- nextFields[key] = encryptStoredValue(values[key]);
173
- }
174
- });
175
- fieldsToRemove.forEach(key => {
176
- delete nextFields[key];
177
- });
178
-
179
- const nextData = {
180
- fields: nextFields
181
- };
182
-
183
- if (existingRecord) {
184
- await existingRecord.update({ data: nextData });
185
- return existingRecord;
186
- }
187
- return AccountDataModel.create({
188
- rcAccountId,
189
- platformName: platform,
190
- dataKey: MANAGED_AUTH_ORG_DATA_KEY,
191
- data: nextData
192
- });
193
- }
194
-
195
- async function upsertUserManagedAuthValues({ rcAccountId, platform, rcExtensionId, rcUserName, values = {}, fieldsToRemove = [] }) {
196
- if (!rcExtensionId) {
197
- throw new Error('rcExtensionId is required for user managed auth values');
198
- }
199
- const userDataKey = getUserManagedAuthDataKey({ rcExtensionId });
200
- const existingRecord = await getManagedAuthRecord({
201
- rcAccountId,
202
- platform,
203
- dataKey: userDataKey
204
- });
205
- const nextFields = {
206
- ...(existingRecord?.data?.fields ?? {})
207
- };
208
-
209
- Object.keys(values).forEach(key => {
210
- if (isFilled(values[key])) {
211
- nextFields[key] = encryptStoredValue(values[key]);
212
- }
213
- });
214
- fieldsToRemove.forEach(key => {
215
- delete nextFields[key];
216
- });
217
-
218
- const nextData = {
219
- rcExtensionId,
220
- rcUserName: rcUserName ?? existingRecord?.data?.rcUserName ?? '',
221
- fields: nextFields
222
- };
223
-
224
- if (existingRecord) {
225
- await existingRecord.update({ data: nextData });
226
- return existingRecord;
227
- }
228
- return AccountDataModel.create({
229
- rcAccountId,
230
- platformName: platform,
231
- dataKey: userDataKey,
232
- data: nextData
233
- });
234
- }
235
-
236
- function getStoredFieldValue({ value }) {
237
- if (!isFilled(value)) {
238
- return {
239
- hasValue: false,
240
- value: ''
241
- };
242
- }
243
- return {
244
- hasValue: true,
245
- value
246
- };
247
- }
248
-
249
- async function getManagedAuthAdminSettings({ platform, rcAccountId, connectorId, isPrivate = false }) {
250
- const fieldDefinitions = await getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
251
- const orgFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'account');
252
- const userFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'user');
253
- const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
254
- const userRecords = await AccountDataModel.findAll({
255
- where: {
256
- rcAccountId,
257
- platformName: platform,
258
- dataKey: {
259
- [Op.like]: `${MANAGED_AUTH_USER_DATA_KEY}:%`
260
- }
261
- }
262
- });
263
- const userEntries = userRecords
264
- .map(record => {
265
- const extensionIdFromDataKey = record.dataKey?.split(`${MANAGED_AUTH_USER_DATA_KEY}:`)?.[1];
266
- const rcExtensionId = record?.data?.rcExtensionId ?? extensionIdFromDataKey;
267
- if (!rcExtensionId) {
268
- return null;
269
- }
270
- return {
271
- rcExtensionId,
272
- rcUserName: record?.data?.rcUserName ?? '',
273
- fields: record?.data?.fields ?? {}
274
- };
275
- })
276
- .filter(Boolean);
277
-
278
- const adminOrgValues = {};
279
- orgFieldDefinitions.forEach(field => {
280
- adminOrgValues[field.const] = getStoredFieldValue({
281
- value: orgValues[field.const]
282
- });
283
- });
284
-
285
- const adminUserValues = userEntries.map(entry => {
286
- const fields = {};
287
- userFieldDefinitions.forEach(field => {
288
- fields[field.const] = getStoredFieldValue({
289
- value: decryptStoredValue(entry?.fields?.[field.const])
290
- });
291
- });
292
- return {
293
- rcExtensionId: entry.rcExtensionId,
294
- rcUserName: entry.rcUserName ?? '',
295
- fields
296
- };
297
- });
298
-
299
- return {
300
- hasManagedAuth: fieldDefinitions.length > 0,
301
- fields: fieldDefinitions,
302
- orgFields: orgFieldDefinitions,
303
- userFields: userFieldDefinitions,
304
- orgValues: adminOrgValues,
305
- userValues: adminUserValues
306
- };
307
- }
308
-
309
- async function getManagedAuthState({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false }) {
310
- const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
311
- const managedFieldDefinitions = fieldDefinitions.filter(field => field?.managed);
312
- const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
313
- const userValues = await getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId });
314
- const hasLoginFailureFallback = await hasManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId });
315
-
316
- const visibleFieldConsts = [];
317
- const missingRequiredFieldConsts = [];
318
- let allRequiredFieldsSatisfied = true;
319
-
320
- // Default behavior for connectors without managed auth: render full API key form.
321
- if (managedFieldDefinitions.length === 0) {
322
- return {
323
- hasManagedAuth: false,
324
- allRequiredFieldsSatisfied: false,
325
- visibleFieldConsts: null,
326
- missingRequiredFieldConsts: fieldDefinitions.filter(field => field.required).map(field => field.const),
327
- fallbackToManualAuth: false
328
- };
329
- }
330
-
331
- if (hasLoginFailureFallback) {
332
- return {
333
- hasManagedAuth: true,
334
- allRequiredFieldsSatisfied: false,
335
- visibleFieldConsts: null,
336
- missingRequiredFieldConsts: fieldDefinitions.filter(field => field.required).map(field => field.const),
337
- fallbackToManualAuth: true
338
- };
339
- }
340
-
341
- fieldDefinitions.forEach(field => {
342
- const storedValue = field.managed
343
- ? (field.managedScope === 'user' ? userValues[field.const] : orgValues[field.const])
344
- : undefined;
345
- const hasStoredValue = isFilled(storedValue);
346
- // Show any required field the user still needs to provide, including
347
- // managed fields that have not been configured yet.
348
- if (field.required && (!field.managed || !hasStoredValue)) {
349
- visibleFieldConsts.push(field.const);
350
- }
351
- if (field.required && !hasStoredValue) {
352
- missingRequiredFieldConsts.push(field.const);
353
- allRequiredFieldsSatisfied = false;
354
- }
355
- if (field.required && !field.managed) {
356
- allRequiredFieldsSatisfied = false;
357
- }
358
- });
359
-
360
- return {
361
- hasManagedAuth: managedFieldDefinitions.length > 0,
362
- allRequiredFieldsSatisfied,
363
- visibleFieldConsts,
364
- missingRequiredFieldConsts,
365
- fallbackToManualAuth: false
366
- };
367
- }
368
-
369
- async function resolveApiKeyLoginFields({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false, apiKey, additionalInfo = {}, preferSubmittedValuesForManagedFields = false }) {
370
- const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
371
- const resolvedAdditionalInfo = {
372
- ...(additionalInfo ?? {})
373
- };
374
- if (resolvedAdditionalInfo.apiKey === undefined && apiKey !== undefined) {
375
- resolvedAdditionalInfo.apiKey = apiKey;
376
- }
377
-
378
- const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
379
- const userValues = await getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId });
380
- const missingRequiredFieldConsts = [];
381
-
382
- fieldDefinitions.forEach(field => {
383
- if (!field?.managed) {
384
- if (field.required && !isFilled(resolvedAdditionalInfo[field.const])) {
385
- missingRequiredFieldConsts.push(field.const);
386
- }
387
- return;
388
- }
389
-
390
- const storedValue = field.managedScope === 'user'
391
- ? userValues[field.const]
392
- : orgValues[field.const];
393
- // Prefer managed values when configured, but fall back to submitted
394
- // auth-page input so missing managed fields can still be supplied.
395
- if (isFilled(storedValue) && !(preferSubmittedValuesForManagedFields && isFilled(resolvedAdditionalInfo[field.const]))) {
396
- resolvedAdditionalInfo[field.const] = storedValue;
397
- }
398
-
399
- if (field.required && !isFilled(resolvedAdditionalInfo[field.const])) {
400
- missingRequiredFieldConsts.push(field.const);
401
- }
402
- });
403
-
404
- return {
405
- resolvedAdditionalInfo,
406
- resolvedApiKey: resolvedAdditionalInfo.apiKey ?? apiKey,
407
- missingRequiredFieldConsts
408
- };
409
- }
410
-
411
- async function persistSubmittedManagedValues({ platform, rcAccountId, rcExtensionId, rcUserName, submittedManagedValues = {} }) {
412
- if (!rcAccountId) {
413
- return;
414
- }
415
- if (Object.keys(submittedManagedValues.org ?? {}).length > 0) {
416
- await upsertOrgManagedAuthValues({
417
- rcAccountId,
418
- platform,
419
- values: submittedManagedValues.org
420
- });
421
- }
422
- if (rcExtensionId && Object.keys(submittedManagedValues.user ?? {}).length > 0) {
423
- await upsertUserManagedAuthValues({
424
- rcAccountId,
425
- platform,
426
- rcExtensionId,
427
- rcUserName,
428
- values: submittedManagedValues.user
429
- });
430
- }
431
- }
432
-
433
- exports.MANAGED_AUTH_ORG_DATA_KEY = MANAGED_AUTH_ORG_DATA_KEY;
434
- exports.MANAGED_AUTH_USER_DATA_KEY = MANAGED_AUTH_USER_DATA_KEY;
435
- exports.getApiKeyFieldDefinitions = getApiKeyFieldDefinitions;
436
- exports.getManagedFieldDefinitions = getManagedFieldDefinitions;
437
- exports.getManagedAuthAdminSettings = getManagedAuthAdminSettings;
438
- exports.getManagedAuthState = getManagedAuthState;
439
- exports.hasManagedAuthLoginFailure = hasManagedAuthLoginFailure;
440
- exports.markManagedAuthLoginFailure = markManagedAuthLoginFailure;
441
- exports.clearManagedAuthLoginFailure = clearManagedAuthLoginFailure;
442
- exports.resolveApiKeyLoginFields = resolveApiKeyLoginFields;
443
- exports.persistSubmittedManagedValues = persistSubmittedManagedValues;
444
- exports.upsertOrgManagedAuthValues = upsertOrgManagedAuthValues;
445
- exports.upsertUserManagedAuthValues = upsertUserManagedAuthValues;
446
-
1
+ const connectorRegistry = require('../connector/registry');
2
+ const developerPortal = require('../connector/developerPortal');
3
+ const { AccountDataModel } = require('../models/accountDataModel');
4
+ const { Op } = require('sequelize');
5
+ const { encode, decoded } = require('../lib/encode');
6
+
7
+ const MANAGED_AUTH_ORG_DATA_KEY = 'managed-auth-org';
8
+ const MANAGED_AUTH_USER_DATA_KEY = 'managed-auth-user';
9
+ const MANAGED_AUTH_LOGIN_FAILURE_DATA_KEY = 'managed-auth-login-failure';
10
+
11
+ function getUserManagedAuthDataKey({ rcExtensionId }) {
12
+ return `${MANAGED_AUTH_USER_DATA_KEY}:${rcExtensionId}`;
13
+ }
14
+
15
+ function getManagedAuthLoginFailureDataKey({ rcExtensionId }) {
16
+ return `${MANAGED_AUTH_LOGIN_FAILURE_DATA_KEY}:${rcExtensionId}`;
17
+ }
18
+
19
+ function isFilled(value) {
20
+ return value !== undefined && value !== null && value !== '';
21
+ }
22
+
23
+ async function getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
24
+ if (!platform) {
25
+ return [];
26
+ }
27
+ if (connectorId) {
28
+ const manifest = await developerPortal.getConnectorManifest({ rcAccountId, connectorId, isPrivate });
29
+ if (manifest?.platforms?.[platform]?.auth?.apiKey?.page?.content) {
30
+ return manifest.platforms[platform].auth.apiKey.page.content;
31
+ }
32
+ }
33
+ try {
34
+ const manifest = connectorRegistry.getManifest(platform, true);
35
+ return manifest?.platforms?.[platform]?.auth?.apiKey?.page?.content ?? [];
36
+ }
37
+ catch (error) {
38
+ return [];
39
+ }
40
+ }
41
+
42
+ async function getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
43
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
44
+ return fieldDefinitions.filter(field => field?.managed);
45
+ }
46
+
47
+ function encryptStoredValue(value) {
48
+ return {
49
+ version: 1,
50
+ encrypted: true,
51
+ value: encode(JSON.stringify(value))
52
+ };
53
+ }
54
+
55
+ function decryptStoredValue(value) {
56
+ if (!value) {
57
+ return undefined;
58
+ }
59
+ if (value?.encrypted && value?.value) {
60
+ return JSON.parse(decoded(value.value));
61
+ }
62
+ return value;
63
+ }
64
+
65
+ async function getManagedAuthRecord({ rcAccountId, platform, dataKey }) {
66
+ if (!rcAccountId || !platform) {
67
+ return null;
68
+ }
69
+ return AccountDataModel.findOne({
70
+ where: {
71
+ rcAccountId,
72
+ platformName: platform,
73
+ dataKey
74
+ }
75
+ });
76
+ }
77
+
78
+ async function getOrgManagedAuthValues({ rcAccountId, platform }) {
79
+ const record = await getManagedAuthRecord({
80
+ rcAccountId,
81
+ platform,
82
+ dataKey: MANAGED_AUTH_ORG_DATA_KEY
83
+ });
84
+ const fields = record?.data?.fields ?? {};
85
+ const decryptedFields = {};
86
+ Object.keys(fields).forEach(key => {
87
+ decryptedFields[key] = decryptStoredValue(fields[key]);
88
+ });
89
+ return decryptedFields;
90
+ }
91
+
92
+ async function getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId }) {
93
+ if (!rcExtensionId) {
94
+ return {};
95
+ }
96
+ const userDataKey = getUserManagedAuthDataKey({ rcExtensionId });
97
+ const scopedRecord = await getManagedAuthRecord({
98
+ rcAccountId,
99
+ platform,
100
+ dataKey: userDataKey
101
+ });
102
+ const fields = scopedRecord?.data?.fields ?? {};
103
+ const decryptedFields = {};
104
+ Object.keys(fields).forEach(key => {
105
+ decryptedFields[key] = decryptStoredValue(fields[key]);
106
+ });
107
+ return decryptedFields;
108
+ }
109
+
110
+ async function hasManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
111
+ if (!rcExtensionId) {
112
+ return false;
113
+ }
114
+ const record = await getManagedAuthRecord({
115
+ rcAccountId,
116
+ platform,
117
+ dataKey: getManagedAuthLoginFailureDataKey({ rcExtensionId })
118
+ });
119
+ return !!record;
120
+ }
121
+
122
+ async function markManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
123
+ if (!rcAccountId || !platform || !rcExtensionId) {
124
+ return;
125
+ }
126
+ const dataKey = getManagedAuthLoginFailureDataKey({ rcExtensionId });
127
+ const existingRecord = await getManagedAuthRecord({
128
+ rcAccountId,
129
+ platform,
130
+ dataKey
131
+ });
132
+ const data = {
133
+ failedAt: new Date().toISOString()
134
+ };
135
+ if (existingRecord) {
136
+ await existingRecord.update({ data });
137
+ return existingRecord;
138
+ }
139
+ return AccountDataModel.create({
140
+ rcAccountId,
141
+ platformName: platform,
142
+ dataKey,
143
+ data
144
+ });
145
+ }
146
+
147
+ async function clearManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId }) {
148
+ if (!rcAccountId || !platform || !rcExtensionId) {
149
+ return 0;
150
+ }
151
+ return AccountDataModel.destroy({
152
+ where: {
153
+ rcAccountId,
154
+ platformName: platform,
155
+ dataKey: getManagedAuthLoginFailureDataKey({ rcExtensionId })
156
+ }
157
+ });
158
+ }
159
+
160
+ async function upsertOrgManagedAuthValues({ rcAccountId, platform, values = {}, fieldsToRemove = [] }) {
161
+ const existingRecord = await getManagedAuthRecord({
162
+ rcAccountId,
163
+ platform,
164
+ dataKey: MANAGED_AUTH_ORG_DATA_KEY
165
+ });
166
+ const nextFields = {
167
+ ...(existingRecord?.data?.fields ?? {})
168
+ };
169
+
170
+ Object.keys(values).forEach(key => {
171
+ if (isFilled(values[key])) {
172
+ nextFields[key] = encryptStoredValue(values[key]);
173
+ }
174
+ });
175
+ fieldsToRemove.forEach(key => {
176
+ delete nextFields[key];
177
+ });
178
+
179
+ const nextData = {
180
+ fields: nextFields
181
+ };
182
+
183
+ if (existingRecord) {
184
+ await existingRecord.update({ data: nextData });
185
+ return existingRecord;
186
+ }
187
+ return AccountDataModel.create({
188
+ rcAccountId,
189
+ platformName: platform,
190
+ dataKey: MANAGED_AUTH_ORG_DATA_KEY,
191
+ data: nextData
192
+ });
193
+ }
194
+
195
+ async function upsertUserManagedAuthValues({ rcAccountId, platform, rcExtensionId, rcUserName, values = {}, fieldsToRemove = [] }) {
196
+ if (!rcExtensionId) {
197
+ throw new Error('rcExtensionId is required for user managed auth values');
198
+ }
199
+ const userDataKey = getUserManagedAuthDataKey({ rcExtensionId });
200
+ const existingRecord = await getManagedAuthRecord({
201
+ rcAccountId,
202
+ platform,
203
+ dataKey: userDataKey
204
+ });
205
+ const nextFields = {
206
+ ...(existingRecord?.data?.fields ?? {})
207
+ };
208
+
209
+ Object.keys(values).forEach(key => {
210
+ if (isFilled(values[key])) {
211
+ nextFields[key] = encryptStoredValue(values[key]);
212
+ }
213
+ });
214
+ fieldsToRemove.forEach(key => {
215
+ delete nextFields[key];
216
+ });
217
+
218
+ const nextData = {
219
+ rcExtensionId,
220
+ rcUserName: rcUserName ?? existingRecord?.data?.rcUserName ?? '',
221
+ fields: nextFields
222
+ };
223
+
224
+ if (existingRecord) {
225
+ await existingRecord.update({ data: nextData });
226
+ return existingRecord;
227
+ }
228
+ return AccountDataModel.create({
229
+ rcAccountId,
230
+ platformName: platform,
231
+ dataKey: userDataKey,
232
+ data: nextData
233
+ });
234
+ }
235
+
236
+ function getStoredFieldValue({ value }) {
237
+ if (!isFilled(value)) {
238
+ return {
239
+ hasValue: false,
240
+ value: ''
241
+ };
242
+ }
243
+ return {
244
+ hasValue: true,
245
+ value
246
+ };
247
+ }
248
+
249
+ async function getManagedAuthAdminSettings({ platform, rcAccountId, connectorId, isPrivate = false }) {
250
+ const fieldDefinitions = await getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
251
+ const orgFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'account');
252
+ const userFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'user');
253
+ const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
254
+ const userRecords = await AccountDataModel.findAll({
255
+ where: {
256
+ rcAccountId,
257
+ platformName: platform,
258
+ dataKey: {
259
+ [Op.like]: `${MANAGED_AUTH_USER_DATA_KEY}:%`
260
+ }
261
+ }
262
+ });
263
+ const userEntries = userRecords
264
+ .map(record => {
265
+ const extensionIdFromDataKey = record.dataKey?.split(`${MANAGED_AUTH_USER_DATA_KEY}:`)?.[1];
266
+ const rcExtensionId = record?.data?.rcExtensionId ?? extensionIdFromDataKey;
267
+ if (!rcExtensionId) {
268
+ return null;
269
+ }
270
+ return {
271
+ rcExtensionId,
272
+ rcUserName: record?.data?.rcUserName ?? '',
273
+ fields: record?.data?.fields ?? {}
274
+ };
275
+ })
276
+ .filter(Boolean);
277
+
278
+ const adminOrgValues = {};
279
+ orgFieldDefinitions.forEach(field => {
280
+ adminOrgValues[field.const] = getStoredFieldValue({
281
+ value: orgValues[field.const]
282
+ });
283
+ });
284
+
285
+ const adminUserValues = userEntries.map(entry => {
286
+ const fields = {};
287
+ userFieldDefinitions.forEach(field => {
288
+ fields[field.const] = getStoredFieldValue({
289
+ value: decryptStoredValue(entry?.fields?.[field.const])
290
+ });
291
+ });
292
+ return {
293
+ rcExtensionId: entry.rcExtensionId,
294
+ rcUserName: entry.rcUserName ?? '',
295
+ fields
296
+ };
297
+ });
298
+
299
+ return {
300
+ hasManagedAuth: fieldDefinitions.length > 0,
301
+ fields: fieldDefinitions,
302
+ orgFields: orgFieldDefinitions,
303
+ userFields: userFieldDefinitions,
304
+ orgValues: adminOrgValues,
305
+ userValues: adminUserValues
306
+ };
307
+ }
308
+
309
+ async function getManagedAuthState({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false }) {
310
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
311
+ const managedFieldDefinitions = fieldDefinitions.filter(field => field?.managed);
312
+ const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
313
+ const userValues = await getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId });
314
+ const hasLoginFailureFallback = await hasManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId });
315
+
316
+ const visibleFieldConsts = [];
317
+ const missingRequiredFieldConsts = [];
318
+ let allRequiredFieldsSatisfied = true;
319
+
320
+ // Default behavior for connectors without managed auth: render full API key form.
321
+ if (managedFieldDefinitions.length === 0) {
322
+ return {
323
+ hasManagedAuth: false,
324
+ allRequiredFieldsSatisfied: false,
325
+ visibleFieldConsts: null,
326
+ missingRequiredFieldConsts: fieldDefinitions.filter(field => field.required).map(field => field.const),
327
+ fallbackToManualAuth: false
328
+ };
329
+ }
330
+
331
+ if (hasLoginFailureFallback) {
332
+ return {
333
+ hasManagedAuth: true,
334
+ allRequiredFieldsSatisfied: false,
335
+ visibleFieldConsts: null,
336
+ missingRequiredFieldConsts: fieldDefinitions.filter(field => field.required).map(field => field.const),
337
+ fallbackToManualAuth: true
338
+ };
339
+ }
340
+
341
+ fieldDefinitions.forEach(field => {
342
+ const storedValue = field.managed
343
+ ? (field.managedScope === 'user' ? userValues[field.const] : orgValues[field.const])
344
+ : undefined;
345
+ const hasStoredValue = isFilled(storedValue);
346
+ // Show any required field the user still needs to provide, including
347
+ // managed fields that have not been configured yet.
348
+ if (field.required && (!field.managed || !hasStoredValue)) {
349
+ visibleFieldConsts.push(field.const);
350
+ }
351
+ if (field.required && !hasStoredValue) {
352
+ missingRequiredFieldConsts.push(field.const);
353
+ allRequiredFieldsSatisfied = false;
354
+ }
355
+ if (field.required && !field.managed) {
356
+ allRequiredFieldsSatisfied = false;
357
+ }
358
+ });
359
+
360
+ return {
361
+ hasManagedAuth: managedFieldDefinitions.length > 0,
362
+ allRequiredFieldsSatisfied,
363
+ visibleFieldConsts,
364
+ missingRequiredFieldConsts,
365
+ fallbackToManualAuth: false
366
+ };
367
+ }
368
+
369
+ async function resolveApiKeyLoginFields({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false, apiKey, additionalInfo = {}, preferSubmittedValuesForManagedFields = false }) {
370
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
371
+ const resolvedAdditionalInfo = {
372
+ ...(additionalInfo ?? {})
373
+ };
374
+ if (resolvedAdditionalInfo.apiKey === undefined && apiKey !== undefined) {
375
+ resolvedAdditionalInfo.apiKey = apiKey;
376
+ }
377
+
378
+ const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
379
+ const userValues = await getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId });
380
+ const missingRequiredFieldConsts = [];
381
+
382
+ fieldDefinitions.forEach(field => {
383
+ if (!field?.managed) {
384
+ if (field.required && !isFilled(resolvedAdditionalInfo[field.const])) {
385
+ missingRequiredFieldConsts.push(field.const);
386
+ }
387
+ return;
388
+ }
389
+
390
+ const storedValue = field.managedScope === 'user'
391
+ ? userValues[field.const]
392
+ : orgValues[field.const];
393
+ // Prefer managed values when configured, but fall back to submitted
394
+ // auth-page input so missing managed fields can still be supplied.
395
+ if (isFilled(storedValue) && !(preferSubmittedValuesForManagedFields && isFilled(resolvedAdditionalInfo[field.const]))) {
396
+ resolvedAdditionalInfo[field.const] = storedValue;
397
+ }
398
+
399
+ if (field.required && !isFilled(resolvedAdditionalInfo[field.const])) {
400
+ missingRequiredFieldConsts.push(field.const);
401
+ }
402
+ });
403
+
404
+ return {
405
+ resolvedAdditionalInfo,
406
+ resolvedApiKey: resolvedAdditionalInfo.apiKey ?? apiKey,
407
+ missingRequiredFieldConsts
408
+ };
409
+ }
410
+
411
+ async function persistSubmittedManagedValues({ platform, rcAccountId, rcExtensionId, rcUserName, submittedManagedValues = {} }) {
412
+ if (!rcAccountId) {
413
+ return;
414
+ }
415
+ if (Object.keys(submittedManagedValues.org ?? {}).length > 0) {
416
+ await upsertOrgManagedAuthValues({
417
+ rcAccountId,
418
+ platform,
419
+ values: submittedManagedValues.org
420
+ });
421
+ }
422
+ if (rcExtensionId && Object.keys(submittedManagedValues.user ?? {}).length > 0) {
423
+ await upsertUserManagedAuthValues({
424
+ rcAccountId,
425
+ platform,
426
+ rcExtensionId,
427
+ rcUserName,
428
+ values: submittedManagedValues.user
429
+ });
430
+ }
431
+ }
432
+
433
+ exports.MANAGED_AUTH_ORG_DATA_KEY = MANAGED_AUTH_ORG_DATA_KEY;
434
+ exports.MANAGED_AUTH_USER_DATA_KEY = MANAGED_AUTH_USER_DATA_KEY;
435
+ exports.getApiKeyFieldDefinitions = getApiKeyFieldDefinitions;
436
+ exports.getManagedFieldDefinitions = getManagedFieldDefinitions;
437
+ exports.getManagedAuthAdminSettings = getManagedAuthAdminSettings;
438
+ exports.getManagedAuthState = getManagedAuthState;
439
+ exports.hasManagedAuthLoginFailure = hasManagedAuthLoginFailure;
440
+ exports.markManagedAuthLoginFailure = markManagedAuthLoginFailure;
441
+ exports.clearManagedAuthLoginFailure = clearManagedAuthLoginFailure;
442
+ exports.resolveApiKeyLoginFields = resolveApiKeyLoginFields;
443
+ exports.persistSubmittedManagedValues = persistSubmittedManagedValues;
444
+ exports.upsertOrgManagedAuthValues = upsertOrgManagedAuthValues;
445
+ exports.upsertUserManagedAuthValues = upsertUserManagedAuthValues;
446
+