@app-connect/core 1.7.24 → 1.7.25

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.
@@ -12,21 +12,11 @@ async function getPublicConnectorList() {
12
12
  }
13
13
  }
14
14
 
15
- async function getPrivateConnectorList() {
16
- try {
17
- const response = await axios.get(`https://appconnect.labs.ringcentral.com/public-api/connectors/internal?accountId=${process.env.RC_ACCOUNT_ID}`);
18
- return response.data;
19
- } catch (error) {
20
- logger.error('Error getting private connector list:', error);
21
- return null;
22
- }
23
- }
24
-
25
- async function getConnectorManifest({ connectorId, isPrivate = false }) {
15
+ async function getConnectorManifest({ rcAccountId, connectorId, isPrivate = false }) {
26
16
  try {
27
17
  let response = null;
28
18
  if (isPrivate) {
29
- response = await axios.get(`https://appconnect.labs.ringcentral.com/public-api/connectors/${connectorId}/manifest?access=internal&type=connector&accountId=${process.env.RC_ACCOUNT_ID}`);
19
+ response = await axios.get(`https://appconnect.labs.ringcentral.com/public-api/connectors/${connectorId}/manifest?access=internal&type=connector&accountId=${rcAccountId}`);
30
20
  }
31
21
  else {
32
22
  response = await axios.get(`https://appconnect.labs.ringcentral.com/public-api/connectors/${connectorId}/manifest`);
@@ -39,5 +29,4 @@ async function getConnectorManifest({ connectorId, isPrivate = false }) {
39
29
  }
40
30
 
41
31
  exports.getPublicConnectorList = getPublicConnectorList;
42
- exports.getPrivateConnectorList = getPrivateConnectorList;
43
32
  exports.getConnectorManifest = getConnectorManifest;
@@ -79,9 +79,10 @@ function buildHeaders({ config, operation, authHeader, context }) {
79
79
  async function performRequest({ config, opName, inputs, user, authHeader }) {
80
80
  const op = config.operations?.[opName];
81
81
  if (!op) return null;
82
+ const accessToken = user?.accessToken ?? inputs?.apiKey ?? '';
82
83
  const context = Object.assign({}, inputs, {
83
84
  user: user ? {
84
- accessToken: user.accessToken,
85
+ accessToken,
85
86
  id: user.id?.split('-')[0],
86
87
  hostname: user.hostname,
87
88
  timezoneName: user.timezoneName,
@@ -91,10 +92,10 @@ async function performRequest({ config, opName, inputs, user, authHeader }) {
91
92
  refreshToken: user.refreshToken,
92
93
  tokenExpiry: user.tokenExpiry,
93
94
  } : {
94
- accessToken: '',
95
+ accessToken,
95
96
  },
96
97
  authHeader,
97
- apiKey: user?.accessToken,
98
+ apiKey: accessToken,
98
99
  secretKey: config.secretKey,
99
100
  });
100
101
  const url = joinUrl(config.requestDefaults?.baseUrl, renderTemplateString(op.url, context));
@@ -61,7 +61,6 @@ The proxy connector makes integrations configurable through stored connector met
61
61
  Exports:
62
62
 
63
63
  - `getPublicConnectorList()`
64
- - `getPrivateConnectorList()`
65
64
  - `getConnectorManifest()`
66
65
 
67
66
  These functions are small fetch helpers and return `null` on failure after logging.
package/handlers/auth.js CHANGED
@@ -90,7 +90,7 @@ async function onApiKeyLogin({ platform, hostname, apiKey, proxyId, rcAccountId,
90
90
  let resolvedApiKey = apiKey;
91
91
  let managedFieldDefinitions = [];
92
92
  if (rcAccountId) {
93
- managedFieldDefinitions = await managedAuthCore.getManagedFieldDefinitions({ platform, connectorId, isPrivate });
93
+ managedFieldDefinitions = await managedAuthCore.getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
94
94
  const shouldFallbackToManualAuth = managedFieldDefinitions.length > 0
95
95
  && await managedAuthCore.hasManagedAuthLoginFailure({ rcAccountId, platform, rcExtensionId });
96
96
  const managedAuthResult = await managedAuthCore.resolveApiKeyLoginFields({
@@ -20,12 +20,12 @@ function isFilled(value) {
20
20
  return value !== undefined && value !== null && value !== '';
21
21
  }
22
22
 
23
- async function getApiKeyFieldDefinitions({ platform, connectorId, isPrivate = false }) {
23
+ async function getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
24
24
  if (!platform) {
25
25
  return [];
26
26
  }
27
27
  if (connectorId) {
28
- const manifest = await developerPortal.getConnectorManifest({ connectorId, isPrivate });
28
+ const manifest = await developerPortal.getConnectorManifest({ rcAccountId, connectorId, isPrivate });
29
29
  if (manifest?.platforms?.[platform]?.auth?.apiKey?.page?.content) {
30
30
  return manifest.platforms[platform].auth.apiKey.page.content;
31
31
  }
@@ -39,8 +39,8 @@ async function getApiKeyFieldDefinitions({ platform, connectorId, isPrivate = fa
39
39
  }
40
40
  }
41
41
 
42
- async function getManagedFieldDefinitions({ platform, connectorId, isPrivate = false }) {
43
- const fieldDefinitions = await getApiKeyFieldDefinitions({ platform, connectorId, isPrivate });
42
+ async function getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate = false }) {
43
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
44
44
  return fieldDefinitions.filter(field => field?.managed);
45
45
  }
46
46
 
@@ -247,7 +247,7 @@ function getStoredFieldValue({ value }) {
247
247
  }
248
248
 
249
249
  async function getManagedAuthAdminSettings({ platform, rcAccountId, connectorId, isPrivate = false }) {
250
- const fieldDefinitions = await getManagedFieldDefinitions({ platform, connectorId, isPrivate });
250
+ const fieldDefinitions = await getManagedFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
251
251
  const orgFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'account');
252
252
  const userFieldDefinitions = fieldDefinitions.filter(field => field.managedScope === 'user');
253
253
  const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
@@ -307,7 +307,7 @@ async function getManagedAuthAdminSettings({ platform, rcAccountId, connectorId,
307
307
  }
308
308
 
309
309
  async function getManagedAuthState({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false }) {
310
- const fieldDefinitions = await getApiKeyFieldDefinitions({ platform, connectorId, isPrivate });
310
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
311
311
  const managedFieldDefinitions = fieldDefinitions.filter(field => field?.managed);
312
312
  const orgValues = await getOrgManagedAuthValues({ rcAccountId, platform });
313
313
  const userValues = await getUserManagedAuthValues({ rcAccountId, platform, rcExtensionId });
@@ -367,7 +367,7 @@ async function getManagedAuthState({ platform, rcAccountId, rcExtensionId, conne
367
367
  }
368
368
 
369
369
  async function resolveApiKeyLoginFields({ platform, rcAccountId, rcExtensionId, connectorId, isPrivate = false, apiKey, additionalInfo = {}, preferSubmittedValuesForManagedFields = false }) {
370
- const fieldDefinitions = await getApiKeyFieldDefinitions({ platform, connectorId, isPrivate });
370
+ const fieldDefinitions = await getApiKeyFieldDefinitions({ rcAccountId, platform, connectorId, isPrivate });
371
371
  const resolvedAdditionalInfo = {
372
372
  ...(additionalInfo ?? {})
373
373
  };
package/index.js CHANGED
@@ -1178,13 +1178,13 @@ function createCoreRouter() {
1178
1178
  res.status(400).send(tracer ? tracer.wrapResponse('Missing platform name') : 'Missing platform name');
1179
1179
  return;
1180
1180
  }
1181
- if (!rcAccessToken) {
1182
- res.status(400).send(tracer ? tracer.wrapResponse('Missing RingCentral access token') : 'Missing RingCentral access token');
1183
- return;
1181
+ let rcAccountId = null;
1182
+ let rcExtensionId = null;
1183
+ if (rcAccessToken) {
1184
+ const rcUserTokenResult = await adminCore.validateRcUserToken({ rcAccessToken });
1185
+ rcAccountId = rcUserTokenResult.rcAccountId;
1186
+ rcExtensionId = rcUserTokenResult.rcExtensionId;
1184
1187
  }
1185
- const rcUserTokenResult = await adminCore.validateRcUserToken({ rcAccessToken });
1186
- const rcAccountId = rcUserTokenResult.rcAccountId;
1187
- const rcExtensionId = rcUserTokenResult.rcExtensionId;
1188
1188
  const { userInfo, returnMessage } = await authCore.onApiKeyLogin({
1189
1189
  platform,
1190
1190
  hostname,
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@app-connect/core",
3
- "version": "1.7.24",
4
- "description": "RingCentral App Connect Core",
5
- "main": "index.js",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
- },
10
- "keywords": [
11
- "RingCentral",
12
- "App Connect"
13
- ],
14
- "author": "RingCentral Labs",
15
- "license": "MIT",
16
- "peerDependencies": {
17
- "axios": "^1.12.2",
18
- "express": "^4.22.1",
19
- "moment": "^2.29.4",
20
- "moment-timezone": "^0.5.39",
21
- "pg": "^8.8.0",
22
- "sequelize": "^6.29.0"
23
- },
24
- "dependencies": {
25
- "@aws-sdk/client-dynamodb": "^3.751.0",
26
- "@aws-sdk/client-s3": "^3.947.0",
27
- "@aws-sdk/s3-request-presigner": "^3.947.0",
28
- "@modelcontextprotocol/sdk": "^1.26.0",
29
- "awesome-phonenumber": "^5.6.0",
30
- "body-parser": "^1.20.4",
31
- "body-parser-xml": "^2.0.5",
32
- "client-oauth2": "^4.3.3",
33
- "cors": "^2.8.5",
34
- "country-state-city": "^3.2.1",
35
- "dotenv": "^16.0.3",
36
- "dynamoose": "^4.0.3",
37
- "jsonwebtoken": "^9.0.0",
38
- "mixpanel": "^0.18.0",
39
- "shortid": "^2.2.17",
40
- "tz-lookup": "^6.1.25",
41
- "ua-parser-js": "^1.0.38"
42
- },
43
- "scripts": {
44
- "test": "jest",
45
- "test:watch": "jest --watch",
46
- "test:coverage": "jest --coverage",
47
- "test:ci": "jest --ci --coverage --watchAll=false"
48
- },
49
- "devDependencies": {
50
- "@eslint/js": "^9.22.0",
51
- "@octokit/rest": "^19.0.5",
52
- "axios": "^1.12.2",
53
- "eslint": "^9.22.0",
54
- "express": "^4.22.1",
55
- "globals": "^16.0.0",
56
- "jest": "^29.3.1",
57
- "moment": "^2.29.4",
58
- "moment-timezone": "^0.5.39",
59
- "nock": "^13.2.9",
60
- "pg": "^8.8.0",
61
- "sequelize": "^6.29.0",
62
- "sqlite3": "^5.1.2",
63
- "supertest": "^6.3.1"
64
- },
65
- "overrides": {
66
- "js-object-utilities": "2.2.1"
67
- },
68
- "bugs": {
69
- "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
- },
71
- "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
- }
1
+ {
2
+ "name": "@app-connect/core",
3
+ "version": "1.7.25",
4
+ "description": "RingCentral App Connect Core",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
+ },
10
+ "keywords": [
11
+ "RingCentral",
12
+ "App Connect"
13
+ ],
14
+ "author": "RingCentral Labs",
15
+ "license": "MIT",
16
+ "peerDependencies": {
17
+ "axios": "^1.12.2",
18
+ "express": "^4.22.1",
19
+ "moment": "^2.29.4",
20
+ "moment-timezone": "^0.5.39",
21
+ "pg": "^8.8.0",
22
+ "sequelize": "^6.29.0"
23
+ },
24
+ "dependencies": {
25
+ "@aws-sdk/client-dynamodb": "^3.751.0",
26
+ "@aws-sdk/client-s3": "^3.947.0",
27
+ "@aws-sdk/s3-request-presigner": "^3.947.0",
28
+ "@modelcontextprotocol/sdk": "^1.26.0",
29
+ "awesome-phonenumber": "^5.6.0",
30
+ "body-parser": "^1.20.4",
31
+ "body-parser-xml": "^2.0.5",
32
+ "client-oauth2": "^4.3.3",
33
+ "cors": "^2.8.5",
34
+ "country-state-city": "^3.2.1",
35
+ "dotenv": "^16.0.3",
36
+ "dynamoose": "^4.0.3",
37
+ "jsonwebtoken": "^9.0.0",
38
+ "mixpanel": "^0.18.0",
39
+ "shortid": "^2.2.17",
40
+ "tz-lookup": "^6.1.25",
41
+ "ua-parser-js": "^1.0.38"
42
+ },
43
+ "scripts": {
44
+ "test": "jest",
45
+ "test:watch": "jest --watch",
46
+ "test:coverage": "jest --coverage",
47
+ "test:ci": "jest --ci --coverage --watchAll=false"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.22.0",
51
+ "@octokit/rest": "^19.0.5",
52
+ "axios": "^1.12.2",
53
+ "eslint": "^9.22.0",
54
+ "express": "^4.22.1",
55
+ "globals": "^16.0.0",
56
+ "jest": "^29.3.1",
57
+ "moment": "^2.29.4",
58
+ "moment-timezone": "^0.5.39",
59
+ "nock": "^13.2.9",
60
+ "pg": "^8.8.0",
61
+ "sequelize": "^6.29.0",
62
+ "sqlite3": "^5.1.2",
63
+ "supertest": "^6.3.1"
64
+ },
65
+ "overrides": {
66
+ "js-object-utilities": "2.2.1"
67
+ },
68
+ "bugs": {
69
+ "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
+ },
71
+ "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
+ }
package/releaseNotes.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
+ "1.7.25": {
3
+ "global": [
4
+ {
5
+ "type": "Fix",
6
+ "description": "Click-to-dial render issue"
7
+ }
8
+ ]
9
+ },
2
10
  "1.7.24": {
3
11
  "global": [
4
12
  {
5
13
  "type": "New",
6
14
  "description": "Click-to-dial number matcher selection in User Setting -> General"
7
- },
15
+ },
8
16
  {
9
17
  "type": "Fix",
10
18
  "description": "Error report upload"
@@ -88,6 +88,39 @@ describe('proxy engine utilities', () => {
88
88
  // Basic base64('token-123')
89
89
  expect(args.headers.Authorization).toMatch(/^Basic /);
90
90
  });
91
+
92
+ test('performRequest uses submitted apiKey during first login before user is saved', async () => {
93
+ axios.mockResolvedValue({ data: { ok: true } });
94
+ const config = {
95
+ auth: {
96
+ type: 'apiKey',
97
+ scheme: 'Basic',
98
+ credentialTemplate: '{{apiKey}}',
99
+ encode: 'base64',
100
+ headerName: 'Authorization'
101
+ },
102
+ requestDefaults: {
103
+ baseUrl: 'https://api.example.com'
104
+ },
105
+ operations: {
106
+ getUserInfo: {
107
+ method: 'GET',
108
+ url: '/authentication'
109
+ }
110
+ }
111
+ };
112
+
113
+ await performRequest({
114
+ config,
115
+ opName: 'getUserInfo',
116
+ inputs: { apiKey: 'login-key' },
117
+ user: {},
118
+ authHeader: undefined
119
+ });
120
+
121
+ const args = axios.mock.calls[0][0];
122
+ expect(args.headers.Authorization).toBe(`Basic ${Buffer.from('login-key').toString('base64')}`);
123
+ });
91
124
  });
92
125
 
93
126
 
@@ -24,6 +24,7 @@ const { RingCentral } = require('../../lib/ringcentral');
24
24
  const adminCore = require('../../handlers/admin');
25
25
  const { AccountDataModel } = require('../../models/accountDataModel');
26
26
  const { encode } = require('../../lib/encode');
27
+ const { getHashValue } = require('../../lib/util');
27
28
 
28
29
  describe('Auth Handler', () => {
29
30
  const originalEnv = process.env;
@@ -964,6 +965,8 @@ describe('Auth Handler', () => {
964
965
 
965
966
  test('should handle successful RingCentral OAuth callback', async () => {
966
967
  // Arrange
968
+ process.env.HASH_KEY = 'test-hash-key';
969
+ const rcAccountId = 'rc-account-id';
967
970
  const mockGenerateToken = jest.fn().mockResolvedValue({
968
971
  access_token: 'rc-access-token',
969
972
  refresh_token: 'rc-refresh-token',
@@ -977,7 +980,7 @@ describe('Auth Handler', () => {
977
980
  // Act
978
981
  await authHandler.onRingcentralOAuthCallback({
979
982
  code: 'rc-auth-code',
980
- rcAccountId: 'hashed-rc-account-id'
983
+ rcAccountId
981
984
  });
982
985
 
983
986
  // Assert
@@ -989,7 +992,7 @@ describe('Auth Handler', () => {
989
992
  });
990
993
  expect(mockGenerateToken).toHaveBeenCalledWith({ code: 'rc-auth-code' });
991
994
  expect(adminCore.updateAdminRcTokens).toHaveBeenCalledWith({
992
- hashedRcAccountId: 'hashed-rc-account-id',
995
+ hashedRcAccountId: getHashValue(rcAccountId, 'test-hash-key'),
993
996
  adminAccessToken: 'rc-access-token',
994
997
  adminRefreshToken: 'rc-refresh-token',
995
998
  adminTokenExpiry: expect.any(Number)
@@ -180,7 +180,7 @@ describe('Managed Auth Handler', () => {
180
180
  rcAccountId: 'acc-3'
181
181
  });
182
182
 
183
- expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({ connectorId: 'connector-123', isPrivate: false });
183
+ expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({ rcAccountId: 'acc-3', connectorId: 'connector-123', isPrivate: false });
184
184
  expect(state.hasManagedAuth).toBe(true);
185
185
  expect(state.allRequiredFieldsSatisfied).toBe(true);
186
186
  expect(state.visibleFieldConsts).toEqual([]);
@@ -41,7 +41,8 @@ describe('MCP Tool: checkAuthStatus', () => {
41
41
 
42
42
  expect(LlmSessionModel.upsert).toHaveBeenCalledWith({
43
43
  id: 'rc-ext-1',
44
- jwtToken: 'jwt-token'
44
+ jwtToken: 'jwt-token',
45
+ expiry: expect.any(Date)
45
46
  });
46
47
  expect(result).toEqual({
47
48
  data: {
@@ -32,17 +32,6 @@ describe('Managed Auth Routes', () => {
32
32
  });
33
33
 
34
34
  describe('GET /apiKeyManagedAuthState', () => {
35
- test('should require rcAccessToken', async () => {
36
- const response = await request(app)
37
- .get('/apiKeyManagedAuthState')
38
- .query({ platform: 'testCRM' });
39
-
40
- expect(response.status).toBe(400);
41
- expect(response.text).toContain('Missing RingCentral access token');
42
- expect(adminCore.validateRcUserToken).not.toHaveBeenCalled();
43
- expect(managedAuthCore.getManagedAuthState).not.toHaveBeenCalled();
44
- });
45
-
46
35
  test('should validate rcAccessToken and use validated identity', async () => {
47
36
  adminCore.validateRcUserToken.mockResolvedValue({
48
37
  rcAccountId: 'validated-account-id',
@@ -75,20 +64,6 @@ describe('Managed Auth Routes', () => {
75
64
  });
76
65
 
77
66
  describe('POST /apiKeyLogin', () => {
78
- test('should require rcAccessToken', async () => {
79
- const response = await request(app)
80
- .post('/apiKeyLogin')
81
- .send({
82
- platform: 'testCRM',
83
- apiKey: 'api-key',
84
- hostname: 'test.example.com',
85
- });
86
-
87
- expect(response.status).toBe(400);
88
- expect(response.text).toContain('Missing RingCentral access token');
89
- expect(adminCore.validateRcUserToken).not.toHaveBeenCalled();
90
- expect(authCore.onApiKeyLogin).not.toHaveBeenCalled();
91
- });
92
67
 
93
68
  test('should validate rcAccessToken and ignore spoofed rc ids in body', async () => {
94
69
  adminCore.validateRcUserToken.mockResolvedValue({