@app-connect/core 1.7.20 → 1.7.22
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.
- package/README.md +8 -1
- package/connector/developerPortal.js +4 -4
- package/docs/README.md +50 -0
- package/docs/architecture.md +93 -0
- package/docs/connectors.md +117 -0
- package/docs/handlers.md +125 -0
- package/docs/libraries.md +101 -0
- package/docs/models.md +144 -0
- package/docs/routes.md +115 -0
- package/docs/tests.md +73 -0
- package/handlers/admin.js +22 -2
- package/handlers/auth.js +51 -10
- package/handlers/contact.js +7 -2
- package/handlers/log.js +4 -4
- package/handlers/managedAuth.js +446 -0
- package/index.js +263 -38
- package/lib/jwt.js +1 -1
- package/mcp/tools/createCallLog.js +5 -1
- package/mcp/tools/createContact.js +5 -1
- package/mcp/tools/createMessageLog.js +5 -1
- package/mcp/tools/findContactByName.js +5 -1
- package/mcp/tools/findContactByPhone.js +6 -2
- package/mcp/tools/getCallLog.js +5 -1
- package/mcp/tools/rcGetCallLogs.js +6 -2
- package/mcp/tools/updateCallLog.js +5 -1
- package/mcp/ui/App/lib/developerPortal.ts +1 -1
- package/package.json +1 -1
- package/releaseNotes.json +20 -0
- package/test/handlers/admin.test.js +34 -0
- package/test/handlers/auth.test.js +402 -6
- package/test/handlers/contact.test.js +162 -0
- package/test/handlers/managedAuth.test.js +458 -0
- package/test/index.test.js +105 -0
- package/test/lib/jwt.test.js +15 -0
- package/test/mcp/tools/createCallLog.test.js +11 -0
- package/test/mcp/tools/createContact.test.js +58 -0
- package/test/mcp/tools/createMessageLog.test.js +15 -0
- package/test/mcp/tools/findContactByName.test.js +12 -0
- package/test/mcp/tools/findContactByPhone.test.js +12 -0
- package/test/mcp/tools/getCallLog.test.js +12 -0
- package/test/mcp/tools/rcGetCallLogs.test.js +56 -0
- package/test/mcp/tools/updateCallLog.test.js +14 -0
- package/test/routes/managedAuthRoutes.test.js +132 -0
- package/test/setup.js +2 -0
|
@@ -59,7 +59,11 @@ async function execute(args) {
|
|
|
59
59
|
const { jwtToken, incomingData } = args;
|
|
60
60
|
|
|
61
61
|
// Decode JWT to get userId and platform
|
|
62
|
-
const
|
|
62
|
+
const decodedToken = jwt.decodeJwt(jwtToken);
|
|
63
|
+
if (!decodedToken) {
|
|
64
|
+
throw new Error('Invalid JWT token');
|
|
65
|
+
}
|
|
66
|
+
const { id: userId, platform } = decodedToken;
|
|
63
67
|
|
|
64
68
|
if (!userId) {
|
|
65
69
|
throw new Error('Invalid JWT token: userId not found');
|
|
@@ -90,7 +90,7 @@ export async function fetchManifest(
|
|
|
90
90
|
dbg.info('fetchManifest: connectorId=', connectorId, 'isPrivate=', isPrivate)
|
|
91
91
|
|
|
92
92
|
const url = isPrivate && rcAccountId
|
|
93
|
-
? `${PORTAL_BASE}/connectors/${connectorId}/manifest?
|
|
93
|
+
? `${PORTAL_BASE}/connectors/${connectorId}/manifest?access=internal&type=connector&accountId=${encodeURIComponent(rcAccountId)}`
|
|
94
94
|
: `${PORTAL_BASE}/connectors/${connectorId}/manifest`
|
|
95
95
|
|
|
96
96
|
dbg.info('fetchManifest: GET', url)
|
package/package.json
CHANGED
package/releaseNotes.json
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.7.22": {
|
|
3
|
+
"global": [
|
|
4
|
+
{
|
|
5
|
+
"type": "New",
|
|
6
|
+
"description": "Managed auth fields for non-OAuth platforms"
|
|
7
|
+
}
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"1.7.21": {
|
|
11
|
+
"global": [
|
|
12
|
+
{
|
|
13
|
+
"type": "Fix",
|
|
14
|
+
"description": "Refresh contact for deleted contact issue"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type": "Fix",
|
|
18
|
+
"description": "Click-to-sms button issue"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
2
22
|
"1.7.20": {
|
|
3
23
|
"global": [
|
|
4
24
|
{
|
|
@@ -123,6 +123,40 @@ describe('Admin Handler', () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
describe('validateRcUserToken', () => {
|
|
127
|
+
test('should return rc account and extension identity from valid token', async () => {
|
|
128
|
+
axios.get.mockResolvedValue({
|
|
129
|
+
data: {
|
|
130
|
+
account: { id: 'rc-account-789' },
|
|
131
|
+
id: 'extension-789',
|
|
132
|
+
contact: {
|
|
133
|
+
firstName: 'Alex',
|
|
134
|
+
lastName: 'Johnson'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const result = await adminHandler.validateRcUserToken({
|
|
140
|
+
rcAccessToken: 'valid-user-token'
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual({
|
|
144
|
+
rcAccountId: 'rc-account-789',
|
|
145
|
+
rcExtensionId: 'extension-789',
|
|
146
|
+
rcUserName: 'Alex Johnson'
|
|
147
|
+
});
|
|
148
|
+
expect(axios.get).toHaveBeenCalledWith(
|
|
149
|
+
'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
|
|
150
|
+
{ headers: { Authorization: 'Bearer valid-user-token' } }
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should throw when rcAccessToken is missing', async () => {
|
|
155
|
+
await expect(adminHandler.validateRcUserToken({})).rejects.toThrow('rcAccessToken is required');
|
|
156
|
+
expect(axios.get).not.toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
126
160
|
describe('upsertAdminSettings', () => {
|
|
127
161
|
test('should create new admin config when none exists', async () => {
|
|
128
162
|
// Act
|
|
@@ -22,6 +22,8 @@ const oauth = require('../../lib/oauth');
|
|
|
22
22
|
const { Connector } = require('../../models/dynamo/connectorSchema');
|
|
23
23
|
const { RingCentral } = require('../../lib/ringcentral');
|
|
24
24
|
const adminCore = require('../../handlers/admin');
|
|
25
|
+
const { AccountDataModel } = require('../../models/accountDataModel');
|
|
26
|
+
const { encode } = require('../../lib/encode');
|
|
25
27
|
|
|
26
28
|
describe('Auth Handler', () => {
|
|
27
29
|
const originalEnv = process.env;
|
|
@@ -31,6 +33,7 @@ describe('Auth Handler', () => {
|
|
|
31
33
|
jest.clearAllMocks();
|
|
32
34
|
global.testUtils.resetConnectorRegistry();
|
|
33
35
|
process.env = { ...originalEnv };
|
|
36
|
+
process.env.APP_SERVER_SECRET_KEY = 'test-app-server-secret-key-123456';
|
|
34
37
|
});
|
|
35
38
|
|
|
36
39
|
afterEach(() => {
|
|
@@ -38,6 +41,10 @@ describe('Auth Handler', () => {
|
|
|
38
41
|
});
|
|
39
42
|
|
|
40
43
|
describe('onApiKeyLogin', () => {
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
await AccountDataModel.destroy({ where: {} });
|
|
46
|
+
});
|
|
47
|
+
|
|
41
48
|
test('should handle successful API key login', async () => {
|
|
42
49
|
// Arrange
|
|
43
50
|
const mockUserInfo = {
|
|
@@ -60,7 +67,7 @@ describe('Auth Handler', () => {
|
|
|
60
67
|
getBasicAuth: jest.fn().mockReturnValue('dGVzdC1hcGkta2V5Og=='),
|
|
61
68
|
getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
|
|
62
69
|
});
|
|
63
|
-
|
|
70
|
+
|
|
64
71
|
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
65
72
|
|
|
66
73
|
const requestData = {
|
|
@@ -82,7 +89,7 @@ describe('Auth Handler', () => {
|
|
|
82
89
|
expect(mockConnector.getUserInfo).toHaveBeenCalledWith({
|
|
83
90
|
authHeader: 'Basic dGVzdC1hcGkta2V5Og==',
|
|
84
91
|
hostname: 'test.example.com',
|
|
85
|
-
additionalInfo: {},
|
|
92
|
+
additionalInfo: { apiKey: 'test-api-key' },
|
|
86
93
|
apiKey: 'test-api-key',
|
|
87
94
|
platform: 'testCRM',
|
|
88
95
|
proxyId: undefined
|
|
@@ -105,7 +112,7 @@ describe('Auth Handler', () => {
|
|
|
105
112
|
getBasicAuth: jest.fn().mockReturnValue('dGVzdC1hcGkta2V5Og=='),
|
|
106
113
|
getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
|
|
107
114
|
});
|
|
108
|
-
|
|
115
|
+
|
|
109
116
|
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
110
117
|
|
|
111
118
|
const requestData = {
|
|
@@ -123,6 +130,394 @@ describe('Auth Handler', () => {
|
|
|
123
130
|
expect(result.returnMessage).toEqual(mockUserInfo.returnMessage);
|
|
124
131
|
});
|
|
125
132
|
|
|
133
|
+
test('should mark managed auth auto-login failure so the next attempt can fall back to manual auth', async () => {
|
|
134
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
135
|
+
platforms: {
|
|
136
|
+
testCRM: {
|
|
137
|
+
auth: {
|
|
138
|
+
type: 'apiKey',
|
|
139
|
+
apiKey: {
|
|
140
|
+
page: {
|
|
141
|
+
content: [
|
|
142
|
+
{ const: 'tenantId', required: true, managed: true, managedScope: 'account' },
|
|
143
|
+
{ const: 'apiKey', required: true, managed: true, managedScope: 'user' }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await AccountDataModel.create({
|
|
153
|
+
rcAccountId: 'rc-account-fail',
|
|
154
|
+
platformName: 'testCRM',
|
|
155
|
+
dataKey: 'managed-auth-org',
|
|
156
|
+
data: {
|
|
157
|
+
fields: {
|
|
158
|
+
tenantId: { version: 1, encrypted: true, value: encode(JSON.stringify('tenant-1')) }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
await AccountDataModel.create({
|
|
163
|
+
rcAccountId: 'rc-account-fail',
|
|
164
|
+
platformName: 'testCRM',
|
|
165
|
+
dataKey: 'managed-auth-user:101',
|
|
166
|
+
data: {
|
|
167
|
+
rcExtensionId: '101',
|
|
168
|
+
rcUserName: 'Agent 101',
|
|
169
|
+
fields: {
|
|
170
|
+
apiKey: { version: 1, encrypted: true, value: encode(JSON.stringify('bad-stored-key')) }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
176
|
+
getBasicAuth: jest.fn().mockReturnValue('encoded-bad-key'),
|
|
177
|
+
getUserInfo: jest.fn().mockResolvedValue({
|
|
178
|
+
successful: false,
|
|
179
|
+
platformUserInfo: null,
|
|
180
|
+
returnMessage: {
|
|
181
|
+
messageType: 'error',
|
|
182
|
+
message: 'Invalid API key',
|
|
183
|
+
ttl: 3000
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
188
|
+
|
|
189
|
+
const result = await authHandler.onApiKeyLogin({
|
|
190
|
+
platform: 'testCRM',
|
|
191
|
+
hostname: 'test.example.com',
|
|
192
|
+
rcAccountId: 'rc-account-fail',
|
|
193
|
+
rcExtensionId: '101',
|
|
194
|
+
additionalInfo: {}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(result.userInfo).toBeNull();
|
|
198
|
+
const failureRecord = await AccountDataModel.findOne({
|
|
199
|
+
where: {
|
|
200
|
+
rcAccountId: 'rc-account-fail',
|
|
201
|
+
platformName: 'testCRM',
|
|
202
|
+
dataKey: 'managed-auth-login-failure:101'
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
expect(failureRecord).not.toBeNull();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('should merge stored org managed auth values into additionalInfo', async () => {
|
|
209
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
210
|
+
platforms: {
|
|
211
|
+
testCRM: {
|
|
212
|
+
auth: {
|
|
213
|
+
type: 'apiKey',
|
|
214
|
+
apiKey: {
|
|
215
|
+
page: {
|
|
216
|
+
content: [
|
|
217
|
+
{ const: 'apiKey', required: true, managed: true, managedScope: 'account' },
|
|
218
|
+
{ const: 'tenantId', required: true, managed: true, managedScope: 'account' },
|
|
219
|
+
{ const: 'userToken', required: true }
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
await AccountDataModel.create({
|
|
228
|
+
rcAccountId: 'rc-account-1',
|
|
229
|
+
platformName: 'testCRM',
|
|
230
|
+
dataKey: 'managed-auth-org',
|
|
231
|
+
data: {
|
|
232
|
+
fields: {
|
|
233
|
+
apiKey: { version: 1, encrypted: true, value: encode(JSON.stringify('stored-api-key')) },
|
|
234
|
+
tenantId: { version: 1, encrypted: true, value: encode(JSON.stringify('tenant-1')) }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const mockUserInfo = {
|
|
240
|
+
successful: true,
|
|
241
|
+
platformUserInfo: {
|
|
242
|
+
id: 'test-user-id',
|
|
243
|
+
name: 'Test User',
|
|
244
|
+
platformAdditionalInfo: {}
|
|
245
|
+
},
|
|
246
|
+
returnMessage: { messageType: 'success', message: 'ok' }
|
|
247
|
+
};
|
|
248
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
249
|
+
getBasicAuth: jest.fn().mockReturnValue('encoded-shared'),
|
|
250
|
+
getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
|
|
251
|
+
});
|
|
252
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
253
|
+
|
|
254
|
+
await authHandler.onApiKeyLogin({
|
|
255
|
+
platform: 'testCRM',
|
|
256
|
+
hostname: 'test.example.com',
|
|
257
|
+
rcAccountId: 'rc-account-1',
|
|
258
|
+
additionalInfo: { userToken: 'user-token-1' }
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'stored-api-key' });
|
|
262
|
+
expect(mockConnector.getUserInfo).toHaveBeenCalledWith(expect.objectContaining({
|
|
263
|
+
additionalInfo: expect.objectContaining({
|
|
264
|
+
apiKey: 'stored-api-key',
|
|
265
|
+
tenantId: 'tenant-1',
|
|
266
|
+
userToken: 'user-token-1'
|
|
267
|
+
})
|
|
268
|
+
}));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('should allow submitted shared fields to satisfy missing required managed auth values', async () => {
|
|
272
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
273
|
+
platforms: {
|
|
274
|
+
testCRM: {
|
|
275
|
+
auth: {
|
|
276
|
+
type: 'apiKey',
|
|
277
|
+
apiKey: {
|
|
278
|
+
page: {
|
|
279
|
+
content: [
|
|
280
|
+
{ const: 'companyId', required: true, managed: true, managedScope: 'account' },
|
|
281
|
+
{ const: 'userToken', required: true }
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
291
|
+
getBasicAuth: jest.fn(),
|
|
292
|
+
getUserInfo: jest.fn().mockResolvedValue({
|
|
293
|
+
successful: true,
|
|
294
|
+
platformUserInfo: {
|
|
295
|
+
id: 'test-user-id',
|
|
296
|
+
name: 'Test User',
|
|
297
|
+
platformAdditionalInfo: {}
|
|
298
|
+
},
|
|
299
|
+
returnMessage: { messageType: 'success', message: 'ok' }
|
|
300
|
+
})
|
|
301
|
+
});
|
|
302
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
303
|
+
|
|
304
|
+
const result = await authHandler.onApiKeyLogin({
|
|
305
|
+
platform: 'testCRM',
|
|
306
|
+
hostname: 'test.example.com',
|
|
307
|
+
rcAccountId: 'rc-account-2',
|
|
308
|
+
additionalInfo: {
|
|
309
|
+
companyId: 'company-123',
|
|
310
|
+
userToken: 'user-token-1'
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(result.userInfo).not.toBeNull();
|
|
315
|
+
expect(mockConnector.getUserInfo).toHaveBeenCalledWith(expect.objectContaining({
|
|
316
|
+
additionalInfo: expect.objectContaining({
|
|
317
|
+
companyId: 'company-123',
|
|
318
|
+
userToken: 'user-token-1'
|
|
319
|
+
})
|
|
320
|
+
}));
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('should not persist submitted managed auth values from end users', async () => {
|
|
324
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
325
|
+
platforms: {
|
|
326
|
+
testCRM: {
|
|
327
|
+
auth: {
|
|
328
|
+
type: 'apiKey',
|
|
329
|
+
apiKey: {
|
|
330
|
+
page: {
|
|
331
|
+
content: [
|
|
332
|
+
{ const: 'companyId', required: false, managed: true, managedScope: 'account' },
|
|
333
|
+
{ const: 'userToken', required: true }
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const mockUserInfo = {
|
|
343
|
+
successful: true,
|
|
344
|
+
platformUserInfo: {
|
|
345
|
+
id: 'test-user-id',
|
|
346
|
+
name: 'Test User',
|
|
347
|
+
platformAdditionalInfo: {}
|
|
348
|
+
},
|
|
349
|
+
returnMessage: { messageType: 'success', message: 'ok' }
|
|
350
|
+
};
|
|
351
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
352
|
+
getBasicAuth: jest.fn().mockReturnValue('encoded'),
|
|
353
|
+
getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
|
|
354
|
+
});
|
|
355
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
356
|
+
|
|
357
|
+
await authHandler.onApiKeyLogin({
|
|
358
|
+
platform: 'testCRM',
|
|
359
|
+
hostname: 'test.example.com',
|
|
360
|
+
rcAccountId: 'rc-account-2',
|
|
361
|
+
additionalInfo: {
|
|
362
|
+
companyId: 'company-123',
|
|
363
|
+
userToken: 'user-token-1'
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(mockConnector.getUserInfo).toHaveBeenCalledWith(expect.objectContaining({
|
|
368
|
+
additionalInfo: expect.objectContaining({
|
|
369
|
+
companyId: 'company-123',
|
|
370
|
+
userToken: 'user-token-1'
|
|
371
|
+
})
|
|
372
|
+
}));
|
|
373
|
+
|
|
374
|
+
const stored = await AccountDataModel.findOne({
|
|
375
|
+
where: {
|
|
376
|
+
rcAccountId: 'rc-account-2',
|
|
377
|
+
platformName: 'testCRM',
|
|
378
|
+
dataKey: 'managed-auth-org'
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
expect(stored).toBeNull();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('should allow manual fallback values to override stored managed credentials and clear failure state after success', async () => {
|
|
385
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
386
|
+
platforms: {
|
|
387
|
+
testCRM: {
|
|
388
|
+
auth: {
|
|
389
|
+
type: 'apiKey',
|
|
390
|
+
apiKey: {
|
|
391
|
+
page: {
|
|
392
|
+
content: [
|
|
393
|
+
{ const: 'apiKey', required: true, managed: true, managedScope: 'user' },
|
|
394
|
+
{ const: 'tenantId', required: true, managed: true, managedScope: 'account' }
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await AccountDataModel.create({
|
|
404
|
+
rcAccountId: 'rc-account-recover',
|
|
405
|
+
platformName: 'testCRM',
|
|
406
|
+
dataKey: 'managed-auth-org',
|
|
407
|
+
data: {
|
|
408
|
+
fields: {
|
|
409
|
+
tenantId: { version: 1, encrypted: true, value: encode(JSON.stringify('stored-tenant')) }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
await AccountDataModel.create({
|
|
414
|
+
rcAccountId: 'rc-account-recover',
|
|
415
|
+
platformName: 'testCRM',
|
|
416
|
+
dataKey: 'managed-auth-user:202',
|
|
417
|
+
data: {
|
|
418
|
+
rcExtensionId: '202',
|
|
419
|
+
rcUserName: 'Agent 202',
|
|
420
|
+
fields: {
|
|
421
|
+
apiKey: { version: 1, encrypted: true, value: encode(JSON.stringify('stored-bad-key')) }
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
await AccountDataModel.create({
|
|
426
|
+
rcAccountId: 'rc-account-recover',
|
|
427
|
+
platformName: 'testCRM',
|
|
428
|
+
dataKey: 'managed-auth-login-failure:202',
|
|
429
|
+
data: {
|
|
430
|
+
failedAt: '2026-04-07T00:00:00.000Z'
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
435
|
+
getBasicAuth: jest.fn().mockReturnValue('encoded-manual-key'),
|
|
436
|
+
getUserInfo: jest.fn().mockResolvedValue({
|
|
437
|
+
successful: true,
|
|
438
|
+
platformUserInfo: {
|
|
439
|
+
id: 'test-user-id',
|
|
440
|
+
name: 'Recovered User',
|
|
441
|
+
platformAdditionalInfo: {}
|
|
442
|
+
},
|
|
443
|
+
returnMessage: { messageType: 'success', message: 'ok' }
|
|
444
|
+
})
|
|
445
|
+
});
|
|
446
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
447
|
+
|
|
448
|
+
const result = await authHandler.onApiKeyLogin({
|
|
449
|
+
platform: 'testCRM',
|
|
450
|
+
hostname: 'test.example.com',
|
|
451
|
+
rcAccountId: 'rc-account-recover',
|
|
452
|
+
rcExtensionId: '202',
|
|
453
|
+
additionalInfo: {
|
|
454
|
+
apiKey: 'manual-good-key',
|
|
455
|
+
tenantId: 'manual-tenant'
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(result.userInfo).not.toBeNull();
|
|
460
|
+
expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'manual-good-key' });
|
|
461
|
+
expect(mockConnector.getUserInfo).toHaveBeenCalledWith(expect.objectContaining({
|
|
462
|
+
additionalInfo: {
|
|
463
|
+
apiKey: 'manual-good-key',
|
|
464
|
+
tenantId: 'manual-tenant'
|
|
465
|
+
}
|
|
466
|
+
}));
|
|
467
|
+
|
|
468
|
+
const failureRecord = await AccountDataModel.findOne({
|
|
469
|
+
where: {
|
|
470
|
+
rcAccountId: 'rc-account-recover',
|
|
471
|
+
platformName: 'testCRM',
|
|
472
|
+
dataKey: 'managed-auth-login-failure:202'
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
expect(failureRecord).toBeNull();
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test('should return warning when required auth fields are missing', async () => {
|
|
479
|
+
connectorRegistry.getManifest.mockReturnValue({
|
|
480
|
+
platforms: {
|
|
481
|
+
testCRM: {
|
|
482
|
+
auth: {
|
|
483
|
+
type: 'apiKey',
|
|
484
|
+
apiKey: {
|
|
485
|
+
page: {
|
|
486
|
+
content: [
|
|
487
|
+
{ const: 'tenantId', required: true, managed: true, managedScope: 'account' },
|
|
488
|
+
{ const: 'userToken', required: true }
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const mockConnector = global.testUtils.createMockConnector({
|
|
498
|
+
getBasicAuth: jest.fn(),
|
|
499
|
+
getUserInfo: jest.fn()
|
|
500
|
+
});
|
|
501
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
502
|
+
|
|
503
|
+
const result = await authHandler.onApiKeyLogin({
|
|
504
|
+
platform: 'testCRM',
|
|
505
|
+
hostname: 'test.example.com',
|
|
506
|
+
rcAccountId: 'rc-account-4',
|
|
507
|
+
additionalInfo: {}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
expect(result.userInfo).toBeNull();
|
|
511
|
+
expect(result.returnMessage).toEqual({
|
|
512
|
+
messageType: 'warning',
|
|
513
|
+
message: 'Missing required authentication fields.',
|
|
514
|
+
ttl: 3000,
|
|
515
|
+
missingRequiredFieldConsts: ['tenantId', 'userToken']
|
|
516
|
+
});
|
|
517
|
+
expect(mockConnector.getBasicAuth).not.toHaveBeenCalled();
|
|
518
|
+
expect(mockConnector.getUserInfo).not.toHaveBeenCalled();
|
|
519
|
+
});
|
|
520
|
+
|
|
126
521
|
test('should throw error when connector not found', async () => {
|
|
127
522
|
// Arrange
|
|
128
523
|
connectorRegistry.getConnector.mockImplementation(() => {
|
|
@@ -160,7 +555,7 @@ describe('Auth Handler', () => {
|
|
|
160
555
|
getOauthInfo: jest.fn().mockResolvedValue({}),
|
|
161
556
|
authValidation: jest.fn().mockResolvedValue(mockValidationResponse)
|
|
162
557
|
});
|
|
163
|
-
|
|
558
|
+
|
|
164
559
|
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
165
560
|
|
|
166
561
|
// Mock UserModel.findOne to return a user
|
|
@@ -229,7 +624,7 @@ describe('Auth Handler', () => {
|
|
|
229
624
|
getOauthInfo: jest.fn().mockResolvedValue({}),
|
|
230
625
|
authValidation: jest.fn().mockResolvedValue(mockValidationResponse)
|
|
231
626
|
});
|
|
232
|
-
|
|
627
|
+
|
|
233
628
|
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
234
629
|
|
|
235
630
|
// Mock UserModel.findOne to return a user
|
|
@@ -616,4 +1011,5 @@ describe('Auth Handler', () => {
|
|
|
616
1011
|
expect(RingCentral).not.toHaveBeenCalled();
|
|
617
1012
|
});
|
|
618
1013
|
});
|
|
619
|
-
});
|
|
1014
|
+
});
|
|
1015
|
+
|