az2aws 1.6.2 → 1.7.0

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/lib/login.js CHANGED
@@ -20,6 +20,8 @@ const promises_2 = __importDefault(require("fs/promises"));
20
20
  const https_1 = require("https");
21
21
  const node_http_handler_1 = require("@smithy/node-http-handler");
22
22
  const loginStates_1 = require("./loginStates");
23
+ const sessionDuration_1 = require("./sessionDuration");
24
+ const sensitiveOutput_1 = require("./sensitiveOutput");
23
25
  const debug = (0, debug_1.default)("az2aws");
24
26
  const WIDTH = 425;
25
27
  const HEIGHT = 550;
@@ -30,6 +32,7 @@ const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
30
32
  const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
31
33
  const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
32
34
  const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
35
+ const REDACTED = "[redacted]";
33
36
  // Keep the runtime import as native `import()` so CommonJS output can load
34
37
  // the ESM-only https-proxy-agent package.
35
38
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
@@ -38,48 +41,77 @@ const getProxyUrl = () => process.env.https_proxy ||
38
41
  process.env.HTTPS_PROXY ||
39
42
  process.env.http_proxy ||
40
43
  process.env.HTTP_PROXY;
44
+ function handleBackgroundPromise(promise, description) {
45
+ void promise.catch((error) => {
46
+ const message = (0, sensitiveOutput_1.formatDebugErrorMessage)(error);
47
+ debug(`${description}: ${message}`);
48
+ });
49
+ }
41
50
  exports.login = {
42
51
  async _createHttpsProxyAgentAsync(proxyUrl, proxyOptions) {
43
52
  const { HttpsProxyAgent } = await importHttpsProxyAgent();
44
53
  return new HttpsProxyAgent(proxyUrl, proxyOptions);
45
54
  },
46
- async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false) {
47
- let headless, cliProxy;
48
- if (mode === "cli") {
49
- headless = true;
50
- cliProxy = true;
51
- }
52
- else if (mode === "gui") {
53
- headless = false;
54
- cliProxy = false;
55
- }
56
- else if (mode === "debug") {
57
- headless = false;
58
- cliProxy = true;
59
- }
60
- else {
61
- throw new CLIError_1.CLIError("Invalid mode");
62
- }
63
- const profile = await this._loadProfileAsync(profileName);
64
- console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
65
- if (profile.region && profile.region.startsWith("us-gov")) {
66
- console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
67
- "will use your AWS CLI default region. If needed, set it to match " +
68
- "this GovCloud region (us-gov-west-1 or us-gov-east-1).");
69
- }
70
- let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
71
- if (profile.region && profile.region.startsWith("us-gov")) {
72
- assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
55
+ async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false, credentialProcess = false) {
56
+ const originalConsoleLog = console.log;
57
+ const effectiveNoPrompt = credentialProcess ? true : noPrompt;
58
+ try {
59
+ if (credentialProcess) {
60
+ console.log = (...args) => console.error(...args);
61
+ }
62
+ let headless, cliProxy;
63
+ if (mode === "cli") {
64
+ headless = true;
65
+ cliProxy = true;
66
+ }
67
+ else if (mode === "gui") {
68
+ headless = false;
69
+ cliProxy = false;
70
+ }
71
+ else if (mode === "debug") {
72
+ headless = false;
73
+ cliProxy = true;
74
+ }
75
+ else {
76
+ throw new CLIError_1.CLIError("Invalid mode");
77
+ }
78
+ const profile = await this._loadProfileAsync(profileName);
79
+ console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
80
+ if (profile.region && profile.region.startsWith("us-gov")) {
81
+ console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
82
+ "will use your AWS CLI default region. If needed, set it to match " +
83
+ "this GovCloud region (us-gov-west-1 or us-gov-east-1).");
84
+ }
85
+ let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
86
+ if (profile.region && profile.region.startsWith("us-gov")) {
87
+ assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
88
+ }
89
+ if (profile.region && profile.region.startsWith("cn-")) {
90
+ assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
91
+ }
92
+ console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
93
+ const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
94
+ const allowSensitiveOutput = (0, sensitiveOutput_1.shouldAllowSensitiveOutput)();
95
+ const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, effectiveNoPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu, incognito, allowSensitiveOutput);
96
+ const roles = this._parseRolesFromSamlResponse(samlResponse);
97
+ const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, effectiveNoPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours, credentialProcess ? "--credential-process" : "--no-prompt");
98
+ const credentials = await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region, !credentialProcess);
99
+ if (credentialProcess) {
100
+ if (!credentials) {
101
+ throw new CLIError_1.CLIError("Unable to retrieve credentials.");
102
+ }
103
+ originalConsoleLog(JSON.stringify({
104
+ Version: 1,
105
+ AccessKeyId: credentials.aws_access_key_id,
106
+ SecretAccessKey: credentials.aws_secret_access_key,
107
+ SessionToken: credentials.aws_session_token,
108
+ Expiration: credentials.aws_expiration,
109
+ }));
110
+ }
73
111
  }
