@app-connect/core 1.7.24 → 1.7.26

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 (137) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -42
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -163
  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 -117
  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 +296 -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 +984 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/plugin.js +208 -208
  25. package/handlers/user.js +142 -142
  26. package/index.js +3140 -2652
  27. package/jest.config.js +56 -56
  28. package/lib/analytics.js +54 -54
  29. package/lib/authSession.js +109 -109
  30. package/lib/cacheCleanup.js +21 -0
  31. package/lib/callLogComposer.js +898 -898
  32. package/lib/callLogLookup.js +34 -0
  33. package/lib/constants.js +8 -8
  34. package/lib/debugTracer.js +177 -177
  35. package/lib/encode.js +30 -30
  36. package/lib/errorHandler.js +218 -206
  37. package/lib/generalErrorMessage.js +41 -41
  38. package/lib/jwt.js +18 -18
  39. package/lib/logger.js +190 -190
  40. package/lib/migrateCallLogsSchema.js +116 -0
  41. package/lib/ringcentral.js +266 -266
  42. package/lib/s3ErrorLogReport.js +65 -65
  43. package/lib/sharedSMSComposer.js +471 -471
  44. package/lib/util.js +67 -67
  45. package/mcp/README.md +412 -395
  46. package/mcp/lib/validator.js +91 -91
  47. package/mcp/mcpHandler.js +425 -425
  48. package/mcp/tools/cancelAppointment.js +101 -0
  49. package/mcp/tools/checkAuthStatus.js +105 -105
  50. package/mcp/tools/confirmAppointment.js +101 -0
  51. package/mcp/tools/createAppointment.js +157 -0
  52. package/mcp/tools/createCallLog.js +327 -316
  53. package/mcp/tools/createContact.js +117 -117
  54. package/mcp/tools/createMessageLog.js +287 -287
  55. package/mcp/tools/doAuth.js +60 -60
  56. package/mcp/tools/findContactByName.js +93 -93
  57. package/mcp/tools/findContactByPhone.js +101 -101
  58. package/mcp/tools/getCallLog.js +111 -102
  59. package/mcp/tools/getGoogleFilePicker.js +99 -99
  60. package/mcp/tools/getHelp.js +43 -43
  61. package/mcp/tools/getPublicConnectors.js +94 -94
  62. package/mcp/tools/getSessionInfo.js +90 -90
  63. package/mcp/tools/index.js +51 -41
  64. package/mcp/tools/listAppointments.js +163 -0
  65. package/mcp/tools/logout.js +96 -96
  66. package/mcp/tools/rcGetCallLogs.js +65 -65
  67. package/mcp/tools/updateAppointment.js +154 -0
  68. package/mcp/tools/updateCallLog.js +130 -126
  69. package/mcp/ui/App/App.tsx +358 -358
  70. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  71. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  72. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  73. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  74. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  75. package/mcp/ui/App/lib/callTool.ts +130 -130
  76. package/mcp/ui/App/lib/debugLog.ts +41 -41
  77. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  78. package/mcp/ui/App/main.css +5 -5
  79. package/mcp/ui/App/root.tsx +13 -13
  80. package/mcp/ui/index.html +13 -13
  81. package/mcp/ui/package-lock.json +6356 -6356
  82. package/mcp/ui/package.json +25 -25
  83. package/mcp/ui/tsconfig.json +26 -26
  84. package/mcp/ui/vite.config.ts +16 -16
  85. package/models/accountDataModel.js +33 -33
  86. package/models/adminConfigModel.js +35 -35
  87. package/models/cacheModel.js +30 -26
  88. package/models/callDownListModel.js +34 -34
  89. package/models/callLogModel.js +33 -27
  90. package/models/dynamo/connectorSchema.js +146 -146
  91. package/models/dynamo/lockSchema.js +24 -24
  92. package/models/dynamo/noteCacheSchema.js +29 -29
  93. package/models/llmSessionModel.js +17 -17
  94. package/models/messageLogModel.js +25 -25
  95. package/models/sequelize.js +16 -16
  96. package/models/userModel.js +45 -45
  97. package/package.json +72 -72
  98. package/releaseNotes.json +1093 -1073
  99. package/test/connector/proxy/engine.test.js +126 -93
  100. package/test/connector/proxy/index.test.js +279 -279
  101. package/test/connector/proxy/sample.json +161 -161
  102. package/test/connector/registry.test.js +415 -415
  103. package/test/handlers/admin.test.js +616 -616
  104. package/test/handlers/auth.test.js +1018 -1015
  105. package/test/handlers/contact.test.js +1014 -1014
  106. package/test/handlers/log.test.js +1298 -1160
  107. package/test/handlers/managedAuth.test.js +458 -458
  108. package/test/handlers/plugin.test.js +380 -380
  109. package/test/index.test.js +105 -105
  110. package/test/lib/cacheCleanup.test.js +42 -0
  111. package/test/lib/callLogComposer.test.js +1231 -1231
  112. package/test/lib/debugTracer.test.js +328 -328
  113. package/test/lib/jwt.test.js +176 -176
  114. package/test/lib/logger.test.js +206 -206
  115. package/test/lib/oauth.test.js +359 -359
  116. package/test/lib/ringcentral.test.js +467 -467
  117. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  118. package/test/lib/util.test.js +329 -329
  119. package/test/mcp/tools/checkAuthStatus.test.js +83 -82
  120. package/test/mcp/tools/createCallLog.test.js +436 -436
  121. package/test/mcp/tools/createContact.test.js +58 -58
  122. package/test/mcp/tools/createMessageLog.test.js +595 -595
  123. package/test/mcp/tools/doAuth.test.js +113 -113
  124. package/test/mcp/tools/findContactByName.test.js +275 -275
  125. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  126. package/test/mcp/tools/getCallLog.test.js +298 -298
  127. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  128. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  129. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  130. package/test/mcp/tools/logout.test.js +233 -233
  131. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  132. package/test/mcp/tools/updateCallLog.test.js +360 -360
  133. package/test/models/accountDataModel.test.js +98 -98
  134. package/test/models/dynamo/connectorSchema.test.js +189 -189
  135. package/test/models/models.test.js +568 -539
  136. package/test/routes/managedAuthRoutes.test.js +104 -129
  137. 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({ platform, connectorId, isPrivate = false }) {
24
- if (!platform) {
25
- return [];
26
- }
27
- if (connectorId) {
28
- const manifest = await developerPortal.getConnectorManifest({ 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({ platform, connectorId, isPrivate = false }) {
43
- const fieldDefinitions = await getApiKeyFieldDefinitions({ 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({ 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({ 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({ 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
+