opal-security 3.1.1-beta.e5e99da → 3.1.1-beta.e92fdf8

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,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createEmptyRequestMetadata = createEmptyRequestMetadata;
3
+ exports.initEmptyRequestMetadata = initEmptyRequestMetadata;
4
4
  exports.selectRequestableItems = selectRequestableItems;
5
5
  exports.chooseAssets = chooseAssets;
6
6
  exports.chooseRoles = chooseRoles;
@@ -9,10 +9,13 @@ exports.setRequestDefaults = setRequestDefaults;
9
9
  exports.promptForReason = promptForReason;
10
10
  exports.promptForExpiration = promptForExpiration;
11
11
  exports.submitFinalRequest = submitFinalRequest;
12
+ const chalk_1 = require("chalk");
12
13
  const inquirer = require("inquirer");
13
14
  const graphql_1 = require("../graphql");
14
- inquirer.registerPrompt("autocomplete", require("inquirer-autocomplete-prompt"));
15
- function createEmptyRequestMetadata() {
15
+ const displays_1 = require("../utils/displays");
16
+ const config_1 = require("./config");
17
+ const { AutoComplete, Select, prompt, Form } = require("enquirer");
18
+ function initEmptyRequestMetadata() {
16
19
  // Initialize with empty defaults
17
20
  const requestDefaults = {
18
21
  durationOptions: [],
@@ -28,6 +31,9 @@ function createEmptyRequestMetadata() {
28
31
  return {
29
32
  requestMap,
30
33
  requestDefaults,
34
+ durationLabel: "",
35
+ durationInMinutes: 0,
36
+ reason: "",
31
37
  };
32
38
  }
33
39
  // Queries and Mutations
@@ -73,18 +79,22 @@ async function queryRequestableApps(cmd, client, input) {
73
79
  });
74
80
  return (_b = (_a = resp === null || resp === void 0 ? void 0 : resp.data) === null || _a === void 0 ? void 0 : _a.appsV2) === null || _b === void 0 ? void 0 : _b.edges.map((edge) => {
75
81
  let type = undefined;
76
- if (edge.node.__typename === "Resource") {
77
- type = edge.node.resourceType;
82
+ switch (edge.node.__typename) {
83
+ case "Resource":
84
+ type = edge.node.resourceType;
85
+ break;
86
+ case "Connection":
87
+ type = edge.node.connectionType;
88
+ break;
89
+ default:
90
+ type = edge.node.__typename;
78
91
  }
79
- if (edge.node.__typename === "Connection") {
80
- type = edge.node.connectionType;
81
- }
82
- const label = `${edge.node.displayName} (${type})`;
83
92
  return {
84
- name: label,
93
+ message: `${edge.node.displayName} [${type}]`,
85
94
  value: {
86
95
  id: edge.node.id,
87
- name: label,
96
+ name: edge.node.displayName,
97
+ toString: () => edge.node.displayName,
88
98
  },
89
99
  };
90
100
  });
@@ -147,16 +157,16 @@ async function queryRequestableAssets(cmd, client, appId, input) {
147
157
  switch (resp.data.app.__typename) {
148
158
  case "App":
149
159
  return (_d = (_c = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.app) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.items) === null || _d === void 0 ? void 0 : _d.map((item) => {
150
- var _a, _b, _c, _d, _e, _f;
160
+ var _a, _b, _c, _d, _e, _f, _g, _h;
151
161
  const name = ((_a = item.resource) === null || _a === void 0 ? void 0 : _a.name) || ((_b = item.group) === null || _b === void 0 ? void 0 : _b.name);
152
162
  const id = ((_c = item.resource) === null || _c === void 0 ? void 0 : _c.id) || ((_d = item.group) === null || _d === void 0 ? void 0 : _d.id);
153
163
  const type = ((_e = item.resource) === null || _e === void 0 ? void 0 : _e.__typename) || ((_f = item.group) === null || _f === void 0 ? void 0 : _f.__typename);
154
- const label = `${name} (${type})`;
155
164
  return {
156
- name: label,
165
+ message: `${name} [${type}]`,
157
166
  value: {
158
- name: label,
159
- id: id,
167
+ name: name || "",
168
+ id: id || "",
169
+ type: ((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename) || "",
160
170
  },
161
171
  };
162
172
  });
@@ -182,7 +192,6 @@ const RESOURCE_ROLES_QUERY = (0, graphql_1.graphql)(`
182
192
  __typename
183
193
  ... on ResourceAccessLevelsResult {
184
194
  accessLevels {
185
- __typename
186
195
  ... on ResourceAccessLevel {
187
196
  accessLevelName
188
197
  accessLevelRemoteId
@@ -195,34 +204,82 @@ const RESOURCE_ROLES_QUERY = (0, graphql_1.graphql)(`
195
204
  }
196
205
  }
197
206
  `);
198
- async function queryResourceRoles(cmd, client, resourceId) {
199
- var _a, _b, _c, _d, _e;
207
+ const GROUP_ROLES_QUERY = (0, graphql_1.graphql)(`
208
+ query GroupAccessLevels($groupId: GroupId!) {
209
+ groupAccessLevels(
210
+ input: { groupId: $groupId }
211
+ ) {
212
+ ... on GroupAccessLevelsResult {
213
+ groupId
214
+ accessLevels {
215
+ ... on GroupAccessLevel {
216
+ accessLevelName
217
+ accessLevelRemoteId
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+ `);
224
+ async function queryAssetRoles(cmd, client, assetType, assetId) {
225
+ var _a, _b, _c, _d, _e, _f, _g, _h;
200
226
  try {
201
- const resp = await client.query({
202
- query: RESOURCE_ROLES_QUERY,
203
- variables: {
204
- resourceId: resourceId,
205
- },
206
- fetchPolicy: "network-only", // to avoid caching
207
- });
208
- // no fall through doesn't consider process.exit();
209
- let x;
210
- switch (resp.data.accessLevels.__typename) {
211
- case "ResourceAccessLevelsResult":
212
- return (_c = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.accessLevels) === null || _b === void 0 ? void 0 : _b.accessLevels) === null || _c === void 0 ? void 0 : _c.map((role) => {
213
- return {
214
- name: role.accessLevelName,
215
- value: {
216
- name: role.accessLevelName,
217
- id: role.accessLevelRemoteId,
218
- },
219
- };
227
+ switch (assetType) {
228
+ case "Resource": {
229
+ const resp = await client.query({
230
+ query: RESOURCE_ROLES_QUERY,
231
+ variables: {
232
+ resourceId: assetId,
233
+ },
234
+ fetchPolicy: "network-only", // to avoid caching
220
235
  });
221
- case "ResourceNotFoundError":
222
- x = cmd.error((_e = (_d = resp.data) === null || _d === void 0 ? void 0 : _d.accessLevels) === null || _e === void 0 ? void 0 : _e.message);
223
- break;
224
- default:
225
- cmd.error(resp.error || "Unknown error occurred.");
236
+ // no fall through doesn't consider process.exit();
237
+ let x;
238
+ switch (resp.data.accessLevels.__typename) {
239
+ case "ResourceAccessLevelsResult":
240
+ return (_c = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.accessLevels) === null || _b === void 0 ? void 0 : _b.accessLevels) === null || _c === void 0 ? void 0 : _c.map((role) => {
241
+ return {
242
+ message: role.accessLevelName || "",
243
+ value: {
244
+ name: role.accessLevelName || "",
245
+ id: role.accessLevelRemoteId || "",
246
+ },
247
+ };
248
+ });
249
+ case "ResourceNotFoundError":
250
+ x = cmd.error((_e = (_d = resp.data) === null || _d === void 0 ? void 0 : _d.accessLevels) === null || _e === void 0 ? void 0 : _e.message);
251
+ break;
252
+ default:
253
+ cmd.error(resp.error || "Unknown error occurred.");
254
+ }
255
+ return;
256
+ }
257
+ case "Group": {
258
+ const resp = await client.query({
259
+ query: GROUP_ROLES_QUERY,
260
+ variables: {
261
+ groupId: assetId,
262
+ },
263
+ fetchPolicy: "network-only", // to avoid caching
264
+ });
265
+ // no fall through doesn't consider process.exit();
266
+ let x;
267
+ switch (resp.data.groupAccessLevels.__typename) {
268
+ case "GroupAccessLevelsResult":
269
+ return (_h = (_g = (_f = resp.data) === null || _f === void 0 ? void 0 : _f.groupAccessLevels) === null || _g === void 0 ? void 0 : _g.accessLevels) === null || _h === void 0 ? void 0 : _h.map((role) => {
270
+ return {
271
+ message: role.accessLevelName,
272
+ value: {
273
+ name: role.accessLevelName,
274
+ id: role.accessLevelRemoteId,
275
+ },
276
+ };
277
+ });
278
+ default:
279
+ x = cmd.error(resp.error || "Unknown error occurred.");
280
+ }
281
+ return;
282
+ }
226
283
  }
227
284
  }
228
285
  catch (error) {
@@ -241,16 +298,18 @@ const REQUEST_DEFAULTS_QUERY = (0, graphql_1.graphql)(`
241
298
  requestedGroups: $requestedGroups,
242
299
  }
243
300
  ) {
244
- durationOptions {
245
- durationInMinutes
246
- label
301
+ ... on RequestDefaults {
302
+ durationOptions {
303
+ durationInMinutes
304
+ label
305
+ }
306
+ recommendedDurationInMinutes
307
+ defaultDurationInMinutes
308
+ maxDurationInMinutes
309
+ requireSupportTicket
310
+ reasonOptional
311
+ requesterIsAdmin
247
312
  }
248
- recommendedDurationInMinutes
249
- defaultDurationInMinutes
250
- maxDurationInMinutes
251
- requireSupportTicket
252
- reasonOptional
253
- requesterIsAdmin
254
313
  }
255
314
  }`);
256
315
  async function queryRequestDefaults(cmd, client, requestedResources, requestedGroups) {
@@ -271,20 +330,175 @@ async function queryRequestDefaults(cmd, client, requestedResources, requestedGr
271
330
  }
272
331
  }
273
332
  }
333
+ const CREATE_REQUEST_MUTATION = (0, graphql_1.graphql)(`
334
+ mutation CreateRequest(
335
+ $requestedResources: [RequestedResourceInput!]!
336
+ $requestedGroups: [RequestedGroupInput!]!
337
+ $reason: String!
338
+ $durationInMinutes: Int
339
+ ) {
340
+ createRequest(
341
+ input: {
342
+ requestedResources: $requestedResources
343
+ requestedGroups: $requestedGroups
344
+ reason: $reason
345
+ durationInMinutes: $durationInMinutes
346
+ }
347
+ ) {
348
+ ... on CreateRequestResult {
349
+ request {
350
+ id
351
+ status
352
+ }
353
+ }
354
+ ... on RequestDurationTooLargeError {
355
+ message
356
+ }
357
+ ... on RequestRequiresUserAuthTokenForConnectionError {
358
+ message
359
+ }
360
+ ... on NoReviewersSetForOwnerError {
361
+ message
362
+ ownerId
363
+ }
364
+ ... on NoReviewersSetForResourceError {
365
+ message
366
+ resourceId
367
+ }
368
+ ... on NoReviewersSetForGroupError {
369
+ message
370
+ groupId
371
+ }
372
+ ... on NoManagerSetForRequestingUserError {
373
+ message
374
+ }
375
+ ... on MfaInvalidError {
376
+ message
377
+ }
378
+ ... on BulkRequestTooLargeError {
379
+ message
380
+ }
381
+ ... on ItemCannotBeRequestedError {
382
+ message
383
+ }
384
+ ... on UserCannotRequestAccessForTargetGroupError {
385
+ message
386
+ groupId
387
+ userId
388
+ }
389
+ ... on GroupNestingNotAllowedError {
390
+ message
391
+ fromGroupId
392
+ toGroupId
393
+ }
394
+ ... on TargetUserHasNestedAccessError {
395
+ message
396
+ groupIds
397
+ }
398
+ ... on RequestReasonMissingError {
399
+ message
400
+ }
401
+ ... on RequestFieldValueMissingError {
402
+ message
403
+ fieldName
404
+ }
405
+ ... on LinkedGroupNotRequestableError {
406
+ message
407
+ sourceGroupId
408
+ groupBindingId
409
+ }
410
+ ... on RequestReasonBelowMinLengthError {
411
+ message
412
+ }
413
+
414
+ }
415
+ }
416
+ `);
417
+ async function createRequest(cmd, client, requestedResources, requestedGroups, reason, durationInMinutes) {
418
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
419
+ try {
420
+ const resp = await client.mutate({
421
+ mutation: CREATE_REQUEST_MUTATION,
422
+ variables: {
423
+ requestedResources: requestedResources,
424
+ requestedGroups: requestedGroups,
425
+ reason: reason,
426
+ durationInMinutes: durationInMinutes,
427
+ },
428
+ });
429
+ switch ((_a = resp.data) === null || _a === void 0 ? void 0 : _a.createRequest.__typename) {
430
+ case "CreateRequestResult":
431
+ return (_b = resp.data) === null || _b === void 0 ? void 0 : _b.createRequest.request;
432
+ case "RequestDurationTooLargeError":
433
+ cmd.log((_c = resp.data) === null || _c === void 0 ? void 0 : _c.createRequest.message);
434
+ break;
435
+ case "RequestRequiresUserAuthTokenForConnectionError":
436
+ cmd.log((_d = resp.data) === null || _d === void 0 ? void 0 : _d.createRequest.message);
437
+ break;
438
+ case "NoReviewersSetForOwnerError":
439
+ cmd.log((_e = resp.data) === null || _e === void 0 ? void 0 : _e.createRequest.message);
440
+ break;
441
+ case "NoReviewersSetForResourceError":
442
+ cmd.log((_f = resp.data) === null || _f === void 0 ? void 0 : _f.createRequest.message);
443
+ break;
444
+ case "NoReviewersSetForGroupError":
445
+ cmd.log((_g = resp.data) === null || _g === void 0 ? void 0 : _g.createRequest.message);
446
+ break;
447
+ case "NoManagerSetForRequestingUserError":
448
+ cmd.log((_h = resp.data) === null || _h === void 0 ? void 0 : _h.createRequest.message);
449
+ break;
450
+ case "MfaInvalidError":
451
+ cmd.log((_j = resp.data) === null || _j === void 0 ? void 0 : _j.createRequest.message);
452
+ break;
453
+ case "BulkRequestTooLargeError":
454
+ cmd.log((_k = resp.data) === null || _k === void 0 ? void 0 : _k.createRequest.message);
455
+ break;
456
+ case "ItemCannotBeRequestedError":
457
+ cmd.log((_l = resp.data) === null || _l === void 0 ? void 0 : _l.createRequest.message);
458
+ break;
459
+ case "UserCannotRequestAccessForTargetGroupError":
460
+ cmd.log((_m = resp.data) === null || _m === void 0 ? void 0 : _m.createRequest.message);
461
+ break;
462
+ case "GroupNestingNotAllowedError":
463
+ cmd.log((_o = resp.data) === null || _o === void 0 ? void 0 : _o.createRequest.message);
464
+ break;
465
+ case "TargetUserHasNestedAccessError":
466
+ cmd.log((_p = resp.data) === null || _p === void 0 ? void 0 : _p.createRequest.message);
467
+ break;
468
+ case "RequestReasonMissingError":
469
+ cmd.log((_q = resp.data) === null || _q === void 0 ? void 0 : _q.createRequest.message);
470
+ break;
471
+ case "RequestFieldValueMissingError":
472
+ cmd.log((_r = resp.data) === null || _r === void 0 ? void 0 : _r.createRequest.message);
473
+ break;
474
+ case "LinkedGroupNotRequestableError":
475
+ cmd.log((_s = resp.data) === null || _s === void 0 ? void 0 : _s.createRequest.message);
476
+ break;
477
+ case "RequestReasonBelowMinLengthError":
478
+ cmd.log((_t = resp.data) === null || _t === void 0 ? void 0 : _t.createRequest.message);
479
+ break;
480
+ }
481
+ }
482
+ catch (error) {
483
+ if (error instanceof Error || typeof error === "string") {
484
+ cmd.error(error);
485
+ }
486
+ }
487
+ }
274
488
  // Helper functions
275
489
  async function selectRequestableItems(cmd, client, requestMap) {
276
- const { App } = await inquirer.prompt([
277
- {
278
- name: "App",
279
- message: "Select an app:",
280
- type: "autocomplete",
281
- source: async (answers, input) => {
282
- var _a;
283
- return (_a = (await queryRequestableApps(cmd, client, input))) !== null && _a !== void 0 ? _a : [];
284
- },
285
- pageSize: 15,
490
+ const initialChoices = (await queryRequestableApps(cmd, client, "")) || [];
491
+ const appPrompt = new AutoComplete({
492
+ name: "App",
493
+ message: "Select an app:",
494
+ limit: 15,
495
+ choices: initialChoices,
496
+ async suggest(input) {
497
+ const filteredChoices = await queryRequestableApps(cmd, client, input || "");
498
+ return filteredChoices || initialChoices;
286
499
  },
287
- ]);
500
+ });
501
+ const App = await appPrompt.run();
288
502
  // Set the app in the requestMap and call choose assets step
289
503
  if (!(App.id in requestMap)) {
290
504
  requestMap[App.id] = {
@@ -295,13 +509,16 @@ async function selectRequestableItems(cmd, client, requestMap) {
295
509
  await chooseAssets(cmd, client, App.id, requestMap);
296
510
  }
297
511
  async function chooseAssets(cmd, client, appId, requestMap) {
298
- var _a;
299
- const { Assets } = await inquirer.prompt({
512
+ const initialChoices = (await queryRequestableAssets(cmd, client, appId, "")) || [];
513
+ const assetPrompt = new AutoComplete({
300
514
  name: "Assets",
301
- type: "checkbox",
302
- pageSize: 15,
303
- message: "Select one or more items:",
304
- choices: (_a = (await queryRequestableAssets(cmd, client, appId, undefined))) !== null && _a !== void 0 ? _a : [],
515
+ message: "Select one or more assets:",
516
+ limit: 15,
517
+ multiple: true,
518
+ async choices(input) {
519
+ const filteredChoices = await queryRequestableAssets(cmd, client, appId, input);
520
+ return filteredChoices || initialChoices;
521
+ },
305
522
  validate: (answer) => {
306
523
  if (answer.length < 1) {
307
524
  return "You must select at least one item.";
@@ -309,14 +526,16 @@ async function chooseAssets(cmd, client, appId, requestMap) {
309
526
  return true;
310
527
  },
311
528
  });
529
+ const Assets = await assetPrompt.run();
312
530
  const entry = requestMap[appId];
313
531
  for (const asset of Assets) {
314
532
  if (entry === undefined) {
315
- throw new Error(`App ${appId} not found in requestMap`);
533
+ throw new Error(`Error formatting app ${appId} in request`);
316
534
  }
317
535
  if (!(asset.id in entry.assets)) {
318
536
  entry.assets[asset.id] = {
319
537
  assetName: asset.name,
538
+ type: asset.type,
320
539
  roles: {},
321
540
  };
322
541
  }
@@ -325,29 +544,31 @@ async function chooseAssets(cmd, client, appId, requestMap) {
325
544
  }
326
545
  async function chooseRoles(cmd, client, appId, assetId, requestMap) {
327
546
  var _a;
328
- const resourceRoles = (_a = (await queryResourceRoles(cmd, client, assetId))) !== null && _a !== void 0 ? _a : [];
329
- if (resourceRoles !== undefined &&
330
- (resourceRoles.length === 0 ||
331
- (resourceRoles.length === 1 && resourceRoles[0].name === ""))) {
547
+ const entry = requestMap[appId];
548
+ const assetEntry = entry === null || entry === void 0 ? void 0 : entry.assets[assetId];
549
+ if (entry === undefined || assetEntry === undefined) {
550
+ throw new Error(`App ${appId} or Asset ${assetId} not found in requestMap`);
551
+ }
552
+ const assetRoles = (_a = (await queryAssetRoles(cmd, client, assetEntry.type, assetId))) !== null && _a !== void 0 ? _a : [];
553
+ if (assetRoles !== undefined &&
554
+ (assetRoles.length === 0 ||
555
+ (assetRoles.length === 1 && assetRoles[0].value.name === ""))) {
332
556
  return;
333
557
  }
334
- const { roles } = await inquirer.prompt({
335
- name: "roles",
336
- type: "checkbox",
337
- message: `Select one or more roles for ${assetId}:`,
338
- choices: resourceRoles,
558
+ const rolePrompt = new AutoComplete({
559
+ name: "Roles",
560
+ message: `Select one or more roles for ${assetEntry.assetName}:`,
561
+ limit: 15,
562
+ multiple: true,
563
+ choices: assetRoles,
339
564
  validate: (answer) => {
340
- if ((resourceRoles === null || resourceRoles === void 0 ? void 0 : resourceRoles.length) > 1 && answer.length < 1) {
341
- return "You must select at least one role.";
565
+ if (answer.length < 1) {
566
+ return "You must select at least one item.";
342
567
  }
343
568
  return true;
344
569
  },
345
570
  });
346
- const entry = requestMap[appId];
347
- const assetEntry = entry === null || entry === void 0 ? void 0 : entry.assets[assetId];
348
- if (entry === undefined || assetEntry === undefined) {
349
- throw new Error(`App ${appId} or Asset ${assetId} not found in requestMap`);
350
- }
571
+ const roles = await rolePrompt.run();
351
572
  if (!assetEntry.roles) {
352
573
  assetEntry.roles = {};
353
574
  }
@@ -360,14 +581,12 @@ async function chooseRoles(cmd, client, appId, assetId, requestMap) {
360
581
  async function doneSelectingAssets() {
361
582
  const submitMessage = "✅ Yes, proceed with request";
362
583
  const addMoreMessage = "❌ No, add more items";
363
- const { submitOrAdd } = await inquirer.prompt([
364
- {
365
- name: "submitOrAdd",
366
- message: "Is this all you want to request?",
367
- type: "list",
368
- choices: [submitMessage, addMoreMessage],
369
- },
370
- ]);
584
+ const prompt = new Select({
585
+ name: "submitOrAdd",
586
+ message: "Is this all you want to request?",
587
+ choices: [submitMessage, addMoreMessage],
588
+ });
589
+ const submitOrAdd = await prompt.run();
371
590
  return submitOrAdd === submitMessage;
372
591
  }
373
592
  async function setRequestDefaults(cmd, client, metadata) {
@@ -377,18 +596,34 @@ async function setRequestDefaults(cmd, client, metadata) {
377
596
  for (const appNode of Object.values(requestMap)) {
378
597
  for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
379
598
  if (assetNode.roles !== undefined) {
380
- for (const roleId of Object.keys(assetNode.roles)) {
381
- requestedResources.push({
382
- resourceId: assetId,
383
- accessLevelRemoteId: roleId,
384
- });
599
+ const mappedRoles = Object.entries(assetNode.roles).map(([roleId, _roleNode]) => {
600
+ return roleId;
601
+ });
602
+ const roleIds = mappedRoles.length ? mappedRoles : [""];
603
+ for (const roleId of roleIds) {
604
+ switch (assetNode.type) {
605
+ case "Resource": {
606
+ requestedResources.push({
607
+ resourceId: assetId,
608
+ accessLevelRemoteId: roleId,
609
+ });
610
+ break;
611
+ }
612
+ case "Group": {
613
+ requestedGroups.push({
614
+ groupId: assetId,
615
+ accessLevelRemoteId: roleId,
616
+ });
617
+ break;
618
+ }
619
+ }
385
620
  }
386
621
  }
387
622
  }
388
623
  }
389
624
  try {
390
625
  const requestDefaults = await queryRequestDefaults(cmd, client, requestedResources, requestedGroups);
391
- if ((requestDefaults === null || requestDefaults === void 0 ? void 0 : requestDefaults.__typename) === "RequestDefaults") {
626
+ if (requestDefaults !== undefined) {
392
627
  metadata.requestDefaults.durationOptions =
393
628
  requestDefaults.durationOptions;
394
629
  metadata.requestDefaults.recommendedDurationInMinutes =
@@ -409,49 +644,133 @@ async function setRequestDefaults(cmd, client, metadata) {
409
644
  }
410
645
  }
411
646
  async function promptForReason(metadata) {
412
- return await inquirer.prompt([
647
+ const { reason } = await prompt([
413
648
  {
414
649
  name: "reason",
415
650
  message: "I need access to this because...",
416
651
  type: "input",
417
652
  validate: (answer) => {
418
- if (metadata.requestDefaults.reasonOptional && answer.length < 1) {
653
+ if (!metadata.requestDefaults.reasonOptional && answer.length < 1) {
419
654
  return "A reason for requesting these assets is required.";
420
655
  }
421
656
  return true;
422
657
  },
423
658
  },
424
659
  ]);
660
+ metadata.reason = reason;
425
661
  }
426
662
  async function promptForExpiration(metadata) {
427
663
  var _a, _b;
428
664
  const durations = ((_b = (_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.durationOptions) === null || _b === void 0 ? void 0 : _b.map((option) => {
665
+ var _a;
666
+ let label = option.label;
667
+ if (option.durationInMinutes ===
668
+ ((_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.maxDurationInMinutes)) {
669
+ label = `${label} (MAX)`;
670
+ }
671
+ if (option.durationInMinutes ===
672
+ metadata.requestDefaults.recommendedDurationInMinutes) {
673
+ label = `${label} (RECOMMENDED)`;
674
+ }
429
675
  return {
430
- name: option.durationInMinutes ===
431
- metadata.requestDefaults.recommendedDurationInMinutes
432
- ? `${option.label} (Recommended)`
433
- : option.label,
676
+ message: label,
434
677
  value: {
435
- label: option.label,
678
+ label: label,
436
679
  durationInMinutes: option.durationInMinutes,
680
+ toString: () => label,
437
681
  },
438
682
  };
439
683
  })) || [];
440
- // TODO: Sort durations by minutes
441
- // durations = durations.sort(
442
- // durations.filter((option) => option.value.durationInMinutes),
443
- // );
444
- return await inquirer.prompt([
445
- {
446
- name: "expiration",
447
- message: "When should access expire?",
448
- type: "list",
449
- choices: durations,
450
- pageSize: 15,
684
+ // Sort durations by minutes
685
+ durations.sort((a, b) => a.value.durationInMinutes - b.value.durationInMinutes);
686
+ const expirationSelect = new AutoComplete({
687
+ name: "expiration",
688
+ message: "When should access expire?",
689
+ type: "list",
690
+ choices: durations,
691
+ pageSize: 15,
692
+ });
693
+ let selected = await expirationSelect.run();
694
+ switch (selected.label) {
695
+ case "Custom": {
696
+ selected = await setCustomDuration(metadata);
697
+ break;
698
+ }
699
+ case "Permanent": {
700
+ selected.durationInMinutes = undefined;
701
+ break;
702
+ }
703
+ }
704
+ metadata.durationInMinutes = selected.durationInMinutes;
705
+ metadata.durationLabel = selected.label;
706
+ }
707
+ function getDurationNumbers(duration) {
708
+ const d = +duration.days || 0;
709
+ const h = +duration.hours || 0;
710
+ const m = +duration.minutes || 0;
711
+ return { d, h, m };
712
+ }
713
+ function getDurationInMinutes(duration) {
714
+ const { d, h, m } = getDurationNumbers(duration);
715
+ const minutesInDay = 1440; // 24 hours * 60 minutes
716
+ const minutesInHour = 60;
717
+ return d * minutesInDay + h * minutesInHour + m;
718
+ }
719
+ function getDHMFromMinutes(minutes) {
720
+ const label = [];
721
+ const d = Math.floor(minutes / 1440);
722
+ if (d > 0) {
723
+ label.push(`${d}d`);
724
+ }
725
+ const remainingMinutes = minutes % 1440;
726
+ const h = Math.floor(remainingMinutes / 60);
727
+ if (h > 0) {
728
+ label.push(`${h}h`);
729
+ }
730
+ const m = remainingMinutes % 60;
731
+ if (m > 0) {
732
+ label.push(`${m}m`);
733
+ }
734
+ return label.join(" ");
735
+ }
736
+ async function setCustomDuration(metadata) {
737
+ const durationForm = new Form({
738
+ name: "user",
739
+ message: "Please set a custom access duration:",
740
+ choices: [
741
+ { name: "days", message: "Days", initial: "0" },
742
+ { name: "hours", message: "Hours", initial: "0" },
743
+ { name: "minutes", message: "Minutes", initial: "0" },
744
+ ],
745
+ validate: (answer) => {
746
+ var _a, _b, _c;
747
+ const { d, h, m } = getDurationNumbers(answer);
748
+ const durationInMinutes = getDurationInMinutes(answer);
749
+ if (d < 0 || h < 0 || m < 0 || d + h + m === 0 || (h > 23 && m > 59)) {
750
+ return "Please enter a valid duration.";
751
+ }
752
+ if (((_a = metadata.requestDefaults) === null || _a === void 0 ? void 0 : _a.maxDurationInMinutes) &&
753
+ durationInMinutes > ((_b = metadata.requestDefaults) === null || _b === void 0 ? void 0 : _b.maxDurationInMinutes)) {
754
+ const maxDHM = getDHMFromMinutes((_c = metadata.requestDefaults) === null || _c === void 0 ? void 0 : _c.maxDurationInMinutes);
755
+ return `The max duration for the selected assets is ${maxDHM}.`;
756
+ }
757
+ return true;
451
758
  },
452
- ]);
759
+ return: (answer) => {
760
+ return getDurationInMinutes(answer);
761
+ },
762
+ });
763
+ const durationResult = await durationForm.run();
764
+ const { d, h, m } = getDurationNumbers(durationResult);
765
+ const durationInMinutes = getDurationInMinutes(durationResult);
766
+ const durationLabel = getDHMFromMinutes(durationInMinutes);
767
+ return {
768
+ durationInMinutes: durationInMinutes,
769
+ label: durationLabel,
770
+ };
453
771
  }
454
- async function submitFinalRequest(cmd) {
772
+ async function submitFinalRequest(cmd, client, metadata) {
773
+ var _a, _b, _c, _d;
455
774
  const submitMessage = "✅ Yes, submit request";
456
775
  const cancelMessage = "❌ No, cancel request";
457
776
  const { submit } = await inquirer.prompt([
@@ -462,12 +781,65 @@ async function submitFinalRequest(cmd) {
462
781
  choices: [submitMessage, cancelMessage],
463
782
  },
464
783
  ]);
465
- if (submit === submitMessage) {
466
- const requestLink = "https://dev.opal.dev/requests/sent/05ca5d5f-ea60-4cdb-84e1-7e3c575b2b72"; //TODO: Replace with actual request link
467
- cmd.log("\n🎉 Your Access Request has been submitted! Request ID: 1234");
468
- cmd.log(`🔍 View request status here: ${requestLink}`);
469
- }
470
- else {
471
- cmd.log("🚫 Access Request has been cancelled.");
784
+ switch (submit) {
785
+ case submitMessage: {
786
+ // Build requested assets lists for the mutation
787
+ const requestedResources = [];
788
+ const requestedGroups = [];
789
+ for (const appNode of Object.values(metadata.requestMap)) {
790
+ for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
791
+ if (assetNode.roles !== undefined) {
792
+ const mappedRoles = Object.entries(assetNode.roles).map(([roleId, _roleNode]) => {
793
+ return roleId;
794
+ });
795
+ const roleIds = mappedRoles.length ? mappedRoles : [""];
796
+ for (const roleId of roleIds) {
797
+ switch (assetNode.type) {
798
+ case "Resource": {
799
+ requestedResources.push({
800
+ resourceId: assetId,
801
+ accessLevel: {
802
+ accessLevelName: ((_b = (_a = assetNode.roles) === null || _a === void 0 ? void 0 : _a[roleId]) === null || _b === void 0 ? void 0 : _b.roleName) || "",
803
+ accessLevelRemoteId: roleId,
804
+ },
805
+ });
806
+ break;
807
+ }
808
+ case "Group": {
809
+ requestedGroups.push({
810
+ groupId: assetId,
811
+ accessLevel: {
812
+ accessLevelName: ((_d = (_c = assetNode.roles) === null || _c === void 0 ? void 0 : _c[roleId]) === null || _d === void 0 ? void 0 : _d.roleName) || "",
813
+ accessLevelRemoteId: roleId,
814
+ },
815
+ });
816
+ break;
817
+ }
818
+ }
819
+ }
820
+ }
821
+ }
822
+ }
823
+ const resp = await createRequest(cmd, client, requestedResources, requestedGroups, metadata.reason, metadata.durationInMinutes);
824
+ // Build link to request
825
+ const configData = (0, config_1.getOrCreateConfigData)(cmd.config.configDir);
826
+ if (resp === null || resp === void 0 ? void 0 : resp.id) {
827
+ cmd.log("\n🎉 Your Access Request has been submitted!\n");
828
+ cmd.log(`${chalk_1.default.bold("ID: ")} ${chalk_1.default.cyan(resp === null || resp === void 0 ? void 0 : resp.id)}`);
829
+ if (resp === null || resp === void 0 ? void 0 : resp.status) {
830
+ cmd.log((0, displays_1.getStyledStatus)(resp === null || resp === void 0 ? void 0 : resp.status));
831
+ }
832
+ const requestLink = `${configData[config_1.urlKey]}/requests/sent/${resp === null || resp === void 0 ? void 0 : resp.id}`;
833
+ cmd.log(`${chalk_1.default.bold("Link:")} ${chalk_1.default.underline(requestLink)}\n`);
834
+ }
835
+ return;
836
+ }
837
+ case cancelMessage: {
838
+ cmd.log("🚫 Access Request has been cancelled.");
839
+ return;
840
+ }
841
+ default: {
842
+ cmd.error("Unknown error occurred.");
843
+ }
472
844
  }
473
845
  }