74
- if (profile.region && profile.region.startsWith("cn-")) {
75
- assertionConsumerServiceURL = AWS_CN_SAML_ENDPOINT;
112
+ finally {
113
+ console.log = originalConsoleLog;
76
114
  }
77
- console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
78
- const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
79
- const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu, incognito);
80
- const roles = this._parseRolesFromSamlResponse(samlResponse);
81
- const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, noPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours);
82
- await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region);
83
115
  },
84
116
  async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito = false) {
85
117
  const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
@@ -117,12 +149,22 @@ exports.login = {
117
149
  }
118
150
  }
119
151
  debug("Environment");
120
- debug({
121
- ...env,
122
- azure_default_password: "xxxxxxxxxx",
123
- });
152
+ debug(this._redactProfileForDebug(env));
124
153
  return env;
125
154
  },
155
+ _redactProfileForDebug(env) {
156
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [
157
+ key,
158
+ key === "azure_default_duration_hours" ? value : REDACTED,
159
+ ]));
160
+ },
161
+ _redactArnForDebug(arn) {
162
+ const match = arn.match(/^(arn:[^:]+:iam::)[^:]+:(.+?\/).+$/);
163
+ if (!match) {
164
+ return arn;
165
+ }
166
+ return `${match[1]}${REDACTED}:${match[2]}${REDACTED}`;
167
+ },
126
168
  // Load the profile
127
169
  async _loadProfileAsync(profileName) {
128
170
  const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
@@ -156,7 +198,7 @@ exports.login = {
156
198
  <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"></samlp:NameIDPolicy>
157
199
  </samlp:AuthnRequest>
158
200
  `;
159
- debug("Generated SAML request", samlRequest);
201
+ debug("Generated SAML request");
160
202
  debug("Deflating SAML");
161
203
  return new Promise((resolve, reject) => {
162
204
  zlib_1.default.deflateRaw(samlRequest, (err, samlBuffer) => {
@@ -166,7 +208,7 @@ exports.login = {
166
208
  debug("Encoding SAML in base64");
167
209
  const samlBase64 = samlBuffer.toString("base64");
168
210
  const url = `https://login.microsoftonline.com/${tenantId}/saml2?SAMLRequest=${encodeURIComponent(samlBase64)}`;
169
- debug("Created login URL", url);
211
+ debug("Created login URL", (0, sensitiveOutput_1.redactUrlForLogs)(url));
170
212
  return resolve(url);
171
213
  });
172
214
  });
