@rockcarver/frodo-lib 0.16.1 → 0.16.2-1

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.
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import _ from 'lodash';
4
- import { convertBase64TextToArray, getTypedFilename, saveJsonToFile, getRealmString, convertTextArrayToBase64, convertTextArrayToBase64Url, findFilesByName } from './utils/ExportImportUtils';
4
+ import { convertBase64TextToArray, getTypedFilename, convertTextArrayToBase64, convertTextArrayToBase64Url, findFilesByName } from './utils/ExportImportUtils';
5
5
  import { getRealmManagedUser, replaceAll } from './utils/OpsUtils';
6
6
  import storage from '../storage/SessionStorage';
7
7
  import { getNode, putNode, deleteNode, getNodeTypes, getNodesByType } from '../api/NodeApi';
@@ -10,8 +10,7 @@ import { getTrees, getTree, putTree, deleteTree } from '../api/TreeApi';
10
10
  import { getEmailTemplate, putEmailTemplate } from '../api/EmailTemplateApi';
11
11
  import { getScript } from '../api/ScriptApi';
12
12
  import * as global from '../storage/StaticStorage';
13
- import { printMessage, createProgressIndicator, updateProgressIndicator, stopProgressIndicator, createTable, debug } from './utils/Console';
14
- import wordwrap from './utils/Wordwrap';
13
+ import { printMessage, createProgressIndicator, updateProgressIndicator, stopProgressIndicator, debug } from './utils/Console';
15
14
  import { getProviderByLocationAndId, getProviders, getProviderMetadata, createProvider, findProviders, updateProvider } from '../api/Saml2Api';
16
15
  import { createCircleOfTrust, getCirclesOfTrust, updateCircleOfTrust } from '../api/CirclesOfTrustApi';
17
16
  import { decode, encode, encodeBase64Url, isBase64Encoded } from '../api/utils/Base64';
