opal-security 3.1.0 → 3.1.1-beta.16f5ed5

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,205 @@ 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
35
- for (const [_appId, appNode] of requestMap.entries()) {
36
- const appKey = `🔧${appNode.appName}`;
37
- requestTree[appKey] = {}; // Initialize the app key
38
- // Iterate over assets
39
- for (const [_assetId, assetNode] of appNode.assets.entries()) {
40
- const assetKey = `📦${assetNode.assetName}`;
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
+ };
28
+ for (const [_appId, appNode] of Object.entries(requestMap)) {
29
+ const assetsTree = {};
30
+ for (const [_assetId, assetNode] of Object.entries(appNode.assets)) {
31
+ const assetKey = `${assetNode.assetName} [${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
45
- for (const [roleName, _] of assetNode.roles.entries()) {
46
- requestTree[appKey][assetKey][roleName] = null; // Initialize the role key
33
+ assetsTree[assetKey] = {};
34
+ for (const [_roleId, roleNode] of Object.entries(assetNode.roles)) {
35
+ const roleKey = `${roleNode.roleName} [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)} [App]`);
47
+ // Print its assets/roles indented underneath
48
+ cmd.log(assetsTreeString);
53
49
  }
54
- return String(treeify(requestTree));
50
+ cmd.log("\n");
55
51
  }
56
- function displayFinalRequestSummary(cmd, requestMap, reason, expiration) {
52
+ function displayFinalRequestSummary(cmd, metadata) {
57
53
  headerMessage(cmd);
58
54
  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]);
55
+ const requestedAssets = treeifyRequestMap(cmd, metadata.requestMap);
56
+ const table = new Table();
57
+ table.push(["Requested Assets", requestedAssets], ["Reason", metadata.reason], ["Expiration", metadata.durationLabel]);
64
58
  cmd.log(table.toString());
65
59
  }
60
+ function getStyledStatus(status) {
61
+ switch (status) {
62
+ case "PENDING": {
63
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.blueBright(status)}`;
64
+ }
65
+ case "APPROVED": {
66
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.greenBright(status)}`;
67
+ }
68
+ case "DENIED": {
69
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.redBright(status)}`;
70
+ }
71
+ case "CANCELED": {
72
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.redBright(status)}`;
73
+ }
74
+ default: {
75
+ return `${chalk_1.default.bold("Status:")} ${chalk_1.default.gray(status)}`;
76
+ }
77
+ }
78
+ }
79
+ function displayRequestDetails(cmd, requestResp) {
80
+ var _a, _b, _c, _d, _e, _f;
81
+ switch (requestResp.data.request.__typename) {
82
+ case "RequestResult": {
83
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
84
+ cmd.log(`Request Details ${chalk_1.default.cyan(requestResp.data.request.request.id)}`);
85
+ cmd.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
86
+ const status = requestResp.data.request.request.status;
87
+ cmd.log(getStyledStatus(status));
88
+ // Request users "Requested by: <requester> -> Requested for: <targetUser>"
89
+ const requester = (_a = requestResp.data.request.request.requester) === null || _a === void 0 ? void 0 : _a.displayName;
90
+ const targetUser = (_b = requestResp.data.request.request.targetUser) === null || _b === void 0 ? void 0 : _b.displayName;
91
+ if (requester && targetUser) {
92
+ cmd.log(`${chalk_1.default.bold("Requested by:")} ${requester} ${chalk_1.default.gray("->")} ${chalk_1.default.bold("Requested for:")} ${targetUser}`);
93
+ }
94
+ const durationInMinutes = requestResp.data.request.request.durationInMinutes;
95
+ cmd.log(`${chalk_1.default.bold("Duration:")} ${durationInMinutes ? formatDuration(durationInMinutes) : "Permanent"}`);
96
+ const reason = requestResp.data.request.request.reason;
97
+ if (reason) {
98
+ cmd.log(`${chalk_1.default.bold("Reason:")} "${chalk_1.default.italic(reason)}"`);
99
+ }
100
+ // Requested resources
101
+ const requestedResources = (_d = (_c = requestResp.data.request.request.requestedResources) === null || _c === void 0 ? void 0 : _c.map((resource) => {
102
+ var _a, _b, _c;
103
+ if (((_a = resource.resource) === null || _a === void 0 ? void 0 : _a.__typename) === "Resource") {
104
+ return formatAssetName((_b = resource.resource) === null || _b === void 0 ? void 0 : _b.displayName, ((_c = resource.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
105
+ }
106
+ })) !== null && _d !== void 0 ? _d : [];
107
+ const requestedGroups = (_f = (_e = requestResp.data.request.request.requestedGroups) === null || _e === void 0 ? void 0 : _e.map((group) => {
108
+ var _a, _b, _c;
109
+ if (((_a = group.group) === null || _a === void 0 ? void 0 : _a.__typename) === "Group") {
110
+ return formatAssetName((_b = group.group) === null || _b === void 0 ? void 0 : _b.name, ((_c = group.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
111
+ }
112
+ })) !== null && _f !== void 0 ? _f : [];
113
+ const requestedItems = [...requestedResources, ...requestedGroups].join(", ");
114
+ if (requestedItems) {
115
+ cmd.log(`${chalk_1.default.bold("Requested Items:")} ${chalk_1.default.cyan(requestedItems)}`);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ function displayRequestListTable(cmd, requestResp) {
121
+ var _a, _b, _c, _d, _e;
122
+ switch (requestResp.data.requests.__typename) {
123
+ case "RequestsResult": {
124
+ const requests = requestResp.data.requests.requests;
125
+ if (requests && requests.length > 0) {
126
+ const table = new Table({
127
+ head: [
128
+ "Request ID",
129
+ "Status",
130
+ "For",
131
+ "Duration",
132
+ "Requested Items",
133
+ "Reason",
134
+ ],
135
+ colWidths: [null, null, 20, null, 30, 20],
136
+ wordWrap: true,
137
+ wrapOnWordBoundary: false,
138
+ });
139
+ for (const request of requests) {
140
+ const targetUser = (_a = request.targetUser) === null || _a === void 0 ? void 0 : _a.displayName;
141
+ const reason = request.reason;
142
+ const status = request.status;
143
+ let formattedDuration = "";
144
+ if (request.durationInMinutes) {
145
+ formattedDuration = formatDuration(request.durationInMinutes);
146
+ }
147
+ const requestedResources = (_c = (_b = request.requestedResources) === null || _b === void 0 ? void 0 : _b.map((resource) => {
148
+ var _a, _b, _c;
149
+ if (((_a = resource.resource) === null || _a === void 0 ? void 0 : _a.__typename) === "Resource") {
150
+ return formatAssetName((_b = resource.resource) === null || _b === void 0 ? void 0 : _b.displayName, ((_c = resource.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
151
+ }
152
+ })) !== null && _c !== void 0 ? _c : [];
153
+ const requestedGroups = (_e = (_d = request.requestedGroups) === null || _d === void 0 ? void 0 : _d.map((group) => {
154
+ var _a, _b, _c;
155
+ if (((_a = group.group) === null || _a === void 0 ? void 0 : _a.__typename) === "Group") {
156
+ return formatAssetName((_b = group.group) === null || _b === void 0 ? void 0 : _b.name, ((_c = group.accessLevel) === null || _c === void 0 ? void 0 : _c.accessLevelName) || "");
157
+ }
158
+ })) !== null && _e !== void 0 ? _e : [];
159
+ const requestedItems = [
160
+ ...requestedResources,
161
+ ...requestedGroups,
162
+ ].join(", ");
163
+ table.push([
164
+ request.id,
165
+ status,
166
+ targetUser,
167
+ formattedDuration,
168
+ requestedItems,
169
+ reason,
170
+ ]);
171
+ }
172
+ cmd.log(table.toString());
173
+ }
174
+ else {
175
+ cmd.log("No requests found.");
176
+ }
177
+ return;
178
+ }
179
+ default: {
180
+ cmd.log("No requests found.");
181
+ }
182
+ }
183
+ }
184
+ function formatDuration(durationInMinutes) {
185
+ const days = Math.floor(durationInMinutes / 1440);
186
+ const remainingMinutes = durationInMinutes % 1440;
187
+ const hours = Math.floor(remainingMinutes / 60);
188
+ const minutes = remainingMinutes % 60;
189
+ let durationStr = "";
190
+ if (days > 0)
191
+ durationStr += `${days}d`;
192
+ if (hours > 0)
193
+ durationStr += `${hours}h`;
194
+ if (minutes > 0)
195
+ durationStr += `${minutes}m`;
196
+ if (durationStr === "")
197
+ durationStr = `${durationInMinutes}m`;
198
+ durationStr += ` (${durationInMinutes}m)`;
199
+ return durationStr;
200
+ }
201
+ function formatAssetName(assetName, roleName) {
202
+ let str = `${assetName}`;
203
+ if (roleName) {
204
+ str += ` (${roleName})`;
205
+ }
206
+ return str;
207
+ }
@@ -597,7 +597,7 @@
597
597
  "request:create": {
598
598
  "aliases": [],
599
599
  "args": {},
600
- "description": "Opens an Opal access request",
600
+ "description": "Creates an Opal access request via an interactive form",
601
601
  "flags": {},
602
602
  "hasDynamicHelp": false,
603
603
  "hidden": true,
@@ -620,7 +620,34 @@
620
620
  "aliases": [],
621
621
  "args": {},
622
622
  "description": "Lists access requests",
623
- "flags": {},
623
+ "examples": [
624
+ "opal request get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4",
625
+ "opal request get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4 --verbose"
626
+ ],
627
+ "flags": {
628
+ "help": {
629
+ "char": "h",
630
+ "description": "Show CLI help.",
631
+ "name": "help",
632
+ "allowNo": false,
633
+ "type": "boolean"
634
+ },
635
+ "id": {
636
+ "char": "i",
637
+ "description": "The Opal ID of the resource. You can find this from the URL, e.g. https://opal.dev/resources/[ID]",
638
+ "name": "id",
639
+ "hasDynamicHelp": false,
640
+ "multiple": false,
641
+ "type": "option"
642
+ },
643
+ "verbose": {
644
+ "char": "v",
645
+ "description": "Enable verbose output, prints full response in JSON format. Defaults to false.",
646
+ "name": "verbose",
647
+ "allowNo": false,
648
+ "type": "boolean"
649
+ }
650
+ },
624
651
  "hasDynamicHelp": false,
625
652
  "hidden": true,
626
653
  "hiddenAliases": [],
@@ -639,12 +666,47 @@
639
666
  ]
640
667
  },
641
668
  "request:list": {
642
- "aliases": [
643
- "request:ls"
644
- ],
669
+ "aliases": [],
645
670
  "args": {},
646
- "description": "Lists access requests",
647
- "flags": {},
671
+ "description": "Lists your n recent outgoing access requests",
672
+ "examples": [
673
+ "opal request list --n 5",
674
+ "opal request list --n 5 --pending",
675
+ "opal request list --n 5 --verbose",
676
+ "opal request list --n 5 --pending --verbose"
677
+ ],
678
+ "flags": {
679
+ "help": {
680
+ "char": "h",
681
+ "description": "Show CLI help.",
682
+ "name": "help",
683
+ "allowNo": false,
684
+ "type": "boolean"
685
+ },
686
+ "n": {
687
+ "char": "n",
688
+ "description": "Defines number of requests to be returned. 1 <= n <= 100.",
689
+ "name": "n",
690
+ "default": 10,
691
+ "hasDynamicHelp": false,
692
+ "multiple": false,
693
+ "type": "option"
694
+ },
695
+ "pending": {
696
+ "char": "p",
697
+ "description": "Show only pending requests. Defaults to false.",
698
+ "name": "pending",
699
+ "allowNo": false,
700
+ "type": "boolean"
701
+ },
702
+ "verbose": {
703
+ "char": "v",
704
+ "description": "Enable verbose output, prints full response in JSON format. Defaults to false.",
705
+ "name": "verbose",
706
+ "allowNo": false,
707
+ "type": "boolean"
708
+ }
709
+ },
648
710
  "hasDynamicHelp": false,
649
711
  "hidden": true,
650
712
  "hiddenAliases": [],
@@ -909,5 +971,5 @@
909
971
  ]
910
972
  }
911
973
  },
912
- "version": "3.1.0"
974
+ "version": "3.1.1-beta.16f5ed5"
913
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.0",
4
+ "version": "3.1.1-beta.16f5ed5",
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",