@@ -189,7 +231,7 @@ exports.login = {
189
231
  * @returns {Promise.<string>} The SAML response.
190
232
  * @private
191
233
  */
192
- async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false) {
234
+ async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false, allowSensitiveStateOutput = true) {
193
235
  debug("Loading login page in Chrome");
194
236
  let browser;
195
237
  const useRememberMe = rememberMe && !incognito;
@@ -248,7 +290,7 @@ exports.login = {
248
290
  e.name === "TargetCloseError" &&
249
291
  useRememberMe &&
250
292
  !paths_1.paths.userDataDir) {
251
- debug(`Browser launch failed with TargetCloseError. Resetting profile at ${paths_1.paths.chromium}`);
293
+ debug("Browser launch failed with TargetCloseError. Resetting managed browser profile.");
252
294
  console.warn("Browser profile appears incompatible. Resetting profile data and retrying...");
253
295
  await promises_2.default.rm(paths_1.paths.chromium, { recursive: true, force: true });
254
296
  await promises_2.default.mkdir(paths_1.paths.chromium, { recursive: true });
@@ -283,29 +325,27 @@ exports.login = {
283
325
  const samlResponsePromise = new Promise((resolve) => {
284
326
  page.on("request", (req) => {
285
327
  const reqURL = req.url();
286
- debug(`Request: ${reqURL}`);
328
+ const redactedURL = (0, sensitiveOutput_1.redactUrlForLogs)(reqURL);
329
+ debug(`Request: ${redactedURL}`);
287
330
  if (reqURL === AWS_SAML_ENDPOINT ||
288
331
  reqURL === AWS_GOV_SAML_ENDPOINT ||
289
332
  reqURL === AWS_CN_SAML_ENDPOINT) {
290
333
  resolve(undefined);
291
334
  samlResponseData = req.postData();
292
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
293
- req.respond({
335
+ handleBackgroundPromise(req.respond({
294
336
  status: 200,
295
337
  contentType: "text/plain",
296
338
  headers: {},
297
339
  body: "",
298
- });
340
+ }), `Failed to respond to intercepted request ${redactedURL}`);
299
341
  if (browser) {
300
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
301
- browser.close();
342
+ handleBackgroundPromise(browser.close(), "Failed to close browser after receiving SAML response");
302
343
  }
303
344
  browser = undefined;
304
345
  debug(`Received SAML response, browser closed`);
305
346
  }
306
347
  else {
307
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
308
- req.continue();
348
+ handleBackgroundPromise(req.continue(), `Failed to continue intercepted request ${redactedURL}`);
309
349
  }
310
350
  });
311
351
  });
@@ -325,13 +365,12 @@ exports.login = {
325
365
  if (err instanceof Error) {
326
366
  // An error will be thrown if you're still logged in cause the page.goto ot waitForNavigation
327
367
  // will be a redirect to AWS. That's usually OK
328
- debug(`Error occurred during loading the first page: ${err.message}`);
368
+ debug(`Error occurred during loading the first page: ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}`);
329
369
  }
330
370
  }
331
371
  if (cliProxy) {
332
372
  let totalUnrecognizedDelay = 0;
333
- // eslint-disable-next-line no-constant-condition
334
- while (true) {
373
+ for (;;) {
335
374
  if (samlResponseData)
336
375
  break;
337
376
  let foundState = false;
@@ -345,7 +384,7 @@ exports.login = {
345
384
  if (err instanceof Error) {
346
385
  // An error can be thrown if the page isn't in a good state.
347
386
  // If one occurs, try again after another loop.
348
- debug(`Error when running state "${state.name}". ${err.toString()}. Retrying...`);
387
+ debug(`Error when running state "${state.name}". ${(0, sensitiveOutput_1.formatDebugErrorMessage)(err)}. Retrying...`);
349
388
  }
350
389
  break;
351
390
  }
@@ -354,7 +393,7 @@ exports.login = {
354
393
  debug(`Found state: ${state.name}`);
355
394
  await Promise.race([
356
395
  samlResponsePromise,
357
- state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe),
396
+ state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe, allowSensitiveStateOutput),
358
397
  ]);
359
398
  debug(`Finished state: ${state.name}`);
360
399
  break;
@@ -366,6 +405,9 @@ exports.login = {
366
405
  else {
367
406
  debug("State not recognized!");
368
407
  if (totalUnrecognizedDelay > MAX_UNRECOGNIZED_PAGE_DELAY) {
408
+ if (!allowSensitiveStateOutput) {
409
+ throw new CLIError_1.CLIError("Unable to recognize page state in a shared environment. Re-run locally with --mode=debug to capture a screenshot.");
410
+ }
369
411
  const path = "az2aws-unrecognized-state.png";
370
412
  await page.screenshot({ path });
371
413
  throw new CLIError_1.CLIError(`Unable to recognize page state! A screenshot has been dumped to ${path}. If this problem persists, try running with --mode=gui or --mode=debug`);
@@ -383,13 +425,15 @@ exports.login = {
383
425
  throw new Error("SAML response not found");
384
426
  }
385
427
  const samlResponse = querystring_1.default.parse(samlResponseData).SAMLResponse;
386
- debug("Found SAML response", samlResponse);
387
428
  if (!samlResponse) {
388
429
  throw new Error("SAML response not found");
389
430
  }
390
431
  else if (Array.isArray(samlResponse)) {
391
432
  throw new Error("SAML can't be an array");
392
433
  }
434
+ debug("Found SAML response", {
435
+ base64Length: samlResponse.length,
436
+ });
393
437
  return samlResponse;
394
438
  }
395
439
  finally {
@@ -407,16 +451,16 @@ exports.login = {
407
451
  _parseRolesFromSamlResponse(assertion) {
408
452
  debug("Converting assertion from base64 to UTF-8");
409
453
  const samlText = Buffer.from(assertion, "base64").toString("utf8");
410
- debug("Converted", samlText);
454
+ debug("Converted assertion from base64 to UTF-8", {
455
+ xmlLength: samlText.length,
456
+ });
411
457
  debug("Parsing SAML XML");
412
458
  const saml = (0, cheerio_1.load)(samlText, { xmlMode: true });
413
459
  debug("Looking for role SAML attribute");
414
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
415
- const roles = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue")
416
- .map(function () {
417
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
418
- // @ts-ignore
419
- const roleAndPrincipal = saml(this).text();
460
+ const roleSelection = saml("Attribute[Name='https://aws.amazon.com/SAML/Attributes/Role']>AttributeValue");
461
+ const roleNodes = roleSelection.toArray();
462
+ const roles = roleNodes.map((roleNode) => {
463
+ const roleAndPrincipal = saml(roleNode).text();
420
464
  const parts = roleAndPrincipal.split(",");
421
465
  // Role / Principal claims may be in either order
422
466
  const [roleIdx, principalIdx] = parts[0].includes(":role/")
@@ -425,9 +469,11 @@ exports.login = {
425
469
  const roleArn = parts[roleIdx].trim();
426
470
  const principalArn = parts[principalIdx].trim();
427
471
  return { roleArn, principalArn };
428
- })
429
- .get();
430
- debug("Found roles", roles);
472
+ });
473
+ debug("Found roles", roles.map((role) => ({
474
+ roleArn: this._redactArnForDebug(role.roleArn),
475
+ principalArn: this._redactArnForDebug(role.principalArn),
476
+ })));
431
477
  return roles;
432
478
  },
433
479
  /**
@@ -436,21 +482,15 @@ exports.login = {
436
482
  * @param {bool} [noPrompt] - Enable skipping of user prompting
437
483
  * @param {string} [defaultRoleArn] - The default role ARN
438
484
  * @param {number} [defaultDurationHours] - The default session duration in hours
485
+ * @param {string} [nonInteractiveModeLabel] - CLI flag label to reference in
486
+ * non-interactive errors
439
487
  * @returns {Promise.<{role: string, durationHours: number}>} The selected role and duration
440
488
  * @private
441
489
  */
442
- async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours) {
490
+ async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours, nonInteractiveModeLabel = "--no-prompt") {
491
+ var _a;
443
492
  let role;
444
- let durationHours = 1;
445
- if (defaultDurationHours) {
446
- const parsedDuration = parseInt(defaultDurationHours, 10);
447
- if (!Number.isNaN(parsedDuration) &&
448
- parsedDuration > 0 &&
449
- parsedDuration <= 12) {
450
- durationHours = parsedDuration;
451
- }
452
- }
453
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
493
+ let durationHours = (_a = (0, sessionDuration_1.parseSessionDurationHours)(defaultDurationHours)) !== null && _a !== void 0 ? _a : 1;
454
494
  const questions = [];
455
495
  if (roles.length === 0) {
456
496
  throw new CLIError_1.CLIError("No roles found in SAML response.");
@@ -462,11 +502,11 @@ exports.login = {
462
502
  else {
463
503
  if (noPrompt) {
464
504
  if (!defaultRoleArn) {
465
- throw new CLIError_1.CLIError("--no-prompt requires azure_default_role_arn when multiple roles are available.");
505
+ throw new CLIError_1.CLIError(`${nonInteractiveModeLabel} requires azure_default_role_arn when multiple roles are available.`);
466
506
  }
467
507
  role = roles.find((r) => r.roleArn === defaultRoleArn);
468
508
  if (!role) {
469
- throw new CLIError_1.CLIError(`Default role ARN '${defaultRoleArn}' was not found in the SAML response.`);
509
+ throw new CLIError_1.CLIError("Configured default role ARN was not found in the SAML response.");
470
510
  }
471
511
  debug("Valid role found. No need to ask.");
472
512
  }
@@ -494,23 +534,23 @@ exports.login = {
494
534
  name: "durationHours",
495
535
  message: "Session Duration Hours (up to 12):",
496
536
  type: "input",
497
- default: defaultDurationHours || 1,
498
- validate: (input) => {
499
- const num = Number(input);
500
- if (num > 0 && num <= 12)
501
- return true;
502
- return "Duration hours must be between 1 and 12";
503
- },
537
+ default: String(durationHours),
538
+ validate: sessionDuration_1.validateSessionDurationHours,
504
539
  });
505
540
  }
506
541
  // Don't prompt for questions if not needed, an unneeded TTYWRAP prevents node from exiting when
507
542
  // user is logged in and using multiple profiles --all-profiles and --no-prompt
508
543
  if (questions.length > 0) {
509
544
  const answers = await inquirer_1.default.prompt(questions);
510
- if (!role)
545
+ if (!role && answers.role) {
511
546
  role = roles.find((r) => r.roleArn === answers.role);
547
+ }
512
548
  if (answers.durationHours) {
513
- durationHours = parseInt(answers.durationHours, 10);
549
+ const parsedDurationHours = (0, sessionDuration_1.parseSessionDurationHours)(answers.durationHours);
550
+ if (parsedDurationHours === null) {
551
+ throw new CLIError_1.CLIError(sessionDuration_1.sessionDurationHoursValidationMessage);
552
+ }
553
+ durationHours = parsedDurationHours;
514
554
  }
515
555
  }
516
556
  if (!role) {
@@ -522,16 +562,19 @@ exports.login = {
522
562
  * Assume the role.
523
563
  * @param {string} profileName - The profile name
524
564
  * @param {string} assertion - The SAML assertion
525
- * @param {string} role - The role to assume
565
+ * @param {Role} role - The role to assume
526
566
  * @param {number} durationHours - The session duration in hours
527
- * @param {bool} awsNoVerifySsl - Whether to have the AWS CLI verify SSL
567
+ * @param {boolean} awsNoVerifySsl - Whether the AWS SDK STS client should
568
+ * disable TLS certificate verification
528
569
  * @param {string} region - AWS region, if specified
529
- * @returns {Promise} A promise
570
+ * @param {boolean} writeProfile - Whether to persist the credentials to the
571
+ * AWS shared credentials file
572
+ * @returns {Promise<AwsCredentials | undefined>} Retrieved credentials, or
573
+ * undefined when STS does not return them
530
574
  * @private
531
575
  */
532
- async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region) {
533
- var _a, _b, _c, _d, _e;
534
- console.log(`Assuming role ${role.roleArn} in region ${region}...`);
576
+ async _assumeRoleAsync(profileName, assertion, role, durationHours, awsNoVerifySsl, region, writeProfile = true) {
577
+ console.log(`Assuming selected role in region ${region}...`);
535
578
  let stsOptions = {};
536
579
  if (awsNoVerifySsl) {
537
580
  console.warn("WARNING: SSL certificate verification is disabled. " +
@@ -573,13 +616,25 @@ exports.login = {
573
616
  });
574
617
  if (!res.Credentials) {
575
618
  debug("Unable to get security credentials from AWS");
576
- return;
619
+ return undefined;
577
620
  }
578
- await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, {
579
- aws_access_key_id: (_a = res.Credentials.AccessKeyId) !== null && _a !== void 0 ? _a : "",
580
- aws_secret_access_key: (_b = res.Credentials.SecretAccessKey) !== null && _b !== void 0 ? _b : "",
581
- aws_session_token: (_c = res.Credentials.SessionToken) !== null && _c !== void 0 ? _c : "",
582
- aws_expiration: (_e = (_d = res.Credentials.Expiration) === null || _d === void 0 ? void 0 : _d.toISOString()) !== null && _e !== void 0 ? _e : "",
583
- });
621
+ if (!res.Credentials.AccessKeyId ||
622
+ !res.Credentials.SecretAccessKey ||
623
+ !res.Credentials.SessionToken ||
624
+ !res.Credentials.Expiration) {
625
+ debug("Received incomplete credentials from AWS");
626
+ throw new CLIError_1.CLIError("AWS returned incomplete credentials. One or more required fields " +
627
+ "(AccessKeyId, SecretAccessKey, SessionToken, Expiration) are missing.");
628
+ }
629
+ const credentials = {
630
+ aws_access_key_id: res.Credentials.AccessKeyId,
631
+ aws_secret_access_key: res.Credentials.SecretAccessKey,
632
+ aws_session_token: res.Credentials.SessionToken,
633
+ aws_expiration: res.Credentials.Expiration.toISOString(),
634
+ };
635
+ if (writeProfile) {
636
+ await awsConfig_1.awsConfig.setProfileCredentialsAsync(profileName, credentials);
637
+ }
638
+ return credentials;
584
639
  },
585
640
  };