@@ -146,8 +145,7 @@ async function getSaml2NodeDependencies(nodeObject, allProviders, allCirclesOfTr
146
145
 
147
146
  export async function exportJourney(treeId, options = {
148
147
  useStringArrays: true,
149
- deps: true,
150
- verbose: false
148
+ deps: true
151
149
  }) {
152
150
  const exportData = createSingleTreeExportTemplate();
153
151
 
@@ -155,9 +153,9 @@ export async function exportJourney(treeId, options = {
155
153
  const treeObject = await getTree(treeId);
156
154
  const {
157
155
  useStringArrays,
158
- deps,
159
- verbose
156
+ deps
160
157
  } = options;
158
+ const verbose = storage.session.getDebug();
161
159
  if (verbose) printMessage(`\n- ${treeObject._id}\n`, 'info', false); // Process tree
162
160
 
163
161
  if (verbose) printMessage(' - Flow');
@@ -441,7 +439,7 @@ export async function exportJourney(treeId, options = {
441
439
  }
442
440
  /**
443
441
  * Get all the journeys/trees without all their nodes and dependencies.
444
- * @returns {Promise<unknown[]>} a promise that resolves to an array of journey objects
442
+ * @returns {Promise<TreeSkeleton[]>} a promise that resolves to an array of journey objects
445
443
  */
446
444
 
447
445
  export async function getJourneys() {
@@ -458,94 +456,7 @@ export async function getJourneys() {
458
456
  return journeys;
459
457
  }
460
458
  /**
461
- * Export journey by id/name to file
462
- * @param {string} journeyId journey id/name
463
- * @param {string} file optional export file name
464
- * @param {TreeExportOptions} options export options
465
- */
466
-
467
- export async function exportJourneyToFile(journeyId, file, options) {
468
- const {
469
- verbose
470
- } = options;
471
- let fileName = file;
472
-
473
- if (!fileName) {
474
- fileName = getTypedFilename(journeyId, 'journey');
475
- }
476
-
477
- if (!verbose) createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');
478
-
479
- try {
480
- const fileData = await exportJourney(journeyId, options);
481
- if (verbose) createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');
482
- saveJsonToFile(fileData, fileName);
483
- stopProgressIndicator(`Exported ${journeyId['brightCyan']} to ${fileName['brightCyan']}.`, 'success');
484
- } catch (error) {
485
- if (verbose) createProgressIndicator(undefined, `${journeyId}`, 'indeterminate');
486
- stopProgressIndicator(`Error exporting journey ${journeyId}: ${error}`, 'fail');
487
- }
488
- }
489
- /**
490
- * Export all journeys to file
491
- * @param {string} file optional export file name
492
- * @param {TreeExportOptions} options export options
493
- */
494
-
495
- export async function exportJourneysToFile(file, options = {
496
- deps: false,
497
- useStringArrays: false,
498
- verbose: false
499
- }) {
500
- let fileName = file;
501
-
502
- if (!fileName) {
503
- fileName = getTypedFilename(`all${getRealmString()}Journeys`, 'journeys');
504
- }
505
-
506
- const trees = (await getTrees()).result;
507
- const fileData = createMultiTreeExportTemplate();
508
- createProgressIndicator(trees.length, 'Exporting journeys...');
509
-
510
- for (const tree of trees) {
511
- updateProgressIndicator(`${tree._id}`);
512
-
513
- try {
514
- const exportData = await exportJourney(tree._id, options);
515
- delete exportData.meta;
516
- fileData.trees[tree._id] = exportData;
517
- } catch (error) {
518
- printMessage(`Error exporting journey ${tree._id}: ${error}`, 'error');
519
- }
520
- }
521
-
522
- saveJsonToFile(fileData, fileName);
523
- stopProgressIndicator(`Exported to ${fileName}`);
524
- }
525
- /**
526
- * Export all journeys to separate files
527
- * @param {TreeExportOptions} options export options
528
- */
529
-
530
- export async function exportJourneysToFiles(options) {
531
- const trees = (await getTrees()).result;
532
- createProgressIndicator(trees.length, 'Exporting journeys...');
533
-
534
- for (const tree of trees) {
535
- updateProgressIndicator(`${tree._id}`);
536
- const fileName = getTypedFilename(`${tree._id}`, 'journey');
537
-
538
- try {
539
- const exportData = await exportJourney(tree._id, options);
540
- saveJsonToFile(exportData, fileName);
541
- } catch (error) {// do we need to report status here?
542
- }
543
- }
544
-
545
- stopProgressIndicator('Done');
546
- }
547
- /**
548
- * Helper to import a tree with all dependencies from an import data object (typically read from a file)
459
+ * Helper to import a tree with all dependencies from a `SingleTreeExportInterface` object (typically read from a file)
549
460
  * @param {SingleTreeExportInterface} treeObject tree object containing tree and all its dependencies
550
461
  * @param {TreeImportOptions} options import options
551
462
  */
@@ -553,9 +464,9 @@ export async function exportJourneysToFiles(options) {
553
464
  export async function importJourney(treeObject, options) {
554
465
  const {
555
466
  reUuid,
556
- deps,
557
- verbose
467
+ deps
558
468
  } = options;
469
+ const verbose = storage.session.getDebug();
559
470
  if (verbose) printMessage(`\n- ${treeObject.tree._id}\n`, 'info', false);
560
471
  let newUuid = '';
561
472
  const uuidMap = {};
@@ -930,7 +841,7 @@ export async function importJourney(treeObject, options) {
930
841
  * @param {int} index Depth of recursion
931
842
  */
932
843
 
933
- async function resolveDependencies(installedJorneys, journeyMap, unresolvedJourneys, resolvedJourneys, index = -1) {
844
+ export async function resolveDependencies(installedJorneys, journeyMap, unresolvedJourneys, resolvedJourneys, index = -1) {
934
845
  let before = -1;
935
846
  let after = index;
936
847
 
@@ -984,135 +895,13 @@ async function resolveDependencies(installedJorneys, journeyMap, unresolvedJourn
984
895
  resolveDependencies(installedJorneys, journeyMap, unresolvedJourneys, resolvedJourneys, after);
985
896
  }
986
897
  }
987
- /**
988
- * Import a journey from file
989
- * @param {string} journeyId journey id/name
990
- * @param {string} file import file name
991
- * @param {TreeImportOptions} options import options
992
- */
993
-
994
-
995
- export async function importJourneyFromFile(journeyId, file, options) {
996
- const {
997
- verbose
998
- } = options;
999
- fs.readFile(file, 'utf8', async (err, data) => {
1000
- if (err) throw err;
1001
- let journeyData = JSON.parse(data); // check if this is a file with multiple trees and get journey by id
1002
-
1003
- if (journeyData.trees && journeyData.trees[journeyId]) {
1004
- journeyData = journeyData.trees[journeyId];
1005
- } else if (journeyData.trees) {
1006
- journeyData = null;
1007
- } // if a journeyId was specified, only import the matching journey
1008
-
1009
-
1010
- if (journeyData && journeyId === journeyData.tree._id) {
1011
- // attempt dependency resolution for single tree import
1012
- const installedJourneys = (await getTrees()).result.map(x => x._id);
1013
- const unresolvedJourneys = {};
1014
- const resolvedJourneys = [];
1015
- createProgressIndicator(undefined, 'Resolving dependencies', 'indeterminate');
1016
- await resolveDependencies(installedJourneys, {
1017
- [journeyId]: journeyData
1018
- }, unresolvedJourneys, resolvedJourneys);
1019
-
1020
- if (Object.keys(unresolvedJourneys).length === 0) {
1021
- stopProgressIndicator(`Resolved all dependencies.`, 'success');
1022
- if (!verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1023
- importJourney(journeyData, options).then(() => {
1024
- if (verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1025
- stopProgressIndicator(`Imported ${journeyId}.`, 'success');
1026
- }).catch(importError => {
1027
- if (verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1028
- stopProgressIndicator(`${importError}`, 'fail');
1029
- });
1030
- } else {
1031
- stopProgressIndicator(`Unresolved dependencies:`, 'fail');
1032
-
1033
- for (const journey of Object.keys(unresolvedJourneys)) {
1034
- printMessage(` ${journey} requires ${unresolvedJourneys[journey]}`, 'error');
1035
- }
1036
- } // end dependency resolution for single tree import
1037
-
1038
- } else {
1039
- createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1040
- stopProgressIndicator(`${journeyId} not found!`, 'fail');
1041
- }
1042
- });
1043
- }
1044
- /**
1045
- * Import first journey from file
1046
- * @param {string} file import file name
1047
- * @param {TreeImportOptions} options import options
1048
- */
1049
-
1050
- export async function importFirstJourneyFromFile(file, options) {
1051
- const {
1052
- verbose
1053
- } = options;
1054
- fs.readFile(file, 'utf8', async (err, data) => {
1055
- if (err) throw err;
1056
-
1057
- let journeyData = _.cloneDeep(JSON.parse(data));
1058
-
1059
- let journeyId = null; // single tree
1060
-
1061
- if (journeyData.tree) {
1062
- journeyId = _.cloneDeep(journeyData.tree._id);
1063
- } // multiple trees, so get the first tree
1064
- else if (journeyData.trees) {
1065
- for (const treeId in journeyData.trees) {
1066
- if (Object.hasOwnProperty.call(journeyData.trees, treeId)) {
1067
- journeyId = treeId;
1068
- journeyData = journeyData.trees[treeId];
1069
- break;
1070
- }
1071
- }
1072
- } // if a journeyId was specified, only import the matching journey
1073
-
1074
-
1075
- if (journeyData && journeyId) {
1076
- // attempt dependency resolution for single tree import
1077
- const installedJourneys = (await getTrees()).result.map(x => x._id);
1078
- const unresolvedJourneys = {};
1079
- const resolvedJourneys = [];
1080
- createProgressIndicator(undefined, 'Resolving dependencies', 'indeterminate');
1081
- await resolveDependencies(installedJourneys, {
1082
- [journeyId]: journeyData
1083
- }, unresolvedJourneys, resolvedJourneys);
1084
-
1085
- if (Object.keys(unresolvedJourneys).length === 0) {
1086
- stopProgressIndicator(`Resolved all dependencies.`, 'success');
1087
- if (!verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1088
- importJourney(journeyData, options).then(() => {
1089
- if (verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1090
- stopProgressIndicator(`Imported ${journeyId}.`, 'success');
1091
- }).catch(importError => {
1092
- if (verbose) createProgressIndicator(undefined, `Importing ${journeyId}...`, 'indeterminate');
1093
- stopProgressIndicator(`${importError}`, 'fail');
1094
- });
1095
- } else {
1096
- stopProgressIndicator(`Unresolved dependencies:`, 'fail');
1097
-
1098
- for (const journey of Object.keys(unresolvedJourneys)) {
1099
- printMessage(` ${journey} requires ${unresolvedJourneys[journey]}`, 'error');
1100
- }
1101
- }
1102
- } else {
1103
- createProgressIndicator(undefined, `Importing...`, 'indeterminate');
1104
- stopProgressIndicator(`No journeys found!`, 'fail');
1105
- } // end dependency resolution for single tree import
1106
-
1107
- });
1108
- }
1109
898
  /**
1110
899
  * Helper to import multiple trees from a tree map
1111
900
  * @param {Object} treesMap map of trees object
1112
901
  * @param {TreeImportOptions} options import options
1113
902
  */
1114
903
 
1115
- async function importAllJourneys(treesMap, options) {
904
+ export async function importAllJourneys(treesMap, options) {
1116
905
  const installedJourneys = (await getTrees()).result.map(x => x._id);
1117
906
  const unresolvedJourneys = {};
1118
907
  const resolvedJourneys = [];
@@ -1143,39 +932,6 @@ async function importAllJourneys(treesMap, options) {
1143
932
 
1144
933
  stopProgressIndicator('Done');
1145
934
  }
1146
- /**
1147
- * Import all journeys from file
1148
- * @param {string} file import file name
1149
- * @param {TreeImportOptions} options import options
1150
- */
1151
-
1152
-
1153
- export async function importJourneysFromFile(file, options) {
1154
- fs.readFile(file, 'utf8', (err, data) => {
1155
- if (err) throw err;
1156
- const fileData = JSON.parse(data);
1157
- importAllJourneys(fileData.trees, options);
1158
- });
1159
- }
1160
- /**
1161
- * Import all journeys from separate files
1162
- * @param {TreeImportOptions} options import options
1163
- */
1164
-
1165
- export async function importJourneysFromFiles(options) {
1166
- const names = fs.readdirSync('.');
1167
- const jsonFiles = names.filter(name => name.toLowerCase().endsWith('.journey.json'));
1168
- const allJourneysData = {
1169
- trees: {}
1170
- };
1171
-
1172
- for (const file of jsonFiles) {
1173
- const journeyData = JSON.parse(fs.readFileSync(file, 'utf8'));
1174
- allJourneysData.trees[journeyData.tree._id] = journeyData;
1175
- }
1176
-
1177
- importAllJourneys(allJourneysData.trees, options);
1178
- }
1179
935
  /**
1180
936
  * Get the node reference obbject for a node object. Node reference objects
1181
937
  * are used in a tree flow definition and within page nodes to reference
@@ -1217,8 +973,7 @@ export const onlineTreeExportResolver = async function (treeId) {
1217
973
  debug(`onlineTreeExportResolver(${treeId})`);
1218
974
  return await exportJourney(treeId, {
1219
975
  deps: false,
1220
- useStringArrays: false,
1221
- verbose: false
976
+ useStringArrays: false
1222
977
  });
1223
978
  };
1224
979
  /**
@@ -1501,70 +1256,6 @@ export function getJourneyClassification(journey) {
1501
1256
  if (premium) classifications.push(JourneyClassification.PREMIUM);
1502
1257
  return classifications;
1503
1258
  }
1504
- /**
1505
- * List all the journeys/trees
1506
- * @param {boolean} long Long version, all the fields
1507
- * @param {boolean} analyze Analyze journeys/trees for custom nodes (expensive)
1508
- * @returns {Promise<unknown[]>} a promise that resolves to an array journey objects
1509
- */
1510
-
1511
- export async function listJourneys(long = false, analyze = false) {
1512
- let journeys = [];
1513
-
1514
- try {
1515
- journeys = await getJourneys();
1516
-
1517
- if (!long && !analyze) {
1518
- for (const journeyStub of journeys) {
1519
- printMessage(`${journeyStub['_id']}`, 'data');
1520
- }
1521
- } else {
1522
- if (!analyze) {
1523
- const table = createTable(['Name', 'Status', 'Tags']);
1524
-
1525
- for (const journeyStub of journeys) {
1526
- var _journeyStub$uiConfig;
1527
-
1528
- table.push([`${journeyStub._id}`, journeyStub.enabled === false ? 'disabled'['brightRed'] : 'enabled'['brightGreen'], (_journeyStub$uiConfig = journeyStub.uiConfig) !== null && _journeyStub$uiConfig !== void 0 && _journeyStub$uiConfig.categories ? wordwrap(JSON.parse(journeyStub.uiConfig.categories).join(', '), 60) : '']);
1529
- }
1530
-
1531
- printMessage(table.toString(), 'data');
1532
- } else {
1533
- createProgressIndicator(0, 'Retrieving details of all journeys...', 'indeterminate');
1534
- const exportPromises = [];
1535
-
1536
- try {
1537
- for (const journeyStub of journeys) {
1538
- exportPromises.push(exportJourney(journeyStub['_id'], {
1539
- useStringArrays: false,
1540
- deps: false,
1541
- verbose: false
1542
- }));
1543
- }
1544
-
1545
- const journeyExports = await Promise.all(exportPromises);
1546
- stopProgressIndicator('Retrieved details of all journeys.', 'success');
1547
- const table = createTable(['Name', 'Status', 'Classification', 'Tags']);
1548
-
1549
- for (const journeyExport of journeyExports) {
1550
- var _journeyExport$tree$u;
1551
-
1552
- table.push([`${journeyExport.tree._id}`, journeyExport.tree.enabled === false ? 'disabled'['brightRed'] : 'enabled'['brightGreen'], getJourneyClassification(journeyExport).join(', '), (_journeyExport$tree$u = journeyExport.tree.uiConfig) !== null && _journeyExport$tree$u !== void 0 && _journeyExport$tree$u.categories ? wordwrap(JSON.parse(journeyExport.tree.uiConfig.categories).join(', '), 60) : '']);
1553
- }
1554
-
1555
- printMessage(table.toString(), 'data');
1556
- } catch (error) {
1557
- stopProgressIndicator('Error retrieving details of all journeys.', 'fail');
1558
- printMessage(error.response.data, 'error');
1559
- }
1560
- }
1561
- }
1562
- } catch (error) {
1563
- printMessage(error.response.data, 'error');
1564
- }
1565
-
1566
- return journeys;
1567
- }
1568
1259
  /**
1569
1260
  * Delete a journey
1570
1261
  * @param {string} journeyId journey id/name
@@ -151,7 +151,11 @@ export async function fetchLogs(source, startTs, endTs, levels, txid, ffString,
151
151
  const noiseFilter = nf == null ? noise : nf;
152
152
 
153
153
  if (Array.isArray(logsObject.result)) {
154
- filteredLogs = logsObject.result.filter(el => !noiseFilter.includes(el.payload.logger) && !noiseFilter.includes(el.type) && (levels[0] === 'ALL' || levels.includes(resolvePayloadLevel(el))) && (typeof txid === 'undefined' || txid === null || el.payload.transactionId.includes(txid)));
154
+ filteredLogs = logsObject.result.filter(el => {
155
+ var _el$payload$transacti2;
156
+
157
+ return !noiseFilter.includes(el.payload.logger) && !noiseFilter.includes(el.type) && (levels[0] === 'ALL' || levels.includes(resolvePayloadLevel(el))) && (typeof txid === 'undefined' || txid === null || ((_el$payload$transacti2 = el.payload.transactionId) === null || _el$payload$transacti2 === void 0 ? void 0 : _el$payload$transacti2.includes(txid)));
158
+ });
155
159
  }
156
160
 
157
161
  filteredLogs.forEach(e => {
@@ -50,6 +50,8 @@ export default {
50
50
  getMasterKeyPath: () => _sessionStorage['masterKeyPath'],
51
51
  setOutputFile: value => _sessionStorage['outputFile'] = value,
52
52
  getOutputFile: () => _sessionStorage['outputFile'],
53
+ setDirectory: value => _sessionStorage['directory'] = value,
54
+ getDirectory: () => _sessionStorage['directory'],
53
55
  setPrintHandler: printHandler => _sessionStorage['printHandler'] = printHandler,
54
56
  getPrintHandler: () => _sessionStorage['printHandler'],
55
57
  setErrorHandler: errorHandler => _sessionStorage['errorHandler'] = errorHandler,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rockcarver/frodo-lib",
3
- "version": "0.16.1",
3
+ "version": "0.16.2-1",
4
4
  "type": "commonjs",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.mjs",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ops/AuthenticateOps.ts"],"names":[],"mappings":"AAgTA;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,UAAQ,oBAwC3C","file":"AuthenticateOps.d.ts","sourcesContent":["import url from 'url';\nimport { createHash, randomBytes } from 'crypto';\nimport readlineSync from 'readline-sync';\nimport { encodeBase64Url } from '../api/utils/Base64';\nimport storage from '../storage/SessionStorage';\nimport * as global from '../storage/StaticStorage';\nimport { printMessage } from './utils/Console';\nimport { getServerInfo, getServerVersionInfo } from '../api/ServerInfoApi';\nimport { step } from '../api/AuthenticateApi';\nimport { accessToken, authorize } from '../api/OAuth2OIDCApi';\nimport {\n getConnectionProfile,\n saveConnectionProfile,\n} from './ConnectionProfileOps';\n\nconst adminClientPassword = 'doesnotmatter';\nconst redirectUrlTemplate = '/platform/appAuthHelperRedirect.html';\n\nconst idmAdminScope = 'fr:idm:* openid';\n\nlet adminClientId = 'idmAdminClient';\n\n/**\n * Helper function to get cookie name\n * @returns {String} cookie name\n */\nasync function getCookieName() {\n try {\n return (await getServerInfo()).data.cookieName;\n } catch (error) {\n printMessage(`Error getting cookie name: ${error}`, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to determine if this is a setup mfa prompt in the ID Cloud tenant admin login journey\n * @param {Object} payload response from the previous authentication journey step\n * @returns {Object} an object indicating if 2fa is required and the original payload\n */\nfunction checkAndHandle2FA(payload) {\n // let skippable = false;\n if ('callbacks' in payload) {\n for (const element of payload.callbacks) {\n if (element.type === 'HiddenValueCallback') {\n if (element.input[0].value.includes('skip')) {\n // skippable = true;\n element.input[0].value = 'Skip';\n return {\n need2fa: true,\n payload,\n };\n }\n }\n if (element.type === 'NameCallback') {\n if (element.output[0].value.includes('code')) {\n // skippable = false;\n printMessage('2FA is enabled and required for this user...');\n const code = readlineSync.question(`${element.output[0].value}: `);\n element.input[0].value = code;\n return {\n need2fa: true,\n payload,\n };\n }\n }\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n}\n\n/**\n * Helper function to set the default realm by deployment type\n * @param {String} deploymentType deployment type\n */\nfunction determineDefaultRealm(deploymentType) {\n if (storage.session.getRealm() === global.DEFAULT_REALM_KEY) {\n storage.session.setRealm(global.DEPLOYMENT_TYPE_REALM_MAP[deploymentType]);\n }\n}\n\n/**\n * Helper function to determine the deployment type\n * @returns {String} deployment type\n */\nasync function determineDeploymentType() {\n const fidcClientId = 'idmAdminClient';\n const forgeopsClientId = 'idm-admin-ui';\n\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n\n const config = {\n maxRedirects: 0,\n };\n let bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${fidcClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n\n let deploymentType = global.CLASSIC_DEPLOYMENT_TYPE_KEY;\n try {\n await authorize(bodyFormData, config);\n } catch (e) {\n if (\n e.response?.status === 302 &&\n e.response.headers?.location?.indexOf('code=') > -1\n ) {\n printMessage('ForgeRock Identity Cloud ', 'info', false);\n deploymentType = global.CLOUD_DEPLOYMENT_TYPE_KEY;\n } else {\n try {\n bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${forgeopsClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n await authorize(bodyFormData, config);\n } catch (ex) {\n if (\n ex.response?.status === 302 &&\n ex.response.headers?.location?.indexOf('code=') > -1\n ) {\n adminClientId = forgeopsClientId;\n printMessage('ForgeOps deployment ', 'info', false);\n deploymentType = global.FORGEOPS_DEPLOYMENT_TYPE_KEY;\n } else {\n printMessage('Classic deployment ', 'info', false);\n }\n }\n }\n printMessage('detected.');\n }\n determineDefaultRealm(deploymentType);\n return deploymentType;\n}\n\n/**\n * Helper function to extract the semantic version string from a version info object\n * @param {Object} versionInfo version info object\n * @returns {String} semantic version\n */\nasync function getSemanticVersion(versionInfo) {\n if ('version' in versionInfo) {\n const versionString = versionInfo.version;\n const rx = /([\\d]\\.[\\d]\\.[\\d](\\.[\\d])*)/g;\n const version = versionString.match(rx);\n return version[0];\n }\n throw new Error('Cannot extract semantic version from version info object.');\n}\n\n/**\n * Helper function to authenticate and obtain and store session cookie\n * @returns {String} empty string or null\n */\nasync function authenticate() {\n storage.session.setCookieName(await getCookieName());\n try {\n const config = {\n headers: {\n 'X-OpenAM-Username': storage.session.getUsername(),\n 'X-OpenAM-Password': storage.session.getPassword(),\n },\n };\n const response1 = (await step({}, config)).data;\n const skip2FA = checkAndHandle2FA(response1);\n let response2 = {};\n if (skip2FA.need2fa) {\n response2 = (await step(skip2FA.payload)).data;\n } else {\n response2 = skip2FA.payload;\n }\n if ('tokenId' in response2) {\n storage.session.setCookieValue(response2['tokenId']);\n if (!storage.session.getDeploymentType()) {\n storage.session.setDeploymentType(await determineDeploymentType());\n } else {\n determineDefaultRealm(storage.session.getDeploymentType());\n }\n const versionInfo = (await getServerVersionInfo()).data;\n printMessage(`Connected to ${versionInfo.fullVersion}`);\n const version = await getSemanticVersion(versionInfo);\n storage.session.setAmVersion(version);\n return '';\n }\n printMessage(`error authenticating`, 'error');\n printMessage('+++ likely cause, bad credentials!!! +++', 'error');\n return null;\n } catch (e) {\n if (e.response?.status === 401) {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ likely cause, bad credentials +++', 'error');\n }\n if (e.message === 'self signed certificate') {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ use -k, --insecure option to allow +++', 'error');\n } else {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage(e.response?.data, 'error');\n }\n return null;\n }\n}\n\n/**\n * Helper function to obtain an oauth2 authorization code\n * @param {String} redirectURL oauth2 redirect uri\n * @param {String} codeChallenge PKCE code challenge\n * @param {String} codeChallengeMethod PKCE code challenge method\n * @returns {String} oauth2 authorization code or null\n */\nasync function getAuthCode(redirectURL, codeChallenge, codeChallengeMethod) {\n try {\n const bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${adminClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${codeChallenge}&code_challenge_method=${codeChallengeMethod}`;\n const config = {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n };\n const response = await authorize(bodyFormData, config);\n if (response.status < 200 || response.status > 399) {\n printMessage('error getting auth code', 'error');\n printMessage(\n 'likely cause: mismatched parameters with OAuth client config',\n 'error'\n );\n return null;\n }\n const redirectLocationURL = response.request.res.responseUrl;\n const queryObject = url.parse(redirectLocationURL, true).query;\n if ('code' in queryObject) {\n return queryObject.code;\n }\n printMessage('auth code not found', 'error');\n return null;\n } catch (error) {\n printMessage(`error getting auth code - ${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to obtain oauth2 access token\n * @returns {String} empty string or null\n */\nasync function getAccessToken() {\n try {\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n const authCode = await getAuthCode(redirectURL, challenge, challengeMethod);\n if (authCode == null) {\n printMessage('error getting auth code', 'error');\n return null;\n }\n let response = null;\n if (\n storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY\n ) {\n const config = {\n auth: {\n username: adminClientId,\n password: adminClientPassword,\n },\n };\n const bodyFormData = `redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData, config);\n } else {\n const bodyFormData = `client_id=${adminClientId}&redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData);\n }\n if (response.status < 200 || response.status > 399) {\n printMessage(`access token call returned ${response.status}`, 'error');\n return null;\n }\n if ('access_token' in response.data) {\n storage.session.setBearerToken(response.data.access_token);\n return '';\n }\n printMessage(\"can't get access token\", 'error');\n return null;\n } catch (e) {\n printMessage('error getting access token - ', 'error');\n return null;\n }\n}\n\n/**\n * Get tokens\n * @param {boolean} save true to save a connection profile upon successful authentication, false otherwise\n * @returns {boolean} true if tokens were successfully obtained, false otherwise\n */\nexport async function getTokens(save = false) {\n let credsFromParameters = true;\n // if username/password on cli are empty, try to read from connections.json\n if (\n storage.session.getUsername() == null &&\n storage.session.getPassword() == null\n ) {\n credsFromParameters = false;\n const conn = await getConnectionProfile();\n if (conn) {\n storage.session.setTenant(conn.tenant);\n storage.session.setUsername(conn.username);\n storage.session.setPassword(conn.password);\n storage.session.setAuthenticationService(conn.authenticationService);\n storage.session.setAuthenticationHeaderOverrides(\n conn.authenticationHeaderOverrides\n );\n } else {\n return false;\n }\n }\n await authenticate();\n if (\n storage.session.getCookieValue() &&\n !storage.session.getBearerToken() &&\n (storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY)\n ) {\n await getAccessToken();\n }\n if (save && storage.session.getCookieValue() && credsFromParameters) {\n // valid cookie, which means valid username/password combo. Save it in connections.json\n saveConnectionProfile();\n return true;\n }\n if (!storage.session.getCookieValue()) {\n return false;\n }\n return true;\n}\n"]}
1
+ {"version":3,"sources":["../src/ops/AuthenticateOps.ts"],"names":[],"mappings":"AAyTA;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,UAAQ,oBAwC3C","file":"AuthenticateOps.d.ts","sourcesContent":["import url from 'url';\nimport { createHash, randomBytes } from 'crypto';\nimport readlineSync from 'readline-sync';\nimport { encodeBase64Url } from '../api/utils/Base64';\nimport storage from '../storage/SessionStorage';\nimport * as global from '../storage/StaticStorage';\nimport { printMessage } from './utils/Console';\nimport { getServerInfo, getServerVersionInfo } from '../api/ServerInfoApi';\nimport { step } from '../api/AuthenticateApi';\nimport { accessToken, authorize } from '../api/OAuth2OIDCApi';\nimport {\n getConnectionProfile,\n saveConnectionProfile,\n} from './ConnectionProfileOps';\n\nconst adminClientPassword = 'doesnotmatter';\nconst redirectUrlTemplate = '/platform/appAuthHelperRedirect.html';\n\nconst idmAdminScope = 'fr:idm:* openid';\n\nlet adminClientId = 'idmAdminClient';\n\n/**\n * Helper function to get cookie name\n * @returns {String} cookie name\n */\nasync function getCookieName() {\n try {\n return (await getServerInfo()).data.cookieName;\n } catch (error) {\n printMessage(`Error getting cookie name: ${error}`, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to determine if this is a setup mfa prompt in the ID Cloud tenant admin login journey\n * @param {Object} payload response from the previous authentication journey step\n * @returns {Object} an object indicating if 2fa is required and the original payload\n */\nfunction checkAndHandle2FA(payload) {\n // let skippable = false;\n if ('callbacks' in payload) {\n for (const element of payload.callbacks) {\n if (element.type === 'HiddenValueCallback') {\n if (element.input[0].value.includes('skip')) {\n // skippable = true;\n element.input[0].value = 'Skip';\n return {\n need2fa: true,\n payload,\n };\n }\n }\n if (element.type === 'NameCallback') {\n if (element.output[0].value.includes('code')) {\n // skippable = false;\n printMessage('2FA is enabled and required for this user...');\n const code = readlineSync.question(`${element.output[0].value}: `);\n element.input[0].value = code;\n return {\n need2fa: true,\n payload,\n };\n }\n }\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n}\n\n/**\n * Helper function to set the default realm by deployment type\n * @param {String} deploymentType deployment type\n */\nfunction determineDefaultRealm(deploymentType) {\n if (storage.session.getRealm() === global.DEFAULT_REALM_KEY) {\n storage.session.setRealm(global.DEPLOYMENT_TYPE_REALM_MAP[deploymentType]);\n }\n}\n\n/**\n * Helper function to determine the deployment type\n * @returns {String} deployment type\n */\nasync function determineDeploymentType() {\n const fidcClientId = 'idmAdminClient';\n const forgeopsClientId = 'idm-admin-ui';\n\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n\n const config = {\n maxRedirects: 0,\n };\n let bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${fidcClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n\n let deploymentType = global.CLASSIC_DEPLOYMENT_TYPE_KEY;\n try {\n await authorize(bodyFormData, config);\n } catch (e) {\n if (\n e.response?.status === 302 &&\n e.response.headers?.location?.indexOf('code=') > -1\n ) {\n printMessage('ForgeRock Identity Cloud ', 'info', false);\n deploymentType = global.CLOUD_DEPLOYMENT_TYPE_KEY;\n } else {\n try {\n bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${forgeopsClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n await authorize(bodyFormData, config);\n } catch (ex) {\n if (\n ex.response?.status === 302 &&\n ex.response.headers?.location?.indexOf('code=') > -1\n ) {\n adminClientId = forgeopsClientId;\n printMessage('ForgeOps deployment ', 'info', false);\n deploymentType = global.FORGEOPS_DEPLOYMENT_TYPE_KEY;\n } else {\n printMessage('Classic deployment ', 'info', false);\n }\n }\n }\n printMessage('detected.');\n }\n determineDefaultRealm(deploymentType);\n return deploymentType;\n}\n\n/**\n * Helper function to extract the semantic version string from a version info object\n * @param {Object} versionInfo version info object\n * @returns {String} semantic version\n */\nasync function getSemanticVersion(versionInfo) {\n if ('version' in versionInfo) {\n const versionString = versionInfo.version;\n const rx = /([\\d]\\.[\\d]\\.[\\d](\\.[\\d])*)/g;\n const version = versionString.match(rx);\n return version[0];\n }\n throw new Error('Cannot extract semantic version from version info object.');\n}\n\n/**\n * Helper function to authenticate and obtain and store session cookie\n * @returns {String} empty string or null\n */\nasync function authenticate() {\n storage.session.setCookieName(await getCookieName());\n try {\n const config = {\n headers: {\n 'X-OpenAM-Username': storage.session.getUsername(),\n 'X-OpenAM-Password': storage.session.getPassword(),\n },\n };\n const response1 = (await step({}, config)).data;\n const skip2FA = checkAndHandle2FA(response1);\n let response2 = {};\n if (skip2FA.need2fa) {\n response2 = (await step(skip2FA.payload)).data;\n } else {\n response2 = skip2FA.payload;\n }\n if ('tokenId' in response2) {\n storage.session.setCookieValue(response2['tokenId']);\n if (!storage.session.getDeploymentType()) {\n storage.session.setDeploymentType(await determineDeploymentType());\n } else {\n determineDefaultRealm(storage.session.getDeploymentType());\n }\n const versionInfo = (await getServerVersionInfo()).data;\n\n // https://github.com/rockcarver/frodo-cli/issues/109\n // printMessage(`Connected to ${versionInfo.fullVersion}`);\n\n // https://github.com/rockcarver/frodo-cli/issues/102\n printMessage(\n `Connected to [${storage.session.getTenant()}], [${\n !storage.session.getRealm() ? 'alpha' : storage.session.getRealm()\n }] realm, as [${storage.session.getUsername()}]`\n );\n const version = await getSemanticVersion(versionInfo);\n storage.session.setAmVersion(version);\n return '';\n }\n printMessage(`error authenticating`, 'error');\n printMessage('+++ likely cause, bad credentials!!! +++', 'error');\n return null;\n } catch (e) {\n if (e.response?.status === 401) {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ likely cause, bad credentials +++', 'error');\n }\n if (e.message === 'self signed certificate') {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ use -k, --insecure option to allow +++', 'error');\n } else {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage(e.response?.data, 'error');\n }\n return null;\n }\n}\n\n/**\n * Helper function to obtain an oauth2 authorization code\n * @param {String} redirectURL oauth2 redirect uri\n * @param {String} codeChallenge PKCE code challenge\n * @param {String} codeChallengeMethod PKCE code challenge method\n * @returns {String} oauth2 authorization code or null\n */\nasync function getAuthCode(redirectURL, codeChallenge, codeChallengeMethod) {\n try {\n const bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${adminClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${codeChallenge}&code_challenge_method=${codeChallengeMethod}`;\n const config = {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n };\n const response = await authorize(bodyFormData, config);\n if (response.status < 200 || response.status > 399) {\n printMessage('error getting auth code', 'error');\n printMessage(\n 'likely cause: mismatched parameters with OAuth client config',\n 'error'\n );\n return null;\n }\n const redirectLocationURL = response.request.res.responseUrl;\n const queryObject = url.parse(redirectLocationURL, true).query;\n if ('code' in queryObject) {\n return queryObject.code;\n }\n printMessage('auth code not found', 'error');\n return null;\n } catch (error) {\n printMessage(`error getting auth code - ${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to obtain oauth2 access token\n * @returns {String} empty string or null\n */\nasync function getAccessToken() {\n try {\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n const authCode = await getAuthCode(redirectURL, challenge, challengeMethod);\n if (authCode == null) {\n printMessage('error getting auth code', 'error');\n return null;\n }\n let response = null;\n if (\n storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY\n ) {\n const config = {\n auth: {\n username: adminClientId,\n password: adminClientPassword,\n },\n };\n const bodyFormData = `redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData, config);\n } else {\n const bodyFormData = `client_id=${adminClientId}&redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData);\n }\n if (response.status < 200 || response.status > 399) {\n printMessage(`access token call returned ${response.status}`, 'error');\n return null;\n }\n if ('access_token' in response.data) {\n storage.session.setBearerToken(response.data.access_token);\n return '';\n }\n printMessage(\"can't get access token\", 'error');\n return null;\n } catch (e) {\n printMessage('error getting access token - ', 'error');\n return null;\n }\n}\n\n/**\n * Get tokens\n * @param {boolean} save true to save a connection profile upon successful authentication, false otherwise\n * @returns {boolean} true if tokens were successfully obtained, false otherwise\n */\nexport async function getTokens(save = false) {\n let credsFromParameters = true;\n // if username/password on cli are empty, try to read from connections.json\n if (\n storage.session.getUsername() == null &&\n storage.session.getPassword() == null\n ) {\n credsFromParameters = false;\n const conn = await getConnectionProfile();\n if (conn) {\n storage.session.setTenant(conn.tenant);\n storage.session.setUsername(conn.username);\n storage.session.setPassword(conn.password);\n storage.session.setAuthenticationService(conn.authenticationService);\n storage.session.setAuthenticationHeaderOverrides(\n conn.authenticationHeaderOverrides\n );\n } else {\n return false;\n }\n }\n await authenticate();\n if (\n storage.session.getCookieValue() &&\n !storage.session.getBearerToken() &&\n (storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY)\n ) {\n await getAccessToken();\n }\n if (save && storage.session.getCookieValue() && credsFromParameters) {\n // valid cookie, which means valid username/password combo. Save it in connections.json\n saveConnectionProfile();\n return true;\n }\n if (!storage.session.getCookieValue()) {\n return false;\n }\n return true;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ops/ConnectionProfileOps.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAmBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,UAAQ,QA2BlD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,SAkCrC;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,KAAA;;;;;;;;GAkCpD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB;;;;;;;;GAEzC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,kBAoD1C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,KAAA,QAwB3C;AAED,wBAAsB,yBAAyB,CAAC,IAAI,KAAA,EAAE,WAAW,KAAA,iBAyBhE","file":"ConnectionProfileOps.d.ts","sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport storage from '../storage/SessionStorage';\nimport DataProtection from './utils/DataProtection';\nimport { createObjectTable, createTable, printMessage } from './utils/Console';\nimport { FRODO_CONNECTION_PROFILES_PATH_KEY } from '../storage/StaticStorage';\n\nconst dataProtection = new DataProtection();\n\nconst fileOptions = {\n indentation: 4,\n};\n\n/**\n * Get connection profiles file name\n * @returns {String} connection profiles file name\n */\nexport function getConnectionProfilesPath(): string {\n return (\n storage.session.getConnectionProfilesPath() ||\n process.env[FRODO_CONNECTION_PROFILES_PATH_KEY] ||\n `${os.homedir()}/.frodo/.frodorc`\n );\n}\n\n/**\n * Find connection profile\n * @param {Object} connectionProfiles connection profile object\n * @param {String} host tenant host url or unique substring\n * @returns {Object} connection profile object or null\n */\nfunction findConnectionProfile(connectionProfiles, host) {\n for (const tenant in connectionProfiles) {\n if (tenant.includes(host)) {\n const profile = connectionProfiles[tenant];\n profile.tenant = tenant;\n return profile;\n }\n }\n return null;\n}\n\n/**\n * List connection profiles\n * @param {boolean} long Long list format with details\n */\nexport function listConnectionProfiles(long = false) {\n const filename = getConnectionProfilesPath();\n try {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n if (long) {\n const table = createTable(['Host', 'Username', 'Log API Key']);\n Object.keys(connectionsData).forEach((c) => {\n table.push([\n c,\n connectionsData[c].username,\n connectionsData[c].logApiKey,\n ]);\n });\n printMessage(table.toString(), 'data');\n } else {\n Object.keys(connectionsData).forEach((c) => {\n printMessage(`${c}`, 'data');\n });\n }\n printMessage(\n 'Any unique substring of a saved host can be used as the value for host parameter in all commands',\n 'info'\n );\n } catch (e) {\n printMessage(`No connections found in ${filename} (${e.message})`, 'error');\n }\n}\n\n/**\n * Initialize connection profiles\n */\nexport function initConnectionProfiles() {\n // create connections.json file if it doesn't exist\n const filename = getConnectionProfilesPath();\n const folderName = path.dirname(filename);\n if (!fs.existsSync(folderName)) {\n fs.mkdirSync(folderName, { recursive: true });\n if (!fs.existsSync(filename)) {\n fs.writeFileSync(\n filename,\n JSON.stringify({}, null, fileOptions.indentation)\n );\n }\n }\n // encrypt the password from clear text to aes-256-GCM\n else {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n let convert = false;\n Object.keys(connectionsData).forEach(async (conn) => {\n if (connectionsData[conn].password) {\n convert = true;\n connectionsData[conn].encodedPassword = await dataProtection.encrypt(\n connectionsData[conn].password\n ); // Buffer.from(connectionsData[conn].password).toString('base64');\n delete connectionsData[conn].password;\n }\n });\n if (convert) {\n fs.writeFileSync(\n filename,\n JSON.stringify(connectionsData, null, fileOptions.indentation)\n );\n }\n }\n}\n\n/**\n * Get connection profile by host\n * @param {String} host host tenant host url or unique substring\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfileByHost(host) {\n try {\n const filename = getConnectionProfilesPath();\n const connectionsData = JSON.parse(fs.readFileSync(filename, 'utf8'));\n const profile = findConnectionProfile(connectionsData, host);\n if (!profile) {\n printMessage(\n `Profile for ${host} not found. Please specify credentials on command line`,\n 'error'\n );\n return null;\n }\n return {\n tenant: profile.tenant,\n username: profile.username ? profile.username : null,\n password: profile.encodedPassword\n ? await dataProtection.decrypt(profile.encodedPassword)\n : null,\n key: profile.logApiKey ? profile.logApiKey : null,\n secret: profile.logApiSecret ? profile.logApiSecret : null,\n authenticationService: profile.authenticationService\n ? profile.authenticationService\n : null,\n authenticationHeaderOverrides: profile.authenticationHeaderOverrides\n ? profile.authenticationHeaderOverrides\n : {},\n };\n } catch (e) {\n printMessage(\n `Can not read saved connection info, please specify credentials on command line: ${e}`,\n 'error'\n );\n return null;\n }\n}\n\n/**\n * Get connection profile\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfile() {\n return getConnectionProfileByHost(storage.session.getTenant());\n}\n\n/**\n * Save connection profile\n */\nexport async function saveConnectionProfile() {\n const filename = getConnectionProfilesPath();\n printMessage(`Saving creds in ${filename}...`);\n let connectionsData = {};\n let existingData = {};\n try {\n fs.statSync(filename);\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n if (connectionsData[storage.session.getTenant()]) {\n existingData = connectionsData[storage.session.getTenant()];\n printMessage(\n `Updating connection profile ${storage.session.getTenant()}`\n );\n } else\n printMessage(`Adding connection profile ${storage.session.getTenant()}`);\n } catch (e) {\n printMessage(\n `Creating connection profiles file ${filename} with ${storage.session.getTenant()}`\n );\n }\n if (storage.session.getUsername())\n existingData['username'] = storage.session.getUsername();\n if (storage.session.getPassword())\n existingData['encodedPassword'] = await dataProtection.encrypt(\n storage.session.getPassword()\n );\n if (storage.session.getLogApiKey())\n existingData['logApiKey'] = storage.session.getLogApiKey();\n if (storage.session.getLogApiSecret())\n existingData['logApiSecret'] = storage.session.getLogApiSecret();\n\n // advanced settings\n if (storage.session.getAuthenticationService()) {\n existingData['authenticationService'] =\n storage.session.getAuthenticationService();\n printMessage(\n 'Advanced setting: Authentication Service: ' +\n storage.session.getAuthenticationService(),\n 'info'\n );\n }\n if (storage.session.getAuthenticationHeaderOverrides()) {\n existingData['authenticationHeaderOverrides'] =\n storage.session.getAuthenticationHeaderOverrides();\n printMessage('Advanced setting: Authentication Header Overrides: ', 'info');\n printMessage(storage.session.getAuthenticationHeaderOverrides(), 'info');\n }\n\n connectionsData[storage.session.getTenant()] = existingData;\n\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n}\n\n/**\n * Delete connection profile\n * @param {String} host host tenant host url or unique substring\n */\nexport function deleteConnectionProfile(host) {\n const filename = getConnectionProfilesPath();\n let connectionsData = {};\n fs.stat(filename, (err) => {\n if (err == null) {\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n const profile = findConnectionProfile(connectionsData, host);\n if (profile) {\n printMessage(`Deleting connection profile ${profile.tenant}`);\n delete connectionsData[profile.tenant];\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n } else if (err.code === 'ENOENT') {\n printMessage(`Connection profile file ${filename} not found`);\n } else {\n printMessage(\n `Error in deleting connection profile: ${err.code}`,\n 'error'\n );\n }\n });\n}\n\nexport async function describeConnectionProfile(host, showSecrets) {\n const profile = await getConnectionProfileByHost(host);\n if (profile) {\n if (!showSecrets) {\n delete profile.password;\n delete profile.secret;\n }\n if (!profile.key) {\n delete profile.key;\n delete profile.secret;\n }\n const keyMap = {\n tenant: 'Host',\n username: 'Username',\n password: 'Password',\n key: 'Log API Key',\n secret: 'Log API Secret',\n authenticationService: 'Authentication Service',\n authenticationHeaderOverrides: 'Authentication Header Overrides',\n };\n const table = createObjectTable(profile, keyMap);\n printMessage(table.toString(), 'data');\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/ops/ConnectionProfileOps.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAoBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,UAAQ,QA2BlD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,SAkCrC;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,KAAA;;;;;;;;GA0CpD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB;;;;;;;;GAEzC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,kBAoD1C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,KAAA,QAiC3C;AAED,wBAAsB,yBAAyB,CAAC,IAAI,KAAA,EAAE,WAAW,KAAA,iBAyBhE","file":"ConnectionProfileOps.d.ts","sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport storage from '../storage/SessionStorage';\nimport DataProtection from './utils/DataProtection';\nimport { createObjectTable, createTable, printMessage } from './utils/Console';\nimport { FRODO_CONNECTION_PROFILES_PATH_KEY } from '../storage/StaticStorage';\nimport { profile } from 'console';\n\nconst dataProtection = new DataProtection();\n\nconst fileOptions = {\n indentation: 4,\n};\n\n/**\n * Get connection profiles file name\n * @returns {String} connection profiles file name\n */\nexport function getConnectionProfilesPath(): string {\n return (\n storage.session.getConnectionProfilesPath() ||\n process.env[FRODO_CONNECTION_PROFILES_PATH_KEY] ||\n `${os.homedir()}/.frodo/.frodorc`\n );\n}\n\n/**\n * Find connection profile\n * @param {Object} connectionProfiles connection profile object\n * @param {String} host tenant host url or unique substring\n * @returns {Object} connection profile object or null\n */\nfunction findConnectionProfile(connectionProfiles, host) {\n const profiles = [];\n for (const tenant in connectionProfiles) {\n if (tenant.includes(host)) {\n const foundProfile = connectionProfiles[tenant];\n foundProfile.tenant = tenant;\n profiles.push(foundProfile);\n }\n }\n return profiles;\n}\n\n/**\n * List connection profiles\n * @param {boolean} long Long list format with details\n */\nexport function listConnectionProfiles(long = false) {\n const filename = getConnectionProfilesPath();\n try {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n if (long) {\n const table = createTable(['Host', 'Username', 'Log API Key']);\n Object.keys(connectionsData).forEach((c) => {\n table.push([\n c,\n connectionsData[c].username,\n connectionsData[c].logApiKey,\n ]);\n });\n printMessage(table.toString(), 'data');\n } else {\n Object.keys(connectionsData).forEach((c) => {\n printMessage(`${c}`, 'data');\n });\n }\n printMessage(\n 'Any unique substring of a saved host can be used as the value for host parameter in all commands',\n 'info'\n );\n } catch (e) {\n printMessage(`No connections found in ${filename} (${e.message})`, 'error');\n }\n}\n\n/**\n * Initialize connection profiles\n */\nexport function initConnectionProfiles() {\n // create connections.json file if it doesn't exist\n const filename = getConnectionProfilesPath();\n const folderName = path.dirname(filename);\n if (!fs.existsSync(folderName)) {\n fs.mkdirSync(folderName, { recursive: true });\n if (!fs.existsSync(filename)) {\n fs.writeFileSync(\n filename,\n JSON.stringify({}, null, fileOptions.indentation)\n );\n }\n }\n // encrypt the password from clear text to aes-256-GCM\n else {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n let convert = false;\n Object.keys(connectionsData).forEach(async (conn) => {\n if (connectionsData[conn].password) {\n convert = true;\n connectionsData[conn].encodedPassword = await dataProtection.encrypt(\n connectionsData[conn].password\n ); // Buffer.from(connectionsData[conn].password).toString('base64');\n delete connectionsData[conn].password;\n }\n });\n if (convert) {\n fs.writeFileSync(\n filename,\n JSON.stringify(connectionsData, null, fileOptions.indentation)\n );\n }\n }\n}\n\n/**\n * Get connection profile by host\n * @param {String} host host tenant host url or unique substring\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfileByHost(host) {\n try {\n const filename = getConnectionProfilesPath();\n const connectionsData = JSON.parse(fs.readFileSync(filename, 'utf8'));\n const profiles = findConnectionProfile(connectionsData, host);\n if (profiles.length == 0) {\n printMessage(\n `Profile for ${host} not found. Please specify credentials on command line`,\n 'error'\n );\n return null;\n }\n if (profiles.length > 1) {\n printMessage(`Multiple matching profiles found.`, 'error');\n profiles.forEach((p) => {\n printMessage(`- ${p.tenant}`, 'error');\n });\n printMessage(`Please specify a unique sub-string`, 'error');\n return null;\n }\n return {\n tenant: profiles[0].tenant,\n username: profiles[0].username ? profiles[0].username : null,\n password: profiles[0].encodedPassword\n ? await dataProtection.decrypt(profiles[0].encodedPassword)\n : null,\n key: profiles[0].logApiKey ? profiles[0].logApiKey : null,\n secret: profiles[0].logApiSecret ? profiles[0].logApiSecret : null,\n authenticationService: profiles[0].authenticationService\n ? profiles[0].authenticationService\n : null,\n authenticationHeaderOverrides: profiles[0].authenticationHeaderOverrides\n ? profiles[0].authenticationHeaderOverrides\n : {},\n };\n } catch (e) {\n printMessage(\n `Can not read saved connection info, please specify credentials on command line: ${e}`,\n 'error'\n );\n return null;\n }\n}\n\n/**\n * Get connection profile\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfile() {\n return getConnectionProfileByHost(storage.session.getTenant());\n}\n\n/**\n * Save connection profile\n */\nexport async function saveConnectionProfile() {\n const filename = getConnectionProfilesPath();\n printMessage(`Saving creds in ${filename}...`);\n let connectionsData = {};\n let existingData = {};\n try {\n fs.statSync(filename);\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n if (connectionsData[storage.session.getTenant()]) {\n existingData = connectionsData[storage.session.getTenant()];\n printMessage(\n `Updating connection profile ${storage.session.getTenant()}`\n );\n } else\n printMessage(`Adding connection profile ${storage.session.getTenant()}`);\n } catch (e) {\n printMessage(\n `Creating connection profiles file ${filename} with ${storage.session.getTenant()}`\n );\n }\n if (storage.session.getUsername())\n existingData['username'] = storage.session.getUsername();\n if (storage.session.getPassword())\n existingData['encodedPassword'] = await dataProtection.encrypt(\n storage.session.getPassword()\n );\n if (storage.session.getLogApiKey())\n existingData['logApiKey'] = storage.session.getLogApiKey();\n if (storage.session.getLogApiSecret())\n existingData['logApiSecret'] = storage.session.getLogApiSecret();\n\n // advanced settings\n if (storage.session.getAuthenticationService()) {\n existingData['authenticationService'] =\n storage.session.getAuthenticationService();\n printMessage(\n 'Advanced setting: Authentication Service: ' +\n storage.session.getAuthenticationService(),\n 'info'\n );\n }\n if (storage.session.getAuthenticationHeaderOverrides()) {\n existingData['authenticationHeaderOverrides'] =\n storage.session.getAuthenticationHeaderOverrides();\n printMessage('Advanced setting: Authentication Header Overrides: ', 'info');\n printMessage(storage.session.getAuthenticationHeaderOverrides(), 'info');\n }\n\n connectionsData[storage.session.getTenant()] = existingData;\n\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n}\n\n/**\n * Delete connection profile\n * @param {String} host host tenant host url or unique substring\n */\nexport function deleteConnectionProfile(host) {\n const filename = getConnectionProfilesPath();\n let connectionsData = {};\n fs.stat(filename, (err) => {\n if (err == null) {\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n const profiles = findConnectionProfile(connectionsData, host);\n if (profiles.length == 1) {\n printMessage(`Deleting connection profile ${profiles[0].tenant}`);\n delete connectionsData[profiles[0].tenant];\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n } else {\n if (profiles.length > 1) {\n printMessage(`Multiple matching profiles found.`, 'error');\n profiles.forEach((p) => {\n printMessage(`- ${p.tenant}`, 'error');\n });\n printMessage(`Please specify a unique sub-string`, 'error');\n return null;\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n }\n } else if (err.code === 'ENOENT') {\n printMessage(`Connection profile file ${filename} not found`);\n } else {\n printMessage(\n `Error in deleting connection profile: ${err.code}`,\n 'error'\n );\n }\n });\n}\n\nexport async function describeConnectionProfile(host, showSecrets) {\n const profile = await getConnectionProfileByHost(host);\n if (profile) {\n if (!showSecrets) {\n delete profile.password;\n delete profile.secret;\n }\n if (!profile.key) {\n delete profile.key;\n delete profile.secret;\n }\n const keyMap = {\n tenant: 'Host',\n username: 'Username',\n password: 'Password',\n key: 'Log API Key',\n secret: 'Log API Secret',\n authenticationService: 'Authentication Service',\n authenticationHeaderOverrides: 'Authentication Header Overrides',\n };\n const table = createObjectTable(profile, keyMap);\n printMessage(table.toString(), 'data');\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n}\n"]}