@itentialopensource/adapter-utils 5.10.28 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -9,6 +9,7 @@ module.exports = {
9
9
  'json'
10
10
  ],
11
11
  parserOptions: {
12
+ ecmaVersion: 2020,
12
13
  sourceType: 'module'
13
14
  },
14
15
  rules: {
@@ -36,6 +36,18 @@ const validator = require('validator');
36
36
 
37
37
  const ThrottleCl = require(path.join(__dirname, '/throttle.js'));
38
38
 
39
+ // Load properties schema to extract valid enum values
40
+ const propertiesSchema = require(path.join(__dirname, '../schemas/propertiesSchema.json'));
41
+
42
+ // Token placement constants
43
+ const TokenPlacement = Object.freeze({
44
+ HEADER: 'HEADER',
45
+ BODY: 'BODY',
46
+ XML2JSON: 'XML2JSON',
47
+ QUERY: 'QUERY',
48
+ PATH: 'PATH'
49
+ });
50
+
39
51
  const allowFailover = 'AD.300';
40
52
  const noFailover = 'AD.500';
41
53
  let transUtilInst = null;
@@ -113,6 +125,32 @@ let refTokenReq = null;
113
125
  let refTokenTimeout = -1;
114
126
  let runRefreshToken = false;
115
127
  let addSensitiveItems = [];
128
+ let authRequestDatatype = null;
129
+ let authResponseDatatype = null;
130
+ let tokenResponsePlacement = null;
131
+ // Extract valid values from schema (filter out empty string which is just for default)
132
+ const VALID_AUTH_REQUEST_DATATYPES = propertiesSchema.definitions.authentication.properties.auth_request_datatype.enum.filter((v) => v !== '');
133
+ const VALID_AUTH_RESPONSE_DATATYPES = propertiesSchema.definitions.authentication.properties.auth_response_datatype.enum.filter((v) => v !== '');
134
+ const VALID_TOKEN_RESPONSE_PLACEMENTS = propertiesSchema.definitions.authentication.properties.token_response_placement.enum.filter((v) => v !== '');
135
+
136
+ /**
137
+ * Validates an authentication property value against a list of valid values
138
+ * @param {*} value - The value to validate
139
+ * @param {Array} validValues - Array of valid values
140
+ * @param {string} propertyName - Name of the property for error messaging
141
+ * @returns {string|undefined} - Returns the validated value or undefined if invalid/empty
142
+ */
143
+ function validateAuthProperty(value, validValues, propertyName) {
144
+ if (typeof value !== 'string' || value === '') {
145
+ return;
146
+ }
147
+
148
+ if (!validValues.includes(value)) {
149
+ throw new Error(`Invalid ${propertyName}: '${value}'. Must be one of: ${validValues.join(', ')}`);
150
+ }
151
+
152
+ return value;
153
+ }
116
154
 
117
155
  // Other global variables
118
156
  let id = null;
@@ -1100,6 +1138,20 @@ function findExpireInResult(result) {
1100
1138
  return expire;
1101
1139
  }
1102
1140
 
1141
+ /*
1142
+ * INTERNAL FUNCTION: gets the effective token placement
1143
+ * (adapter property overrides schema)
1144
+ */
1145
+ function getTokenPlacement(tokenSchema, tokenProperty) {
1146
+ if (tokenResponsePlacement) {
1147
+ return tokenResponsePlacement.toUpperCase();
1148
+ }
1149
+ if (tokenSchema?.responseSchema?.properties?.[tokenProperty]?.placement) {
1150
+ return tokenSchema.responseSchema.properties[tokenProperty].placement.toUpperCase();
1151
+ }
1152
+ return null;
1153
+ }
1154
+
1103
1155
  /*
1104
1156
  * INTERNAL FUNCTION: makes the request and processes the response
1105
1157
  * for the request to get the token
@@ -1107,6 +1159,7 @@ function findExpireInResult(result) {
1107
1159
  async function getToken(reqPath, options, tokenSchema, bodyString, callProperties, callback) {
1108
1160
  const origin = `${id}-connectorRest-getToken`;
1109
1161
  log.trace(origin);
1162
+ const responseDatatype = authResponseDatatype || (tokenSchema && tokenSchema.responseDatatype);
1110
1163
 
1111
1164
  const p = new Promise((resolve, reject) => {
1112
1165
  try {
@@ -1225,8 +1278,7 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1225
1278
  };
1226
1279
 
1227
1280
  // process primary token from header
1228
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1229
- && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'HEADER') {
1281
+ if (getTokenPlacement(tokenSchema, 'token') === TokenPlacement.HEADER) {
1230
1282
  if (!tokenSchema.responseSchema.properties.token.external_name) {
1231
1283
  const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Primary Token', ['Primary Token', result.code], null, null, null);
1232
1284
  log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
@@ -1306,8 +1358,7 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1306
1358
  }
1307
1359
 
1308
1360
  // process second token from header
1309
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1310
- && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'HEADER') {
1361
+ if (getTokenPlacement(tokenSchema, 'tokenp2') === TokenPlacement.HEADER) {
1311
1362
  if (!tokenSchema.responseSchema.properties.tokenp2.external_name) {
1312
1363
  const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Secondary Token', ['Secondary Token', result.code], null, null, null);
1313
1364
  log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
@@ -1388,11 +1439,10 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1388
1439
 
1389
1440
  // process the body
1390
1441
  // if response is just a string
1391
- if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'PLAIN') {
1442
+ if (responseDatatype && responseDatatype.toUpperCase() === 'PLAIN') {
1392
1443
  log.debug('Attempting to get tokens from text body repsonse');
1393
1444
 
1394
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1395
- && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1445
+ if (getTokenPlacement(tokenSchema, 'token') === TokenPlacement.BODY) {
1396
1446
  currResult.token = result.response;
1397
1447
 
1398
1448
  // if we got a stringified string - we can remove the double quotes wrapping it
@@ -1400,8 +1450,7 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1400
1450
  currResult.token = currResult.token.substring(1, currResult.token.length - 1);
1401
1451
  }
1402
1452
  }
1403
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1404
- && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1453
+ if (getTokenPlacement(tokenSchema, 'tokenp2') === TokenPlacement.BODY) {
1405
1454
  currResult.tokenp2 = result.response;
1406
1455
 
1407
1456
  // if we got a stringified string - we can remove the double quotes wrapping it
@@ -1413,15 +1462,14 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1413
1462
  // return the string as there is no other processing needed
1414
1463
  return resolve(currResult);
1415
1464
  }
1416
- if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'XML') {
1465
+
1466
+ if (responseDatatype && responseDatatype.toUpperCase() === 'XML') {
1417
1467
  log.debug('Attempting to get tokens from XML body repsonse');
1418
1468
 
1419
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1420
- && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1469
+ if (getTokenPlacement(tokenSchema, 'token') === TokenPlacement.BODY) {
1421
1470
  currResult.token = result.response;
1422
1471
  }
1423
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1424
- && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1472
+ if (getTokenPlacement(tokenSchema, 'tokenp2') === TokenPlacement.BODY) {
1425
1473
  currResult.tokenp2 = result.response;
1426
1474
  }
1427
1475
 
@@ -1432,7 +1480,7 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1432
1480
  // if response can be put into a JSON object
1433
1481
  let tempResult = null;
1434
1482
  if (result.response) {
1435
- if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'XML2JSON') {
1483
+ if (responseDatatype && responseDatatype.toUpperCase() === TokenPlacement.XML2JSON) {
1436
1484
  log.debug('Attempting to get tokens from XML2JSON repsonse');
1437
1485
 
1438
1486
  try {
@@ -1446,17 +1494,15 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1446
1494
  });
1447
1495
  } catch (ex) {
1448
1496
  log.warn(`${origin}: Unable to get json from xml ${ex}`);
1449
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1450
- && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1497
+ if (getTokenPlacement(tokenSchema, 'token') === TokenPlacement.BODY) {
1451
1498
  currResult.token = result.response;
1452
1499
  }
1453
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1454
- && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1500
+ if (getTokenPlacement(tokenSchema, 'tokenp2') === TokenPlacement.BODY) {
1455
1501
  currResult.tokenp2 = result.response;
1456
1502
  }
1457
1503
  }
1458
1504
  }
1459
- if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'URLENCODE') {
1505
+ if (responseDatatype && responseDatatype.toUpperCase() === 'URLENCODE') {
1460
1506
  log.debug('Attempting to get tokens from URLENCODE repsonse');
1461
1507
  tempResult = querystring.parse(result.response.trim());
1462
1508
  } else {
@@ -1509,20 +1555,16 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
1509
1555
  translated = translated[0];
1510
1556
  }
1511
1557
 
1512
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1513
- && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1558
+ if (getTokenPlacement(tokenSchema, 'token') === TokenPlacement.BODY) {
1514
1559
  currResult.token = translated.token;
1515
1560
  }
1516
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1517
- && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1561
+ if (getTokenPlacement(tokenSchema, 'tokenp2') === TokenPlacement.BODY) {
1518
1562
  currResult.tokenp2 = translated.tokenp2;
1519
1563
  }
1520
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.expires
1521
- && tokenSchema.responseSchema.properties.expires.placement && tokenSchema.responseSchema.properties.expires.placement.toUpperCase() === 'BODY') {
1564
+ if (getTokenPlacement(tokenSchema, 'expires') === TokenPlacement.BODY) {
1522
1565
  currResult.expires = translated.expires;
1523
1566
  }
1524
- if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.refreshToken
1525
- && tokenSchema.responseSchema.properties.refreshToken.placement && tokenSchema.responseSchema.properties.refreshToken.placement.toUpperCase() === 'BODY') {
1567
+ if (getTokenPlacement(tokenSchema, 'refreshToken') === TokenPlacement.BODY) {
1526
1568
  currResult.refreshToken = translated.refreshToken;
1527
1569
  }
1528
1570
  // return the token that we find in the translated object
@@ -1669,13 +1711,15 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
1669
1711
 
1670
1712
  // set the Content Type headers based on the type of request data for the call
1671
1713
  if (thisAHdata['Content-Type'] === undefined || thisAHdata['Content-Type'] === null) {
1672
- if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'PLAIN') {
1714
+ // Check adapter property override first, then fall back to tokenSchema
1715
+ const requestDatatype = authRequestDatatype || (tokenSchema && tokenSchema.requestDatatype);
1716
+ if (requestDatatype && requestDatatype.toUpperCase() === 'PLAIN') {
1673
1717
  // add the Plain headers if they were not set already
1674
1718
  thisAHdata['Content-Type'] = 'text/plain';
1675
- } else if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'XML') {
1719
+ } else if (requestDatatype && requestDatatype.toUpperCase() === 'XML') {
1676
1720
  // add the XML headers if they were not set already
1677
1721
  thisAHdata['Content-Type'] = 'application/xml';
1678
- } else if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'URLENCODE') {
1722
+ } else if (requestDatatype && requestDatatype.toUpperCase() === 'URLENCODE') {
1679
1723
  // add the URLENCODE headers if they were not set already
1680
1724
  thisAHdata['Content-Type'] = 'application/x-www-form-urlencoded';
1681
1725
  } else {
@@ -1685,13 +1729,14 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
1685
1729
  }
1686
1730
  // set the Accept headers based on the type of response data for the call
1687
1731
  if (thisAHdata.Accept === undefined || thisAHdata.Accept === null) {
1688
- if (tokenSchema && tokenSchema.responseDatatype && tokenSchema.responseDatatype.toUpperCase() === 'PLAIN') {
1732
+ const responseDatatype = authResponseDatatype || (tokenSchema && tokenSchema.responseDatatype);
1733
+ if (responseDatatype && responseDatatype.toUpperCase() === 'PLAIN') {
1689
1734
  // add the Plain headers if they were not set already
1690
1735
  thisAHdata.Accept = 'text/plain';
1691
- } else if (tokenSchema && tokenSchema.responseDatatype && (tokenSchema.responseDatatype.toUpperCase() === 'XML' || tokenSchema.responseDatatype.toUpperCase() === 'XML2JSON')) {
1736
+ } else if (responseDatatype && (responseDatatype.toUpperCase() === 'XML' || responseDatatype.toUpperCase() === TokenPlacement.XML2JSON)) {
1692
1737
  // add the XML headers if they were not set already
1693
1738
  thisAHdata.Accept = 'application/xml';
1694
- } else if (tokenSchema && tokenSchema.responseDatatype && tokenSchema.responseDatatype.toUpperCase() === 'URLENCODE') {
1739
+ } else if (responseDatatype && responseDatatype.toUpperCase() === 'URLENCODE') {
1695
1740
  // add the URLENCODE headers if they were not set already
1696
1741
  thisAHdata.Accept = 'application/x-www-form-urlencoded';
1697
1742
  } else {
@@ -2159,11 +2204,13 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
2159
2204
  bodyString = tokenEntity;
2160
2205
 
2161
2206
  // if it is JSON or URLENCODE need to put body into right format
2162
- if (!tokenSchema.requestDatatype || tokenSchema.requestDatatype.toUpperCase() === 'JSON' || tokenSchema.requestDatatype.toUpperCase() === 'FORM') {
2207
+ // Check adapter property override first, then fall back to tokenSchema
2208
+ const requestDatatype = authRequestDatatype || (tokenSchema && tokenSchema.requestDatatype);
2209
+ if (!requestDatatype || requestDatatype.toUpperCase() === 'JSON' || requestDatatype.toUpperCase() === 'FORM') {
2163
2210
  bodyString = JSON.stringify(tokenEntity);
2164
- } else if (tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'URLENCODE') {
2211
+ } else if (requestDatatype && requestDatatype.toUpperCase() === 'URLENCODE') {
2165
2212
  bodyString = querystring.stringify(tokenEntity);
2166
- } else if (tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'URLQUERY') {
2213
+ } else if (requestDatatype && requestDatatype.toUpperCase() === 'URLQUERY') {
2167
2214
  // if the datatype is URLQUERY need to put into the query on the request
2168
2215
  if (authQueryEncode != null) {
2169
2216
  if (authQueryEncode === true) {
@@ -4275,6 +4322,36 @@ class ConnectorRest {
4275
4322
  if (props.authentication.sensitive) {
4276
4323
  addSensitiveItems = props.authentication.sensitive;
4277
4324
  }
4325
+
4326
+ // set the auth request datatype override (optional - default is null)
4327
+ const validatedAuthRequestDatatype = validateAuthProperty(
4328
+ props.authentication.auth_request_datatype,
4329
+ VALID_AUTH_REQUEST_DATATYPES,
4330
+ 'auth_request_datatype'
4331
+ );
4332
+ if (validatedAuthRequestDatatype !== undefined) {
4333
+ authRequestDatatype = validatedAuthRequestDatatype;
4334
+ }
4335
+
4336
+ // set the auth response datatype override (optional - default is null)
4337
+ const validatedAuthResponseDatatype = validateAuthProperty(
4338
+ props.authentication.auth_response_datatype,
4339
+ VALID_AUTH_RESPONSE_DATATYPES,
4340
+ 'auth_response_datatype'
4341
+ );
4342
+ if (validatedAuthResponseDatatype !== undefined) {
4343
+ authResponseDatatype = validatedAuthResponseDatatype;
4344
+ }
4345
+
4346
+ // set the token response placement override (optional - default is null)
4347
+ const validatedTokenResponsePlacement = validateAuthProperty(
4348
+ props.authentication.token_response_placement,
4349
+ VALID_TOKEN_RESPONSE_PLACEMENTS,
4350
+ 'token_response_placement'
4351
+ );
4352
+ if (validatedTokenResponsePlacement !== undefined) {
4353
+ tokenResponsePlacement = validatedTokenResponsePlacement;
4354
+ }
4278
4355
  }
4279
4356
 
4280
4357
  // set the stub mode (optional - default is false)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itentialopensource/adapter-utils",
3
- "version": "5.10.28",
3
+ "version": "5.11.0",
4
4
  "description": "Itential Adapter Utility Libraries",
5
5
  "scripts": {
6
6
  "postinstall": "node utils/setup.js",
@@ -272,6 +272,44 @@
272
272
  "description": "This property turns on logging of Authentication Information and should only be true when debugging authentication and connectivity",
273
273
  "default": false
274
274
  },
275
+ "auth_request_datatype": {
276
+ "type": "string",
277
+ "description": "Override the request data type for token authentication requests. When set, this overrides the schema's requestDatatype",
278
+ "default": "",
279
+ "enum": [
280
+ "",
281
+ "JSON",
282
+ "JSON2XML",
283
+ "PLAIN",
284
+ "XML",
285
+ "URLENCODE",
286
+ "URLQUERY",
287
+ "FORM"
288
+ ]
289
+ },
290
+ "auth_response_datatype": {
291
+ "type": "string",
292
+ "description": "Override the response data type for token authentication requests. When set, this overrides the schema's responseDatatype",
293
+ "default": "",
294
+ "enum": [
295
+ "",
296
+ "JSON",
297
+ "XML2JSON",
298
+ "PLAIN",
299
+ "XML",
300
+ "URLENCODE"
301
+ ]
302
+ },
303
+ "token_response_placement": {
304
+ "type": "string",
305
+ "description": "Override where to extract the token from the authentication response (HEADER or BODY). When set, this overrides the schema's token placement setting",
306
+ "default": "",
307
+ "enum": [
308
+ "",
309
+ "HEADER",
310
+ "BODY"
311
+ ]
312
+ },
275
313
  "client_id": {
276
314
  "type": "string",
277
315
  "description": "The client id for OAuth requests - can also use username depending on schema",