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,5 +1,10 @@
1
+ import type { ApolloQueryResult } from "@apollo/client";
1
2
  import type { Command } from "@oclif/core/lib/command";
2
- import type { RequestMap } from "../lib/requests";
3
+ import type { GetRequestQuery, GetRequestsQuery } from "../graphql/graphql";
4
+ import type { RequestMap, RequestMetadata } from "../lib/requests";
3
5
  export declare function headerMessage(cmd: Command): void;
4
- export declare function treeifyRequestMap(requestMap: RequestMap): string;
5
- export declare function displayFinalRequestSummary(cmd: Command, requestMap: RequestMap, reason: string, expiration: string): void;
6
+ export declare function treeifyRequestMap(cmd: Command, requestMap: RequestMap): void;
7
+ export declare function displayFinalRequestSummary(cmd: Command, metadata: RequestMetadata): void;
8
+ export declare function getStyledStatus(status: string): string;
9
+ export declare function displayRequestDetails(cmd: Command, requestResp: ApolloQueryResult<GetRequestQuery>): void;
10
+ export declare function displayRequestListTable(cmd: Command, requestResp: ApolloQueryResult<GetRequestsQuery>): void;
@@ -3,63 +3,211 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.headerMessage = headerMessage;
4
4
  exports.treeifyRequestMap = treeifyRequestMap;
5
5
  exports.displayFinalRequestSummary = displayFinalRequestSummary;
6
- const treeify = require("object-treeify");
6
+ exports.getStyledStatus = getStyledStatus;
7
+ exports.displayRequestDetails = displayRequestDetails;
8
+ exports.displayRequestListTable = displayRequestListTable;
9
+ const chalk_1 = require("chalk");
7
10
  const Table = require("cli-table3");
8
- const tableStyle = {
9
- top: "═",
10
- "top-mid": "╤",
11
- "top-left": "╔",
12
- "top-right": "╗",
13
- bottom: "═",
14
- "bottom-mid": "╧",
15
- "bottom-left": "╚",
16
- "bottom-right": "╝",
17
- left: "║",
18
- "left-mid": "╟",
19
- mid: "─",
20
- "mid-mid": "┼",
21
- right: "║",
22
- "right-mid": "╢",
23
- middle: "│",
24
- };
11
+ const treeify = require("object-treeify").default;
25
12
  function headerMessage(cmd) {
26
13
  console.clear();
27
- cmd.log("============================================================");
14
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
28
15
  cmd.log("Opal Access Request ✏️");
29
16
  cmd.log("Press Ctrl+C to cancel at any time.\n");
30
17
  }
