opal-security 3.1.1-beta.50f7f9f → 3.1.1-beta.5457bdf

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,7 @@ $ npm install -g opal-security
22
22
  $ opal COMMAND
23
23
  running command...
24
24
  $ opal (--version)
25
- opal-security/3.1.1-beta.50f7f9f linux-x64 node-v20.19.2
25
+ opal-security/3.1.1-beta.5457bdf linux-x64 node-v20.19.2
26
26
  $ opal --help [COMMAND]
27
27
  USAGE
28
28
  $ opal COMMAND
@@ -53,6 +53,7 @@ USAGE
53
53
  * [`opal ssh copyTo`](#opal-ssh-copyto)
54
54
  * [`opal ssh start`](#opal-ssh-start)
55
55
  * [`opal version`](#opal-version)
56
+ * [`opal whoami`](#opal-whoami)
56
57
 
57
58
  ## `opal autocomplete [SHELL]`
58
59
 
@@ -101,7 +102,7 @@ EXAMPLES
101
102
  $ opal aws:identity
102
103
  ```
103
104
 
104
- _See code: [src/commands/aws/identity.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/aws/identity.ts)_
105
+ _See code: [src/commands/aws/identity.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/aws/identity.ts)_
105
106
 
106
107
  ## `opal clear-auth-provider`
107
108
 
@@ -121,7 +122,7 @@ EXAMPLES
121
122
  $ opal clear-auth-provider
122
123
  ```
123
124
 
124
- _See code: [src/commands/clear-auth-provider.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/clear-auth-provider.ts)_
125
+ _See code: [src/commands/clear-auth-provider.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/clear-auth-provider.ts)_
125
126
 
126
127
  ## `opal curl-example`
127
128
 
@@ -138,7 +139,7 @@ DESCRIPTION
138
139
  Prints out an example cURL command containing the parameters the CLI uses to query the Opal server.
139
140
  ```
140
141
 
141
- _See code: [src/commands/curl-example.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/curl-example.ts)_
142
+ _See code: [src/commands/curl-example.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/curl-example.ts)_
142
143
 
143
144
  ## `opal groups get`
144
145
 
@@ -159,7 +160,7 @@ EXAMPLES
159
160
  $ opal groups:get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4
160
161
  ```
161
162
 
162
- _See code: [src/commands/groups/get.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/groups/get.ts)_
163
+ _See code: [src/commands/groups/get.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/groups/get.ts)_
163
164
 
164
165
  ## `opal help [COMMANDS]`
165
166
 
@@ -209,7 +210,7 @@ EXAMPLES
209
210
  $ opal iam-roles:start --id 51f7176b-0464-4a6f-8369-e951e187b398 --profileName "custom-profile"
210
211
  ```
211
212
 
212
- _See code: [src/commands/iam-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/iam-roles/start.ts)_
213
+ _See code: [src/commands/iam-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/iam-roles/start.ts)_
213
214
 
214
215
  ## `opal kube-roles start`
215
216
 
@@ -240,7 +241,7 @@ EXAMPLES
240
241
  $ opal kube-roles:start --id 51f7176b-0464-4a6f-8369-e951e187b398 --accessLevelRemoteId "arn:aws:iam::712234975475:role/acme-eks-cluster-admin-role"
241
242
  ```
242
243
 
243
- _See code: [src/commands/kube-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/kube-roles/start.ts)_
244
+ _See code: [src/commands/kube-roles/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/kube-roles/start.ts)_
244
245
 
245
246
  ## `opal login`
246
247
 
@@ -261,7 +262,7 @@ EXAMPLES
261
262
  $ opal login
262
263
  ```
263
264
 
264
- _See code: [src/commands/login.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/login.ts)_
265
+ _See code: [src/commands/login.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/login.ts)_
265
266
 
266
267
  ## `opal logout`
267
268
 
@@ -281,7 +282,7 @@ EXAMPLES
281
282
  $ opal logout
282
283
  ```
283
284
 
284
- _See code: [src/commands/logout.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/logout.ts)_
285
+ _See code: [src/commands/logout.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/logout.ts)_
285
286
 
286
287
  ## `opal postgres-instances start`
287
288
 
@@ -318,7 +319,7 @@ EXAMPLES
318
319
  $ opal postgres-instances:start --id 51f7176b-0464-4a6f-8369-e951e187b398 --accessLevelRemoteId fullaccess --action view
319
320
  ```
320
321
 
321
- _See code: [src/commands/postgres-instances/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/postgres-instances/start.ts)_
322
+ _See code: [src/commands/postgres-instances/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/postgres-instances/start.ts)_
322
323
 
323
324
  ## `opal resources get`
324
325
 
@@ -339,7 +340,7 @@ EXAMPLES
339
340
  $ opal resources:get --id 54052a3e-5375-4392-aeaf-0c6c44c131d4
340
341
  ```
341
342
 
342
- _See code: [src/commands/resources/get.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/resources/get.ts)_
343
+ _See code: [src/commands/resources/get.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/resources/get.ts)_
343
344
 
344
345
  ## `opal set-auth-provider`
345
346
 
@@ -365,7 +366,7 @@ EXAMPLES
365
366
  $ opal set-auth-provider --clientID 1234asdf --issuerUrl https://auth.example.com
366
367
  ```
367
368
 
368
- _See code: [src/commands/set-auth-provider.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/set-auth-provider.ts)_
369
+ _See code: [src/commands/set-auth-provider.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/set-auth-provider.ts)_
369
370
 
370
371
  ## `opal set-custom-header`
371
372
 
@@ -386,7 +387,7 @@ EXAMPLES
386
387
  $ opal set-custom-header --header 'cf-access-token: $TOKEN'
387
388
  ```
388
389
 
389
- _See code: [src/commands/set-custom-header.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/set-custom-header.ts)_
390
+ _See code: [src/commands/set-custom-header.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/set-custom-header.ts)_
390
391
 
391
392
  ## `opal set-token`
392
393
 
@@ -406,7 +407,7 @@ EXAMPLES
406
407
  $ opal set-token
407
408
  ```
408
409
 
409
- _See code: [src/commands/set-token.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/set-token.ts)_
410
+ _See code: [src/commands/set-token.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/set-token.ts)_
410
411
 
411
412
  ## `opal set-url [URL]`
412
413
 
@@ -430,7 +431,7 @@ EXAMPLES
430
431
  $ opal set-url
431
432
  ```
432
433
 
433
- _See code: [src/commands/set-url.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/set-url.ts)_
434
+ _See code: [src/commands/set-url.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/set-url.ts)_
434
435
 
435
436
  ## `opal ssh copyFrom`
436
437
 
@@ -461,7 +462,7 @@ EXAMPLES
461
462
  $ opal ssh:copyFrom --src instance/dir --dest my/dir --id 51f7176b-0464-4a6f-8369-e951e187b398
462
463
  ```
463
464
 
464
- _See code: [src/commands/ssh/copyFrom.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/ssh/copyFrom.ts)_
465
+ _See code: [src/commands/ssh/copyFrom.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/ssh/copyFrom.ts)_
465
466
 
466
467
  ## `opal ssh copyTo`
467
468
 
@@ -492,7 +493,7 @@ EXAMPLES
492
493
  $ opal ssh:copyTo --src my/dir --dest instance/dir --id 51f7176b-0464-4a6f-8369-e951e187b398
493
494
  ```
494
495
 
495
- _See code: [src/commands/ssh/copyTo.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/ssh/copyTo.ts)_
496
+ _See code: [src/commands/ssh/copyTo.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/ssh/copyTo.ts)_
496
497
 
497
498
  ## `opal ssh start`
498
499
 
@@ -519,7 +520,7 @@ EXAMPLES
519
520
  $ opal ssh:start --id 51f7176b-0464-4a6f-8369-e951e187b398
520
521
  ```
521
522
 
522
- _See code: [src/commands/ssh/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.50f7f9f/src/commands/ssh/start.ts)_
523
+ _See code: [src/commands/ssh/start.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/ssh/start.ts)_
523
524
 
524
525
  ## `opal version`
525
526
 
@@ -540,4 +541,21 @@ FLAG DESCRIPTIONS
540
541
  ```
541
542
 
542
543
  _See code: [@oclif/plugin-version](https://github.com/oclif/plugin-version/blob/v2.2.27/src/commands/version.ts)_
544
+
545
+ ## `opal whoami`
546
+
547
+ Describes current url set, organization name, and logged in user if applicabled.
548
+
549
+ ```
550
+ USAGE
551
+ $ opal whoami [-h]
552
+
553
+ FLAGS
554
+ -h, --help Show CLI help.
555
+
556
+ DESCRIPTION
557
+ Describes current url set, organization name, and logged in user if applicabled.
558
+ ```
559
+
560
+ _See code: [src/commands/whoami.ts](https://github.com/opalsecurity/opal-cli/blob/v3.1.1-beta.5457bdf/src/commands/whoami.ts)_
543
561
  <!-- commandsstop -->
@@ -88,12 +88,14 @@ class Login extends core_1.Command {
88
88
  const configData = (0, config_1.getOrCreateConfigData)(configDir);
89
89
  let email = flags.email;
90
90
  let organizationId;
91
+ let organizationName;
91
92
  let clientIDCandidate;
92
93
  const existingCreds = await (0, credentials_1.getOpalCredentials)(this, false);
93
94
  // Only use the previous email + organizationID if email isn't explicitly specified.
94
95
  if (!email) {
95
96
  email = existingCreds.email;
96
97
  organizationId = existingCreds.organizationID;
98
+ organizationName = existingCreds.organizationName;
97
99
  clientIDCandidate = existingCreds.clientIDCandidate;
98
100
  }
99
101
  await (0, credentials_1.removeOpalCredentials)(this);
@@ -149,6 +151,7 @@ class Login extends core_1.Command {
149
151
  if (signInOrganizations && signInOrganizations.length > 0) {
150
152
  if (signInOrganizations.length === 1) {
151
153
  organizationId = signInOrganizations[0].organizationId;
154
+ organizationName = signInOrganizations[0].organizationName;
152
155
  clientIDCandidate = signInOrganizations[0].cliClientId;
153
156
  }
154
157
  else {
@@ -164,6 +167,7 @@ class Login extends core_1.Command {
164
167
  },
165
168
  ]);
166
169
  organizationId = responses.signInOrganization.organizationId;
170
+ organizationName = responses.signInOrganization.organizationName;
167
171
  clientIDCandidate = responses.signInOrganization.cliClientId;
168
172
  }
169
173
  }
@@ -238,10 +242,10 @@ class Login extends core_1.Command {
238
242
  if (tokenExchangeError) {
239
243
  this.log("WARN: Failed to exchange access token for session in Opal. Falling back to using access token for authenticating requests\n");
240
244
  // TODO: consider adding a warn line recommending upgrading Opal to version XYZ, once accompanying PR is pushed to prod
241
- await (0, credentials_1.setOpalCredentials)(this, email, organizationId !== null && organizationId !== void 0 ? organizationId : "", clientIDCandidate, (tokenSet === null || tokenSet === void 0 ? void 0 : tokenSet.access_token) || "", credentials_1.SecretType.ApiToken);
245
+ await (0, credentials_1.setOpalCredentials)(this, email, organizationId !== null && organizationId !== void 0 ? organizationId : "", clientIDCandidate, (tokenSet === null || tokenSet === void 0 ? void 0 : tokenSet.access_token) || "", credentials_1.SecretType.ApiToken, organizationName);
242
246
  }
243
247
  else {
244
- await (0, credentials_1.setOpalCredentials)(this, email, organizationId !== null && organizationId !== void 0 ? organizationId : "", clientIDCandidate, apollo_1.cookieStr, credentials_1.SecretType.Cookie);
248
+ await (0, credentials_1.setOpalCredentials)(this, email, organizationId !== null && organizationId !== void 0 ? organizationId : "", clientIDCandidate, apollo_1.cookieStr, credentials_1.SecretType.Cookie, organizationName);
245
249
  }
246
250
  // "Representative" authenticated call to check the log-in worked as expected.
247
251
  const { resp: authCheckResp, error: authCheckErr } = await (0, handler_1.runQueryDeprecated)({
@@ -5,6 +5,8 @@ export default class RequestCreate extends Command {
5
5
  static flags: {
6
6
  help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
7
7
  id: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ duration: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
10
  };
9
11
  run(): Promise<void>;
10
12
  }
@@ -14,7 +14,8 @@ class RequestCreate extends core_1.Command {
14
14
  const { flags } = await this.parse(RequestCreate);
15
15
  const metadata = (0, requests_1.initEmptyRequestMetadata)();
16
16
  if (flags.id) {
17
- (0, requests_1.bypassRequestSelection)(this, client, flags.id, metadata);
17
+ // if IDs are provided, bypass the interactive selection process
18
+ await (0, requests_1.bypassRequestSelection)(this, client, flags.id, metadata);
18
19
  }
19
20
  else {
20
21
  (0, displays_1.headerMessage)(this);
@@ -32,13 +33,27 @@ class RequestCreate extends core_1.Command {
32
33
  // Step 4: Set Request Defaults
33
34
  await (0, requests_1.setRequestDefaults)(this, client, metadata);
34
35
  // Step 4: Prompt for request reason
35
- await (0, requests_1.promptForReason)(metadata);
36
+ if (flags.reason) {
37
+ metadata.reason = flags.reason;
38
+ }
39
+ else if (!(metadata.requestDefaults.reasonOptional && flags.id && flags.duration)) {
40
+ await (0, requests_1.promptForReason)(metadata);
41
+ }
36
42
  // Step 5: Prompt for expiration
37
- await (0, requests_1.promptForExpiration)(metadata);
43
+ if (flags.duration) {
44
+ (0, requests_1.bypassDuration)(this, flags.duration, metadata);
45
+ }
46
+ else {
47
+ await (0, requests_1.promptForExpiration)(metadata);
48
+ }
38
49
  // Step 6: Display final summary of request
39
- (0, displays_1.displayFinalRequestSummary)(this, metadata);
40
- // Step 7: Prompt for final submition
41
- await (0, requests_1.submitFinalRequest)(this, client, metadata);
50
+ let canSubmit = true;
51
+ if (!(flags.id && flags.reason && flags.duration)) {
52
+ canSubmit = await (0, requests_1.promptRequestSubmission)(this, metadata);
53
+ }
54
+ // Step 7: Prompt for final submission
55
+ if (canSubmit)
56
+ await (0, requests_1.submitFinalRequest)(this, client, metadata);
42
57
  }
43
58
  }
44
59
  RequestCreate.hidden = true;
@@ -48,7 +63,16 @@ RequestCreate.flags = {
48
63
  id: core_1.Flags.string({
49
64
  char: "i",
50
65
  multiple: true,
51
- description: "The id of the asset (resource, group) to request access to. Append a role ID using a colon if needed, e.g. `--id 123:456`.",
66
+ description: "The id of the asset (resource, group) to request access to. Append a role ID using a colon if needed, e.g. `--id 123:456`.\
67
+ \n If not provided, an interactive selection flow will be available to select assets to request.",
68
+ }),
69
+ reason: core_1.Flags.string({
70
+ char: "r",
71
+ description: "The reason for the request, contained in quotes. If not provided, you will be prompted.",
72
+ }),
73
+ duration: core_1.Flags.integer({
74
+ char: "d",
75
+ description: "The duration of access for the request in minutes. If not provided, you will be prompted.",
52
76
  }),
53
77
  };
54
78
  exports.default = RequestCreate;
@@ -0,0 +1,8 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class WhoAmI extends Command {
3
+ static description: string;
4
+ static flags: {
5
+ help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
6
+ };
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const config_1 = require("../lib/config");
5
+ const credentials_1 = require("../lib/credentials");
6
+ const flags_1 = require("../lib/flags");
7
+ class WhoAmI extends core_1.Command {
8
+ async run() {
9
+ const opalCreds = await (0, credentials_1.getOpalCredentials)(this, false);
10
+ const organizationName = opalCreds === null || opalCreds === void 0 ? void 0 : opalCreds.organizationName;
11
+ const email = opalCreds === null || opalCreds === void 0 ? void 0 : opalCreds.email;
12
+ const configData = (0, config_1.getOrCreateConfigData)(this.config.configDir);
13
+ const url = configData[config_1.urlKey];
14
+ if (email) {
15
+ this.log(`User: ${email}`);
16
+ }
17
+ if (organizationName) {
18
+ if (organizationName === "unset-org-id") {
19
+ this.log("Authenticated with Opal API Token.");
20
+ }
21
+ else {
22
+ this.log(`Organization: ${organizationName}`);
23
+ }
24
+ }
25
+ if (url) {
26
+ this.log(`Server: ${url}`);
27
+ }
28
+ }
29
+ }
30
+ WhoAmI.description = "Describes current url set, organization name, and logged in user if applicabled.";
31
+ WhoAmI.flags = {
32
+ help: flags_1.SHARED_FLAGS.help,
33
+ };
34
+ exports.default = WhoAmI;
package/lib/lib/apollo.js CHANGED
@@ -155,7 +155,7 @@ const initClient = async (command, fetchAccessToken = true) => {
155
155
  return response;
156
156
  });
157
157
  });
158
- const errorLink = (0, error_1.onError)(({ networkError, operation }) => {
158
+ const errorLink = (0, error_1.onError)(({ networkError, operation, forward }) => {
159
159
  var _a;
160
160
  // There's a few GQL operations where we don't want to use this error handler:
161
161
  const customErrorOperations = [
@@ -186,7 +186,7 @@ const initClient = async (command, fetchAccessToken = true) => {
186
186
  case 401: {
187
187
  command.log("Your session is invalid or expired. Authenticating now...\n");
188
188
  const loginCommand = new login_1.default([], command.config);
189
- loginCommand.run().then(() => {
189
+ return (0, core_1.fromPromise)(loginCommand.run().then(() => {
190
190
  if (cmd_1.mostRecentCommandTime && cmd_1.mostRecentCommand) {
191
191
  const lastCommandReexecutionDuration = moment.duration(2, "minutes");
192
192
  const lastCommandReexecutionDurationHasElapsed = cmd_1.mostRecentCommandTime.add(lastCommandReexecutionDuration) >
@@ -195,8 +195,7 @@ const initClient = async (command, fetchAccessToken = true) => {
195
195
  cmd_1.mostRecentCommand.run();
196
196
  }
197
197
  }
198
- });
199
- break;
198
+ })).flatMap(() => forward(operation));
200
199
  }
201
200
  default:
202
201
  return (0, exports.handleError)(command, `Received status code ${networkError.statusCode} from server${errorMessage ? ` with message "${errorMessage}"` : ""}`);
@@ -5,12 +5,13 @@ interface OpalCredentials {
5
5
  clientIDCandidate?: string;
6
6
  secret?: string;
7
7
  secretType?: SecretType;
8
+ organizationName?: string;
8
9
  }
9
10
  export declare enum SecretType {
10
11
  Cookie = "COOKIE",
11
12
  ApiToken = "API_TOKEN"
12
13
  }
13
- export declare const setOpalCredentials: (command: Command, email: string | undefined, organizationID: string, clientIDCandidate: string | undefined | null, secret: string, secretType: SecretType) => Promise<void>;
14
+ export declare const setOpalCredentials: (command: Command, email: string | undefined, organizationID: string, clientIDCandidate: string | undefined | null, secret: string, secretType: SecretType, organizationName?: string) => Promise<void>;
14
15
  export declare const getOpalCredentials: (command: Command, includeAuthSecret?: boolean) => Promise<OpalCredentials>;
15
16
  export declare const removeOpalCredentials: (command: Command) => Promise<void>;
16
17
  export {};
@@ -9,13 +9,14 @@ var SecretType;
9
9
  SecretType["Cookie"] = "COOKIE";
10
10
  SecretType["ApiToken"] = "API_TOKEN";
11
11
  })(SecretType || (exports.SecretType = SecretType = {}));
12
- const setOpalCredentials = async (command, email, organizationID, clientIDCandidate, secret, secretType) => {
12
+ const setOpalCredentials = async (command, email, organizationID, clientIDCandidate, secret, secretType, organizationName) => {
13
13
  const givenEmail = email || "email-unset";
14
14
  const configData = (0, config_1.getOrCreateConfigData)(command.config.configDir);
15
15
  configData.creds = {
16
16
  clientIDCandidate,
17
17
  email,
18
18
  organizationID,
19
+ organizationName,
19
20
  secretType,
20
21
  };
21
22
  (0, config_1.writeConfigData)(command.config.configDir, configData);
@@ -1,16 +1,17 @@
1
1
  import type { NormalizedCacheObject } from "@apollo/client/core";
2
2
  import type { ApolloClient } from "@apollo/client/core/ApolloClient";
3
3
  import type { Command } from "@oclif/core/lib/command";
4
+ import { type AppType, type ConnectionType, EntityType } from "../graphql/graphql";
4
5
  type AppNode = {
5
6
  appId: string;
6
7
  appName: string;
7
- appType?: string;
8
+ appType?: AppType | ConnectionType;
8
9
  assets: Record<string, AssetNode>;
9
10
  };
10
11
  type AssetNode = {
11
12
  assetId: string;
12
13
  assetName: string;
13
- type: string;
14
+ type: EntityType;
14
15
  roles?: Record<string, RoleNode>;
15
16
  };
16
17
  type RoleNode = {
@@ -44,6 +45,8 @@ export declare function doneSelectingAssets(): Promise<boolean>;
44
45
  export declare function setRequestDefaults(cmd: Command, client: ApolloClient<NormalizedCacheObject>, metadata: RequestMetadata): Promise<void>;
45
46
  export declare function promptForReason(metadata: RequestMetadata): Promise<void>;
46
47
  export declare function promptForExpiration(metadata: RequestMetadata): Promise<void>;
48
+ export declare function promptRequestSubmission(cmd: Command, metadata: RequestMetadata): Promise<boolean>;
47
49
  export declare function submitFinalRequest(cmd: Command, client: ApolloClient<NormalizedCacheObject>, metadata: RequestMetadata): Promise<void>;
48
- export declare function bypassRequestSelection(cmd: Command, client: ApolloClient<NormalizedCacheObject>, flagIds: string[], metadata: RequestMetadata): Promise<void>;
50
+ export declare function bypassRequestSelection(cmd: Command, client: ApolloClient<NormalizedCacheObject>, flagValue: string[], metadata: RequestMetadata): Promise<void>;
51
+ export declare function bypassDuration(cmd: Command, duration: number, metadata: RequestMetadata): void;
49
52
  export {};
@@ -6,15 +6,27 @@ exports.doneSelectingAssets = doneSelectingAssets;
6
6
  exports.setRequestDefaults = setRequestDefaults;
7
7
  exports.promptForReason = promptForReason;
8
8
  exports.promptForExpiration = promptForExpiration;
9
+ exports.promptRequestSubmission = promptRequestSubmission;
9
10
  exports.submitFinalRequest = submitFinalRequest;
10
11
  exports.bypassRequestSelection = bypassRequestSelection;
12
+ exports.bypassDuration = bypassDuration;
11
13
  const chalk_1 = require("chalk");
12
- const inquirer = require("inquirer");
13
14
  const graphql_1 = require("../graphql");
14
15
  const graphql_2 = require("../graphql/graphql");
15
16
  const displays_1 = require("../utils/displays");
16
17
  const config_1 = require("./config");
17
- const { AutoComplete, Select, prompt, Form } = require("enquirer");
18
+ const { AutoComplete, Select, prompt, Form, confirm } = require("enquirer");
19
+ function entityTypeFromString(str) {
20
+ const capStr = str === null || str === void 0 ? void 0 : str.toLocaleUpperCase();
21
+ if (capStr === "RESOURCE") {
22
+ return graphql_2.EntityType.Resource;
23
+ }
24
+ if (capStr === "GROUP") {
25
+ return graphql_2.EntityType.Group;
26
+ }
27
+ // if type unknown, default to resource
28
+ return graphql_2.EntityType.Resource;
29
+ }
18
30
  function initEmptyRequestMetadata() {
19
31
  // Initialize with empty defaults
20
32
  const requestDefaults = {
@@ -167,7 +179,7 @@ async function queryRequestableAssets(cmd, client, appId, input) {
167
179
  value: {
168
180
  name: name || "",
169
181
  id: id || "",
170
- type: ((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename) || "",
182
+ type: entityTypeFromString(((_g = item.resource) === null || _g === void 0 ? void 0 : _g.__typename) || ((_h = item.group) === null || _h === void 0 ? void 0 : _h.__typename)),
171
183
  },
172
184
  };
173
185
  });
@@ -611,11 +623,14 @@ async function queryAssociatedItems(cmd, client, id, input) {
611
623
  }
612
624
  }
613
625
  // Helper functions
626
+ const selectInstructions = chalk_1.default.dim("[↑↓] Navigate · [Enter] Select · Type to filter");
627
+ const multiSelectInstructions = chalk_1.default.dim("[↑↓] Navigate · [Space] Select · [Enter] Confirm · Type to filter");
614
628
  async function selectRequestableItems(cmd, client, requestMap) {
615
629
  const initial = (await queryRequestableApps(cmd, client, "")) || [];
616
630
  const appPrompt = new AutoComplete({
617
631
  name: "App",
618
- message: "Select an app:",
632
+ message: "Select an app",
633
+ hint: selectInstructions,
619
634
  limit: 15,
620
635
  choices: initial,
621
636
  async suggest(input) {
@@ -643,6 +658,7 @@ async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
643
658
  const rolePrompt = new AutoComplete({
644
659
  name: "Roles",
645
660
  message: `Select a role for ${app.name}:`,
661
+ hint: multiSelectInstructions,
646
662
  limit: 15,
647
663
  multiple: true,
648
664
  async choices(input) {
@@ -653,7 +669,7 @@ async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
653
669
  },
654
670
  validate: (answer) => {
655
671
  if (answer.length !== 1) {
656
- return "You must select only one item.";
672
+ return "Only one role is allowed to be requested for on Okta or Azure apps.";
657
673
  }
658
674
  return true;
659
675
  },
@@ -665,7 +681,7 @@ async function chooseOktaAzureRoles(cmd, client, app, requestMap) {
665
681
  entry.assets[role.id] = {
666
682
  assetId: role.id,
667
683
  assetName: role.name,
668
- type: role.type || graphql_2.EntityType.Resource,
684
+ type: entityTypeFromString(role.type),
669
685
  roles: {},
670
686
  };
671
687
  }
@@ -717,6 +733,7 @@ async function chooseAssets(cmd, client, appId, requestMap) {
717
733
  const assetPrompt = new AutoComplete({
718
734
  name: "Assets",
719
735
  message: "Select one or more assets:",
736
+ hint: multiSelectInstructions,
720
737
  limit: 15,
721
738
  multiple: true,
722
739
  async choices(input) {
@@ -766,6 +783,7 @@ async function chooseRoles(cmd, client, appId, assetId, requestMap) {
766
783
  const rolePrompt = new AutoComplete({
767
784
  name: "Roles",
768
785
  message: `Select one or more roles for ${assetEntry.assetName}:`,
786
+ hint: multiSelectInstructions,
769
787
  limit: 15,
770
788
  multiple: true,
771
789
  choices: assetRoles,
@@ -856,7 +874,7 @@ async function promptForReason(metadata) {
856
874
  const { reason } = await prompt([
857
875
  {
858
876
  name: "reason",
859
- message: "I need access to this because...",
877
+ message: "Why do you need access?",
860
878
  type: "input",
861
879
  validate: (answer) => {
862
880
  if (!metadata.requestDefaults.reasonOptional && answer.length < 1) {
@@ -978,148 +996,135 @@ async function setCustomDuration(metadata) {
978
996
  label: durationLabel,
979
997
  };
980
998
  }
981
- async function submitFinalRequest(cmd, client, metadata) {
982
- var _a, _b, _c, _d;
999
+ async function promptRequestSubmission(cmd, metadata) {
1000
+ (0, displays_1.displayFinalRequestSummary)(cmd, metadata);
983
1001
  const submitMessage = "✅ Yes, submit request";
984
1002
  const cancelMessage = "❌ No, cancel request";
985
- const { submit } = await inquirer.prompt([
986
- {
987
- name: "submit",
988
- message: "Submit request?",
989
- type: "list",
990
- choices: [submitMessage, cancelMessage],
991
- },
992
- ]);
993
- switch (submit) {
994
- case submitMessage: {
995
- // Build requested assets lists for the mutation
996
- const requestedResources = [];
997
- const requestedGroups = [];
998
- for (const appNode of Object.values(metadata.requestMap)) {
999
- // This extraction is different than the one in setRequestDefaults.
1000
- // Both extract the requestedResources and requestedGroups,
1001
- // use different formats.
1002
- for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
1003
- if (assetNode.roles !== undefined) {
1004
- const mappedRoles = Object.entries(assetNode.roles).map(([roleId, _roleNode]) => {
1005
- return roleId;
1006
- });
1007
- const roleIds = mappedRoles.length ? mappedRoles : [""];
1008
- for (const roleId of roleIds) {
1009
- switch (assetNode.type) {
1010
- case graphql_2.EntityType.Resource: {
1011
- requestedResources.push({
1012
- resourceId: assetId,
1013
- accessLevel: {
1014
- accessLevelName: ((_b = (_a = assetNode.roles) === null || _a === void 0 ? void 0 : _a[roleId]) === null || _b === void 0 ? void 0 : _b.roleName) || "",
1015
- accessLevelRemoteId: roleId,
1016
- },
1017
- });
1018
- break;
1019
- }
1020
- case graphql_2.EntityType.Group: {
1021
- requestedGroups.push({
1022
- groupId: assetId,
1023
- accessLevel: {
1024
- accessLevelName: ((_d = (_c = assetNode.roles) === null || _c === void 0 ? void 0 : _c[roleId]) === null || _d === void 0 ? void 0 : _d.roleName) || "",
1025
- accessLevelRemoteId: roleId,
1026
- },
1027
- });
1028
- break;
1029
- }
1030
- }
1003
+ const prompt = new Select({
1004
+ name: "submitOrCancel",
1005
+ message: "Is this all you want to request?",
1006
+ choices: [submitMessage, cancelMessage],
1007
+ });
1008
+ const submitOrCancel = await prompt.run();
1009
+ return submitOrCancel === submitMessage;
1010
+ }
1011
+ async function submitFinalRequest(cmd, client, metadata) {
1012
+ var _a, _b, _c, _d;
1013
+ // Build requested assets lists for the mutation
1014
+ const requestedResources = [];
1015
+ const requestedGroups = [];
1016
+ for (const appNode of Object.values(metadata.requestMap)) {
1017
+ // This extraction is different than the one in setRequestDefaults.
1018
+ // Both extract the requestedResources and requestedGroups,
1019
+ // use different formats.
1020
+ for (const [assetId, assetNode] of Object.entries(appNode.assets)) {
1021
+ if (assetNode.roles) {
1022
+ const mappedRoles = Object.entries(assetNode.roles).map(([roleId, _roleNode]) => {
1023
+ return roleId;
1024
+ });
1025
+ const roleIds = mappedRoles.length > 0 ? mappedRoles : [""];
1026
+ for (const roleId of roleIds) {
1027
+ switch (assetNode.type) {
1028
+ case graphql_2.EntityType.Resource: {
1029
+ requestedResources.push({
1030
+ resourceId: assetId,
1031
+ accessLevel: {
1032
+ accessLevelName: ((_b = (_a = assetNode.roles) === null || _a === void 0 ? void 0 : _a[roleId]) === null || _b === void 0 ? void 0 : _b.roleName) || "",
1033
+ accessLevelRemoteId: roleId,
1034
+ },
1035
+ });
1036
+ break;
1037
+ }
1038
+ case graphql_2.EntityType.Group: {
1039
+ requestedGroups.push({
1040
+ groupId: assetId,
1041
+ accessLevel: {
1042
+ accessLevelName: ((_d = (_c = assetNode.roles) === null || _c === void 0 ? void 0 : _c[roleId]) === null || _d === void 0 ? void 0 : _d.roleName) || "",
1043
+ accessLevelRemoteId: roleId,
1044
+ },
1045
+ });
1046
+ break;
1031
1047
  }
1032
1048
  }
1033
1049
  }
1034
1050
  }
1035
- const resp = await createRequest(cmd, client, requestedResources, requestedGroups, metadata.reason, metadata.durationInMinutes);
1036
- // Build link to request
1037
- const configData = (0, config_1.getOrCreateConfigData)(cmd.config.configDir);
1038
- if (resp === null || resp === void 0 ? void 0 : resp.id) {
1039
- cmd.log("\n🎉 Your Access Request has been submitted!\n");
1040
- cmd.log(`${chalk_1.default.bold("ID: ")} ${chalk_1.default.cyan(resp === null || resp === void 0 ? void 0 : resp.id)}`);
1041
- if (resp === null || resp === void 0 ? void 0 : resp.status) {
1042
- cmd.log((0, displays_1.getStyledStatus)(resp === null || resp === void 0 ? void 0 : resp.status));
1043
- }
1044
- const requestLink = `${configData[config_1.urlKey]}/requests/sent/${resp === null || resp === void 0 ? void 0 : resp.id}`;
1045
- cmd.log(`${chalk_1.default.bold("Link:")} ${chalk_1.default.underline(requestLink)}\n`);
1046
- }
1047
- return;
1048
1051
  }
1049
- case cancelMessage: {
1050
- cmd.log("🚫 Access Request has been cancelled.");
1051
- return;
1052
- }
1053
- default: {
1054
- cmd.error("Unknown error occurred.");
1052
+ }
1053
+ const resp = await createRequest(cmd, client, requestedResources, requestedGroups, metadata.reason, metadata.durationInMinutes);
1054
+ // Build link to request
1055
+ const configData = (0, config_1.getOrCreateConfigData)(cmd.config.configDir);
1056
+ if (resp === null || resp === void 0 ? void 0 : resp.id) {
1057
+ cmd.log("\n🎉 Your Access Request has been submitted!\n");
1058
+ cmd.log(`${chalk_1.default.bold("ID: ")} ${chalk_1.default.cyan(resp === null || resp === void 0 ? void 0 : resp.id)}`);
1059
+ if (resp === null || resp === void 0 ? void 0 : resp.status) {
1060
+ cmd.log((0, displays_1.getStyledStatus)(resp === null || resp === void 0 ? void 0 : resp.status));
1055
1061
  }
1062
+ const requestLink = `${configData[config_1.urlKey]}/requests/sent/${resp === null || resp === void 0 ? void 0 : resp.id}`;
1063
+ cmd.log(`${chalk_1.default.bold("Link:")} ${chalk_1.default.underline(requestLink)}\n`);
1056
1064
  }
1065
+ return;
1057
1066
  }
1058
- async function bypassRequestSelection(cmd, client, flagIds, metadata) {
1067
+ async function bypassRequestSelection(cmd, client, flagValue, metadata) {
1059
1068
  var _a, _b;
1060
1069
  try {
1061
- for (const id of flagIds) {
1070
+ // Query Catalog Item endpoint to identify what the id belongs to (resource or group)
1071
+ for (const id of flagValue) {
1072
+ const [assetId, roleName] = id.split(":");
1062
1073
  const resp = await client.query({
1063
1074
  query: CATALOG_ITEM,
1064
1075
  variables: {
1065
- uuid: id || "",
1076
+ uuid: assetId || "",
1066
1077
  },
1067
1078
  fetchPolicy: "network-only", // to avoid caching
1068
1079
  });
1069
1080
  switch (resp.data.catalogItem.__typename) {
1081
+ case "Group":
1070
1082
  case "Resource": {
1071
- const resourceName = resp.data.catalogItem.displayName;
1072
- const roles = (resp.data.catalogItem.accessLevels || [])
1083
+ const item = resp.data.catalogItem;
1084
+ const assetName = item.__typename === "Resource" ? item.displayName : item.name;
1085
+ const requestableRoles = (item.accessLevels || [])
1073
1086
  // TODO: Support okta azure apps ?.filter((role) => role.accessLevelName !== "") // This assumes length == 1
1074
1087
  .map((role) => ({
1075
1088
  id: role.accessLevelRemoteId,
1076
1089
  name: role.accessLevelName,
1077
1090
  }));
1078
- if (roles.length > 0 &&
1079
- !(roles.length === 1 && roles[0].name === "")) {
1080
- cmd.log(`Roles not implemented yet for resource ${resourceName}. Skipping roles selection.`); //TODO: Implement roles support
1081
- continue;
1082
- }
1083
- const appId = ((_a = resp.data.catalogItem.connection) === null || _a === void 0 ? void 0 : _a.id) || "";
1091
+ const appId = ((_a = item.connection) === null || _a === void 0 ? void 0 : _a.id) || "";
1084
1092
  if (!(appId in metadata.requestMap)) {
1085
1093
  metadata.requestMap[appId] = {
1086
- appName: ((_b = resp.data.catalogItem.connection) === null || _b === void 0 ? void 0 : _b.displayName) || "",
1094
+ appName: ((_b = item.connection) === null || _b === void 0 ? void 0 : _b.displayName) || "",
1087
1095
  appId: appId,
1088
1096
  assets: {},
1089
1097
  };
1090
1098
  }
1091
1099
  const assetEntry = metadata.requestMap[appId].assets[id];
1092
1100
  if (!assetEntry) {
1093
- metadata.requestMap[appId].assets[id] = {
1094
- assetId: id,
1095
- assetName: resourceName,
1096
- type: "Resource",
1101
+ metadata.requestMap[appId].assets[assetId] = {
1102
+ assetId: assetId,
1103
+ assetName: assetName,
1104
+ type: entityTypeFromString(item.__typename),
1097
1105
  roles: {},
1098
1106
  };
1099
1107
  }
1100
- if (roles) {
1101
- if (!metadata.requestMap[appId].assets[id].roles) {
1102
- metadata.requestMap[appId].assets[id].roles = {};
1103
- }
1104
- for (const role of roles) {
1105
- metadata.requestMap[appId].assets[id].roles[role.id] = {
1106
- roleId: role.id,
1107
- roleName: role.name,
1108
+ if (requestableRoles.length > 0 &&
1109
+ !(requestableRoles.length === 1 && requestableRoles[0].name === "")) {
1110
+ const selectedRole = requestableRoles.find((role) => role.name === roleName);
1111
+ if (selectedRole !== undefined) {
1112
+ if (!metadata.requestMap[appId].assets[assetId].roles) {
1113
+ metadata.requestMap[appId].assets[assetId].roles = {};
1114
+ }
1115
+ metadata.requestMap[appId].assets[assetId].roles[selectedRole.id] = {
1116
+ roleId: selectedRole.id,
1117
+ roleName: selectedRole.name,
1108
1118
  };
1109
1119
  }
1120
+ else {
1121
+ cmd.error(`Access level specified does not match one of ${assetName}'s defined access levels: ${requestableRoles.map((role) => `"${role.name}"`)}`);
1122
+ }
1110
1123
  }
1111
1124
  break;
1112
1125
  }
1113
- case "Group": {
1114
- //TODO
1115
- break;
1116
- }
1117
- case "Connection": {
1118
- //TODO
1119
- break;
1120
- }
1121
1126
  default:
1122
- cmd.error("Unknown error occurred.");
1127
+ cmd.error("Invalid asset id was passed in using the --id flag.");
1123
1128
  }
1124
1129
  }
1125
1130
  }
@@ -1130,3 +1135,10 @@ async function bypassRequestSelection(cmd, client, flagIds, metadata) {
1130
1135
  }
1131
1136
  return;
1132
1137
  }
1138
+ function bypassDuration(cmd, duration, metadata) {
1139
+ const maxDuration = metadata.requestDefaults.maxDurationInMinutes;
1140
+ if (maxDuration && duration > maxDuration) {
1141
+ cmd.error(`The requested duration exceeds the allowed limit of ${maxDuration}`);
1142
+ }
1143
+ metadata.durationInMinutes = duration;
1144
+ }
@@ -35,8 +35,10 @@ function treeifyRequestMap(cmd, requestMap) {
35
35
  assetsTree[assetKey] = {};
36
36
  for (const [_roleId, roleNode] of Object.entries(assetNode.roles)) {
37
37
  const roleName = roleNode.roleName;
38
- const roleKey = `${roleName} ${chalk_1.default.dim("[Role]")}`;
39
- assetsTree[assetKey][roleKey] = null;
38
+ if (roleName !== "") {
39
+ const roleKey = `${roleName} ${chalk_1.default.dim("[Role]")}`;
40
+ assetsTree[assetKey][roleKey] = null;
41
+ }
40
42
  }
41
43
  }
42
44
  else {
@@ -316,6 +316,34 @@
316
316
  "set-url.js"
317
317
  ]
318
318
  },
319
+ "whoami": {
320
+ "aliases": [],
321
+ "args": {},
322
+ "description": "Describes current url set, organization name, and logged in user if applicabled.",
323
+ "flags": {
324
+ "help": {
325
+ "char": "h",
326
+ "description": "Show CLI help.",
327
+ "name": "help",
328
+ "allowNo": false,
329
+ "type": "boolean"
330
+ }
331
+ },
332
+ "hasDynamicHelp": false,
333
+ "hiddenAliases": [],
334
+ "id": "whoami",
335
+ "pluginAlias": "opal-security",
336
+ "pluginName": "opal-security",
337
+ "pluginType": "core",
338
+ "strict": true,
339
+ "enableJsonFlag": false,
340
+ "isESM": false,
341
+ "relativePath": [
342
+ "lib",
343
+ "commands",
344
+ "whoami.js"
345
+ ]
346
+ },
319
347
  "aws:identity": {
320
348
  "aliases": [],
321
349
  "args": {},
@@ -608,11 +636,27 @@
608
636
  },
609
637
  "id": {
610
638
  "char": "i",
611
- "description": "The id of the asset (resource, group) to request access to. Append a role ID using a colon if needed, e.g. `--id 123:456`.",
639
+ "description": "The id of the asset (resource, group) to request access to. Append a role ID using a colon if needed, e.g. `--id 123:456`. \n If not provided, an interactive selection flow will be available to select assets to request.",
612
640
  "name": "id",
613
641
  "hasDynamicHelp": false,
614
642
  "multiple": true,
615
643
  "type": "option"
644
+ },
645
+ "reason": {
646
+ "char": "r",
647
+ "description": "The reason for the request, contained in quotes. If not provided, you will be prompted.",
648
+ "name": "reason",
649
+ "hasDynamicHelp": false,
650
+ "multiple": false,
651
+ "type": "option"
652
+ },
653
+ "duration": {
654
+ "char": "d",
655
+ "description": "The duration of access for the request in minutes. If not provided, you will be prompted.",
656
+ "name": "duration",
657
+ "hasDynamicHelp": false,
658
+ "multiple": false,
659
+ "type": "option"
616
660
  }
617
661
  },
618
662
  "hasDynamicHelp": false,
@@ -987,5 +1031,5 @@
987
1031
  ]
988
1032
  }
989
1033
  },
990
- "version": "3.1.1-beta.50f7f9f"
1034
+ "version": "3.1.1-beta.5457bdf"
991
1035
  }
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.50f7f9f",
4
+ "version": "3.1.1-beta.5457bdf",
5
5
  "author": "Stephen Cobbe",
6
6
  "bin": {
7
7
  "opal": "./bin/run"