31
- function treeifyRequestMap(requestMap) {
32
- const requestTree = {};
33
- // Create a tree structure from the requestMap
34
- // Iterate over apps
18
+ function treeifyRequestMap(cmd, requestMap) {
19
+ // Configuration options for treeify
20
+ const options = {
21
+ joined: true,
22
+ spacerNoNeighbour: " ",
23
+ spacerNeighbour: "│ ",
24
+ keyNoNeighbour: "└── ",
25
+ keyNeighbour: "├── ",
26
+ separator: "",
27
+ };
35
28
  for (const [_appId, appNode] of Object.entries(requestMap)) {
36
- const appKey = `🔧${appNode.appName}`;
37
- requestTree[appKey] = {}; // Initialize the app key
38
- // Iterate over assets
29
+ const assetsTree = {};
39
30
  for (const [_assetId, assetNode] of Object.entries(appNode.assets)) {
40
- const assetKey = `📦${assetNode.assetName}`;
31
+ const assetKey = `${assetNode.assetName} ${chalk_1.default.dim(`[${assetNode.type}]`)}`;
41
32
  if (assetNode.roles !== undefined) {
42
- // If no roles were previously selected
43
- requestTree[appKey][assetKey] = {}; // Initialize the asset key
44
- // Iterate over roles
33
+ assetsTree[assetKey] = {};
45
34
  for (const [_roleId, roleNode] of Object.entries(assetNode.roles)) {
46
- requestTree[appKey][assetKey][roleNode.roleName] = null; // Initialize the role key
35
+ const roleKey = `${roleNode.roleName} ${chalk_1.default.dim("[Role]")}`;
36
+ assetsTree[assetKey][roleKey] = null;
47
37
  }
48
38
  }
49
39
  else {
50
- requestTree[appKey][assetKey] = null;
40
+ assetsTree[assetKey] = null;
51
41
  }
52
42
  }
43
+ // Render tree for this app's assets
44
+ const assetsTreeString = treeify(assetsTree, options);
45
+ // Print App title first (without tree lines)
46
+ cmd.log(`${chalk_1.default.bold(appNode.appName)} ${chalk_1.default.dim("[App]")}`);
47
+ // Print its assets/roles indented underneath
48
+ cmd.log(assetsTreeString);
53
49
  }
54
- return String(treeify(requestTree));
50
+ cmd.log();
55
51
  }
56
- function displayFinalRequestSummary(cmd, requestMap, reason, expiration) {
57
- headerMessage(cmd);
58
- cmd.log("Final Summary of Request\n");
59
- const requestedAssets = treeifyRequestMap(requestMap);
60
- const table = new Table({
61
- chars: tableStyle,
62
- });
63
- table.push(["Requested Assets", requestedAssets], ["Reason", reason], ["Expiration", expiration]);
64
- cmd.log(table.toString());
52
+ function displayFinalRequestSummary(cmd, metadata) {
53
+ console.clear();
54
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
55
+ cmd.log("Final Summary of Request ✏️");
56
+ cmd.log("Press Ctrl+C to cancel at any time.\n");
57
+ cmd.log();
58
+ treeifyRequestMap(cmd, metadata.requestMap);
59
+ const durationInMinutes = metadata.durationInMinutes;
60
+ cmd.log(`Duration: ${durationInMinutes ? formatDuration(durationInMinutes) : "Permanent"}`);
61
+ const reason = metadata.reason;
62
+ if (reason) {
63
+ cmd.log(`Reason: "${chalk_1.default.italic(reason)}"`);
64
+ }
65
+ cmd.log();
66
+ }
67
+ function getStyledStatus(status) {
68
+ switch (status) {
69
+ case "PENDING": {
70
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.blueBright(status)}`;
71
+ }
72
+ case "APPROVED": {
73
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.greenBright(status)}`;
74
+ }
75
+ case "DENIED": {
76
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.redBright(status)}`;
77
+ }
78
+ case "CANCELED": {
79
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.redBright(status)}`;
80
+ }
81
+ default: {
82
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.gray(status)}`;
83
+ }
84
+ }
85
+ }
86
+ function displayRequestDetails(cmd, requestResp) {
87
+ var _a, _b, _c, _d, _e, _f;
88
+ switch (requestResp.data.request.__typename) {
89
+ case "RequestResult": {
90
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
91
+ cmd.log(`Request Details ${chalk_1.default.cyan(requestResp.data.request.request.id)}`);
92
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
93
+ const status = requestResp.data.request.request.status;
94
+ cmd.log(getStyledStatus(status));
95
+ // Request users "Requested by: <requester> -> Requested for: <targetUser>"
96
+ const requester = (_a = requestResp.data.request.request.requester) === null || _a === void 0 ? void 0 : _a.displayName;
97
+ const targetUser = (_b = requestResp.data.request.request.targetUser) === null || _b === void 0 ? void 0 : _b.displayName;
98
+ if (requester && targetUser) {
99
+ cmd.log(`${chalk_1.default.bold("Requested by:")} ${requester} ${chalk_1.default.gray("->")} ${chalk_1.default.bold("Requested for:")} ${targetUser}`);
100
+ }
101
+ const durationInMinutes = requestResp.data.request.request.durationInMinutes;
102
+ cmd.log(`${chalk_1.default.bold("Duration:")} ${durationInMinutes ? formatDuration(durationInMinutes) : "Permanent"}`);
103
+ const reason = requestResp.data.request.request.reason;
104
+ if (reason) {
105
+ cmd.log(`${chalk_1.default.bold("Reason:")} "${chalk_1.default.italic(reason)}"`);
106
+ }
107
+ // Requested resources
108
+ const requestedResources = (_d = (_c = requestResp.data.request.request.requestedResources) === null || _c === void 0 ? void 0 : _c.map((resource) => {
109
+ var _a, _b, _c;
110
+ if (((_a = resource.resource) === null || _a === void 0 ? void 0 : _a.__typename) === "Resource") {
111
+ return formatAssetName((_b = resource.resource) === null || _b === void 0 ? void 0 : _b.displayName, ((_c = resource.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
112
+ }
113
+ })) !== null && _d !== void 0 ? _d : [];
114
+ const requestedGroups = (_f = (_e = requestResp.data.request.request.requestedGroups) === null || _e === void 0 ? void 0 : _e.map((group) => {
115
+ var _a, _b, _c;
116
+ if (((_a = group.group) === null || _a === void 0 ? void 0 : _a.__typename) === "Group") {
117
+ return formatAssetName((_b = group.group) === null || _b === void 0 ? void 0 : _b.name, ((_c = group.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
118
+ }
119
+ })) !== null && _f !== void 0 ? _f : [];
120
+ const requestedItems = [...requestedResources, ...requestedGroups].join(", ");
121
+ if (requestedItems) {
122
+ cmd.log(`${chalk_1.default.bold("Requested Items:")} ${chalk_1.default.cyan(requestedItems)}`);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ function displayRequestListTable(cmd, requestResp) {
128
+ var _a, _b, _c, _d, _e;
129
+ switch (requestResp.data.requests.__typename) {
130
+ case "RequestsResult": {
131
+ const requests = requestResp.data.requests.requests;
132
+ if (requests && requests.length > 0) {
133
+ const table = new Table({
134
+ head: [
135
+ "Request ID",
136
+ "Status",
137
+ "For",
138
+ "Duration",
139
+ "Requested Items",
140
+ "Reason",
141
+ ],
142
+ colWidths: [null, null, 20, null, 30, 20],
143
+ wordWrap: true,
144
+ wrapOnWordBoundary: false,
145
+ });
146
+ for (const request of requests) {
147
+ const targetUser = (_a = request.targetUser) === null || _a === void 0 ? void 0 : _a.displayName;
148
+ const reason = request.reason;
149
+ const status = request.status;
150
+ const formattedDuration = request.durationInMinutes
151
+ ? formatDuration(request.durationInMinutes)
152
+ : "Permanent";
153
+ const requestedResources = (_c = (_b = request.requestedResources) === null || _b === void 0 ? void 0 : _b.map((resource) => {
154
+ var _a, _b, _c;
155
+ if (((_a = resource.resource) === null || _a === void 0 ? void 0 : _a.__typename) === "Resource") {
156
+ return formatAssetName((_b = resource.resource) === null || _b === void 0 ? void 0 : _b.displayName, ((_c = resource.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
157
+ }
158
+ })) !== null && _c !== void 0 ? _c : [];
159
+ const requestedGroups = (_e = (_d = request.requestedGroups) === null || _d === void 0 ? void 0 : _d.map((group) => {
160
+ var _a, _b, _c;
161
+ if (((_a = group.group) === null || _a === void 0 ? void 0 : _a.__typename) === "Group") {
162
+ return formatAssetName((_b = group.group) === null || _b === void 0 ? void 0 : _b.name, ((_c = group.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
163
+ }
164
+ })) !== null && _e !== void 0 ? _e : [];
165
+ const requestedItems = [
166
+ ...requestedResources,
167
+ ...requestedGroups,
168
+ ].join(", ");
169
+ table.push([
170
+ request.id,
171
+ status,
172
+ targetUser,
173
+ formattedDuration,
174
+ requestedItems,
175
+ reason,
176
+ ]);
177
+ }
178
+ cmd.log(table.toString());
179
+ }
180
+ else {
181
+ cmd.log("No requests found.");
182
+ }
183
+ return;
184
+ }
185
+ default: {
186
+ cmd.log("No requests found.");
187
+ }
188
+ }
189
+ }
190
+ function formatDuration(durationInMinutes) {
191
+ const days = Math.floor(durationInMinutes / 1440);
192
+ const remainingMinutes = durationInMinutes % 1440;
193
+ const hours = Math.floor(remainingMinutes / 60);
194
+ const minutes = remainingMinutes % 60;
195
+ let durationStr = "";
196
+ if (days > 0)
197
+ durationStr += `${days}d`;
198
+ if (hours > 0)
199
+ durationStr += `${hours}h`;
200
+ if (minutes > 0)
201
+ durationStr += `${minutes}m`;
202
+ if (durationStr === "")
203
+ durationStr = `${durationInMinutes}m`;
204
+ durationStr += ` (${durationInMinutes}m)`;
205
+ return durationStr;
206
+ }
207
+ function formatAssetName(assetName, roleName) {
208
+ let str = `${assetName}`;
209
+ if (roleName) {
210
+ str += ` (${roleName})`;
211
+ }
212
+ return str;
65
213
  }
@@ -594,10 +594,50 @@
594
594
  "start.js"
595
595
  ]
596
596
  },
597
+ "resources:get": {
598
+ "aliases": [],
599
+ "args": {},
600
+ "description": "Get resource info for a particular resource.",
601
+ "examples": [
602
+ "opal resources:get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4"
603
+ ],
604
+ "flags": {
605
+ "help": {
606
+ "char": "h",
607
+ "description": "Show CLI help.",
608
+ "name": "help",
609
+ "allowNo": false,
610
+ "type": "boolean"
611
+ },
612
+ "id": {
613
+ "char": "i",
614
+ "description": "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
615
+ "name": "id",
616
+ "hasDynamicHelp": false,
617
+ "multiple": false,
618
+ "type": "option"
619
+ }
620
+ },
621
+ "hasDynamicHelp": false,
622
+ "hiddenAliases": [],
623
+ "id": "resources:get",
624
+ "pluginAlias": "opal-security",
625
+ "pluginName": "opal-security",
626
+ "pluginType": "core",
627
+ "strict": true,
628
+ "enableJsonFlag": false,
629
+ "isESM": false,
630
+ "relativePath": [
631
+ "lib",
632
+ "commands",
633
+ "resources",
634
+ "get.js"
635
+ ]
636
+ },
597
637
  "request:create": {
598
638
  "aliases": [],
599
639
  "args": {},
600
- "description": "Opens an Opal access request",
640
+ "description": "Creates an Opal access request via an interactive form",
601
641
  "flags": {},
602
642
  "hasDynamicHelp": false,
603
643
  "hidden": true,
@@ -620,6 +660,10 @@
620
660
  "aliases": [],
621
661
  "args": {},
622
662
  "description": "Lists access requests",
663
+ "examples": [
664
+ "opal request get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4",
665
+ "opal request get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4 --verbose"
666
+ ],
623
667
  "flags": {
624
668
  "help": {
625
669
  "char": "h",
@@ -635,6 +679,13 @@
635
679
  "hasDynamicHelp": false,
636
680
  "multiple": false,
637
681
  "type": "option"
682
+ },
683
+ "verbose": {
684
+ "char": "v",
685
+ "description": "Enable verbose output, prints full response in JSON format. Defaults to false.",
686
+ "name": "verbose",
687
+ "allowNo": false,
688
+ "type": "boolean"
638
689
  }
639
690
  },
640
691
  "hasDynamicHelp": false,
@@ -657,31 +708,12 @@
657
708
  "request:list": {
658
709
  "aliases": [],
659
710
  "args": {},
660
- "description": "Lists access requests",
661
- "flags": {},
662
- "hasDynamicHelp": false,
663
- "hidden": true,
664
- "hiddenAliases": [],
665
- "id": "request:list",
666
- "pluginAlias": "opal-security",
667
- "pluginName": "opal-security",
668
- "pluginType": "core",
669
- "strict": true,
670
- "enableJsonFlag": false,
671
- "isESM": false,
672
- "relativePath": [
673
- "lib",
674
- "commands",
675
- "request",
676
- "list.js"
677
- ]
678
- },
679
- "resources:get": {
680
- "aliases": [],
681
- "args": {},
682
- "description": "Get resource info for a particular resource.",
711
+ "description": "Lists your n recent outgoing access requests",
683
712
  "examples": [
684
- "opal resources:get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4"
713
+ "opal request list --n 5",
714
+ "opal request list --n 5 --pending",
715
+ "opal request list --n 5 --verbose",
716
+ "opal request list --n 5 --pending --verbose"
685
717
  ],
686
718
  "flags": {
687
719
  "help": {
@@ -691,18 +723,34 @@
691
723
  "allowNo": false,
692
724
  "type": "boolean"
693
725
  },
694
- "id": {
695
- "char": "i",
696
- "description": "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
697
- "name": "id",
726
+ "n": {
727
+ "char": "n",
728
+ "description": "Defines number of requests to be returned. 1 <= n <= 100.",
729
+ "name": "n",
730
+ "default": 10,
698
731
  "hasDynamicHelp": false,
699
732
  "multiple": false,
700
733
  "type": "option"
734
+ },
735
+ "pending": {
736
+ "char": "p",
737
+ "description": "Show only pending requests. Defaults to false.",
738
+ "name": "pending",
739
+ "allowNo": false,
740
+ "type": "boolean"
741
+ },
742
+ "verbose": {
743
+ "char": "v",
744
+ "description": "Enable verbose output, prints full response in JSON format. Defaults to false.",
745
+ "name": "verbose",
746
+ "allowNo": false,
747
+ "type": "boolean"
701
748
  }
702
749
  },
703
750
  "hasDynamicHelp": false,
751
+ "hidden": true,
704
752
  "hiddenAliases": [],
705
- "id": "resources:get",
753
+ "id": "request:list",
706
754
  "pluginAlias": "opal-security",
707
755
  "pluginName": "opal-security",
708
756
  "pluginType": "core",
@@ -712,8 +760,8 @@
712
760
  "relativePath": [
713
761
  "lib",
714
762
  "commands",
715
- "resources",
716
- "get.js"
763
+ "request",
764
+ "list.js"
717
765
  ]
718
766
  },
719
767
  "ssh:copyFrom": {
@@ -923,5 +971,5 @@
923
971
  ]
924
972
  }
925
973
  },
926
- "version": "3.1.1-beta.e5e99da"
974
+ "version": "3.1.1-beta.e92fdf8"
927
975
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opal-security",
3
3
  "description": "Opal allows you to centrally manage access to all of your sensitive systems.",
4
- "version": "3.1.1-beta.e5e99da",
4
+ "version": "3.1.1-beta.e92fdf8",
5
5
  "author": "Stephen Cobbe",
6
6
  "bin": {
7
7
  "opal": "./bin/run"
@@ -16,6 +16,7 @@
16
16
  "argon2": "^0.40.1",
17
17
  "chalk": "^2.4.2",
18
18
  "cli-table3": "^0.6.5",
19
+ "enquirer": "^2.4.1",
19
20
  "graphql": "^15.5.0",
20
21
  "inquirer": "^8.2.6",
21
22
  "inquirer-autocomplete-prompt": "^2.0.1",
@@ -23,6 +24,7 @@
23
24
  "lodash": "^4.17.21",
24
25
  "moment": "^2.30.1",
25
26
  "node-fetch": "^2.6.7",
27
+ "object-treeify": "^5.0.1",
26
28
  "open": "^8.0.4",
27
29
  "openid-client": "^5.6.5",
28
30
  "prettyjson": "^1.2.1",
@@ -39,6 +41,7 @@
39
41
  "@types/lodash": "^4.14.169",
40
42
  "@types/node": "^22.14.0",
41
43
  "@types/prettyjson": "0.0.29",
44
+ "@types/react": "^19.1.4",
42
45
  "@types/semver": "^7.3.8",
43
46
  "better-npm-audit": "^3.7.3",
44
47
  "get-graphql-schema": "^2.1.2",