az2aws 1.2.0 → 1.3.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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.2.0"
2
+ ".": "1.3.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.0](https://github.com/kuma0128/az2aws/compare/v1.2.0...v1.3.0) (2026-01-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add Comprehensive Loginstate Tests and Implementation ([#110](https://github.com/kuma0128/az2aws/issues/110)) ([09fce50](https://github.com/kuma0128/az2aws/commit/09fce501050731e2b0e5063ef8d2d5e4076827a1))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Apply Prettier formatting and add region logging to login functions ([#109](https://github.com/kuma0128/az2aws/issues/109)) ([26a5ef6](https://github.com/kuma0128/az2aws/commit/26a5ef636014f56ad77813f42e866a25b08be2f3))
14
+ * clarify session duration validation message ([#91](https://github.com/kuma0128/az2aws/issues/91)) ([22d87ec](https://github.com/kuma0128/az2aws/commit/22d87ec57318c6dc69d09e098d7e123d07df882e))
15
+ * correct typo 'occured' to 'occurred' in error message ([#86](https://github.com/kuma0128/az2aws/issues/86)) ([b0a75d2](https://github.com/kuma0128/az2aws/commit/b0a75d2c7cecaef7888d17a9880bcd63000713b8))
16
+ * enforce defaults for no-prompt role selection ([#90](https://github.com/kuma0128/az2aws/issues/90)) ([bea40f6](https://github.com/kuma0128/az2aws/commit/bea40f64a9dc6851b34a396fdd315752669c1d9d))
17
+
3
18
  ## [1.2.0](https://github.com/kuma0128/az2aws/compare/v1.1.3...v1.2.0) (2026-01-22)
4
19
 
5
20
 
package/README.md CHANGED
@@ -136,6 +136,10 @@ You can set defaults via environment variables (use with `--no-prompt`):
136
136
  - `AZURE_DEFAULT_USERNAME` / `AZURE_DEFAULT_PASSWORD` - Credentials
137
137
  - `AZURE_DEFAULT_ROLE_ARN` / `AZURE_DEFAULT_DURATION_HOURS` - AWS role settings
138
138
 
139
+ When using `--no-prompt` with multiple available roles, you must set
140
+ `AZURE_DEFAULT_ROLE_ARN` (or configure `azure_default_role_arn`) so the CLI can
141
+ select a role without prompting.
142
+
139
143
  To avoid storing passwords in bash history, use a leading space:
140
144
 
141
145
  HISTCONTROL=ignoreboth
@@ -176,6 +180,18 @@ You'll be prompted for username, password, and MFA if required. After login, use
176
180
  - Use `--mode gui --disable-gpu` on VMs or if rendering fails
177
181
  - Set `https_proxy` env var for corporate proxy
178
182
 
183
+ #### Troubleshooting
184
+
185
+ If you see device compliance errors (e.g., "Device UnSecured Or Non-Compliant"),
186
+ Try:
187
+ `--mode gui` and use your system Chrome via `BROWSER_CHROME_BIN`.
188
+
189
+ If you see "Unable to recognize page state!", Azure's login pages may have
190
+ changed. Try:
191
+
192
+ - `--mode gui` or `--mode debug`
193
+ - Filing an issue with the screenshot (`az2aws-unrecognized-state.png`) to help maintainers update selectors
194
+
179
195
  ## Automation
180
196
 
181
197
  Renew all profiles at once:
package/issue/issues.md CHANGED
@@ -726,4 +726,444 @@ Created an issues documentation file (`issue/issues.md`) to track and document a
726
726
  **Benefits:**
727
727
  - Centralized documentation of all project issues
728
728
  - Easier onboarding for new contributors
729
- - Clear tracking of issue status and priorities
729
+ - Clear tracking of issue status and priorities
730
+
731
+ ---
732
+
733
+ ## Performance Improvements
734
+
735
+ ---
736
+
737
+ ### Issue #57: Optimize keyboard input loop for clearing input fields
738
+
739
+ **Labels:** `performance`, `priority: high`
740
+
741
+ **Description:**
742
+ The current implementation sends 100 individual keyboard backspace events to clear input fields, causing unnecessary delay.
743
+
744
+ **Location:** `src/login.ts:97-99`, `src/login.ts:377-379`
745
+
746
+ **Current Code:**
747
+ ```typescript
748
+ for (let i = 0; i < 100; i++) {
749
+ await page.keyboard.press("Backspace");
750
+ }
751
+ ```
752
+
753
+ **Problem:**
754
+ - Sends 100 individual keyboard events causing unnecessary delay
755
+ - Each `press` call is an async operation executed serially
756
+
757
+ **Proposed Fix:**
758
+ Use `page.evaluate()` to clear input via DOM directly, or use `Ctrl+A` to select all then delete:
759
+ ```typescript
760
+ // Option 1: Select all and delete
761
+ await page.keyboard.down('Control');
762
+ await page.keyboard.press('a');
763
+ await page.keyboard.up('Control');
764
+ await page.keyboard.press('Backspace');
765
+
766
+ // Option 2: Clear via DOM
767
+ await page.evaluate((selector) => {
768
+ const input = document.querySelector(selector) as HTMLInputElement;
769
+ if (input) input.value = '';
770
+ }, inputSelector);
771
+ ```
772
+
773
+ **Expected Impact:** 100ms - 500ms reduction per input field
774
+
775
+ ---
776
+
777
+ ### Issue #58: Optimize page state polling loop
778
+
779
+ **Labels:** `performance`, `priority: high`
780
+
781
+ **Description:**
782
+ The state detection loop checks all 9 states from the beginning on every iteration, which is inefficient.
783
+
784
+ **Location:** `src/login.ts:814-874`
785
+
786
+ **Current Code:**
787
+ ```typescript
788
+ while (true) {
789
+ if (samlResponseData) break;
790
+
791
+ let foundState = false;
792
+ for (let i = 0; i < states.length; i++) {
793
+ const state = states[i];
794
+ let selected;
795
+ try {
796
+ selected = await page.$(state.selector);
797
+ } catch (err) {
798
+ break;
799
+ }
800
+
801
+ if (selected) {
802
+ foundState = true;
803
+ // ...
804
+ break;
805
+ }
806
+ }
807
+
808
+ if (!foundState) {
809
+ totalUnrecognizedDelay += DELAY_ON_UNRECOGNIZED_PAGE;
810
+ await Bluebird.delay(DELAY_ON_UNRECOGNIZED_PAGE); // 1 second wait
811
+ }
812
+ }
813
+ ```
814
+
815
+ **Problem:**
816
+ - Checks all 9 states from the beginning on every iteration
817
+ - Frequently occurring patterns may be at the end of the array
818
+ - Maximum 30 seconds polling with 1-second intervals
819
+ - Repeats all DOM operations when no state is found
820
+
821
+ **Proposed Fix:**
822
+ - Place frequently occurring states at the front of the array
823
+ - Cache the last matched state and prioritize it in the next check
824
+ - Consider combining multiple selectors with `waitForSelector`
825
+
826
+ **Expected Impact:** Potentially up to 30 seconds reduction in worst case scenarios
827
+
828
+ ---
829
+
830
+ ### Issue #59: Eliminate duplicate profile loading in loginAll
831
+
832
+ **Labels:** `performance`, `priority: medium`
833
+
834
+ **Description:**
835
+ In `loginAll()`, profile information is loaded from disk for each profile in the loop, causing redundant file I/O and INI parsing.
836
+
837
+ **Location:** `src/login.ts:527-556`, `src/login.ts:589-610`
838
+
839
+ **Current Code:**
840
+ ```typescript
841
+ async loginAll(...) {
842
+ const profiles = await awsConfig.getAllProfileNames(); // Load 1
843
+
844
+ for (const profile of profiles) {
845
+ if (!forceRefresh && !(await awsConfig.isProfileAboutToExpireAsync(profile))) {
846
+ continue;
847
+ }
848
+
849
+ await this.loginAsync(profile, ...); // Calls _loadProfileAsync internally
850
+ }
851
+ }
852
+ ```
853
+
854
+ **Problem:**
855
+ - Profile information is loaded from disk for each profile in the loop
856
+ - INI parsing is repeated multiple times
857
+
858
+ **Proposed Fix:**
859
+ - Cache profile information when `getAllProfileNames()` is called
860
+ - Or create a separate method to load all profiles at once
861
+
862
+ ---
863
+
864
+ ### Issue #60: Replace Lodash with native array methods
865
+
866
+ **Labels:** `performance`, `priority: low`
867
+
868
+ **Description:**
869
+ Lodash is used for small-scale operations where native array methods would be more efficient.
870
+
871
+ **Location:** `src/login.ts:175`, `src/login.ts:968`, `src/login.ts:979`, `src/login.ts:1005`
872
+
873
+ **Current Usage:**
874
+ ```typescript
875
+ _.map(accounts, "message")
876
+ _.sortBy(_.map(roles, "roleArn"))
877
+ _.find(roles, ["roleArn", defaultRoleArn])
878
+ ```
879
+
880
+ **Problem:**
881
+ - Lodash dependency for small-scale operations (typically <10 items)
882
+ - Native array methods are more efficient for these cases
883
+
884
+ **Proposed Fix:**
885
+ ```typescript
886
+ // Replace _.map(accounts, "message")
887
+ accounts.map(a => a.message)
888
+
889
+ // Replace _.sortBy(_.map(roles, "roleArn"))
890
+ roles.map(r => r.roleArn).sort()
891
+
892
+ // Replace _.find(roles, ["roleArn", defaultRoleArn])
893
+ roles.find(r => r.roleArn === defaultRoleArn)
894
+ ```
895
+
896
+ Consider removing Lodash from package.json if no longer needed elsewhere.
897
+
898
+ ---
899
+
900
+ ## Additional Bug Reports
901
+
902
+ ---
903
+
904
+ ### Issue #61: Incomplete error handling in page navigation
905
+
906
+ **Labels:** `bug`, `priority: high`
907
+
908
+ **Description:**
909
+ Page navigation errors are logged but not properly handled, allowing the process to continue in an undefined state.
910
+
911
+ **Location:** `src/login.ts:803-809`
912
+
913
+ **Current Code:**
914
+ ```typescript
915
+ try {
916
+ if (headless || (!headless && cliProxy)) {
917
+ await page.goto(url, { waitUntil: "domcontentloaded" });
918
+ } else {
919
+ await page.waitForNavigation({ waitUntil: "networkidle0" });
920
+ }
921
+ } catch (err) {
922
+ if (err instanceof Error) {
923
+ debug(`Error occured during loading the first page: ${err.message}`);
924
+ // Error is swallowed, logic continues
925
+ }
926
+ }
927
+ ```
928
+
929
+ **Problem:**
930
+ - Error is only logged, not thrown or handled
931
+ - May continue in an undefined state when redirect fails
932
+ - Typo: "occured" should be "occurred"
933
+
934
+ **Proposed Fix:**
935
+ ```typescript
936
+ try {
937
+ if (headless || (!headless && cliProxy)) {
938
+ await page.goto(url, { waitUntil: "domcontentloaded" });
939
+ } else {
940
+ await page.waitForNavigation({ waitUntil: "networkidle0" });
941
+ }
942
+ } catch (err) {
943
+ if (err instanceof Error) {
944
+ debug(`Error occurred during loading the first page: ${err.message}`);
945
+ throw new CLIError(`Failed to load login page: ${err.message}`);
946
+ }
947
+ throw err;
948
+ }
949
+ ```
950
+
951
+ ---
952
+
953
+ ### Issue #62: Missing NaN validation in duration hours input
954
+
955
+ **Labels:** `bug`, `priority: medium`
956
+
957
+ **Description:**
958
+ The duration hours input validation does not check for NaN values when converting string input to number.
959
+
960
+ **Location:** `src/configureProfileAsync.ts:50-56`
961
+
962
+ **Current Code:**
963
+ ```typescript
964
+ validate: (input): boolean | string => {
965
+ input = Number(input);
966
+ if (input > 0 && input <= 12) return true;
967
+ return "Duration hours must be between 0 and 12";
968
+ },
969
+ ```
970
+
971
+ **Problem:**
972
+ - Only validates after String to Number conversion
973
+ - No NaN check - `Number("abc")` returns NaN which fails the condition silently
974
+ - Error message says "between 0 and 12" but code checks `> 0` (exclusive)
975
+
976
+ **Proposed Fix:**
977
+ ```typescript
978
+ validate: (input): boolean | string => {
979
+ const num = Number(input);
980
+ if (Number.isNaN(num)) return "Please enter a valid number";
981
+ if (num > 0 && num <= 12) return true;
982
+ return "Duration hours must be greater than 0 and at most 12";
983
+ },
984
+ ```
985
+
986
+ ---
987
+
988
+ ### Issue #63: Fixed delay for browser initialization is environment-dependent
989
+
990
+ **Labels:** `bug`, `priority: medium`
991
+
992
+ **Description:**
993
+ A fixed 200ms delay is used to wait for browser initialization, which may be insufficient in slower environments.
994
+
995
+ **Location:** `src/login.ts:750`
996
+
997
+ **Current Code:**
998
+ ```typescript
999
+ browser = await puppeteer.launch(launchParams);
1000
+
1001
+ // Wait for a bit as sometimes the browser isn't ready.
1002
+ await Bluebird.delay(200);
1003
+
1004
+ const pages = await browser.pages();
1005
+ ```
1006
+
1007
+ **Problem:**
1008
+ - 200ms fixed delay is environment-dependent
1009
+ - May cause instability in CI environments or slower machines
1010
+ - May cause unnecessary delay in faster environments
1011
+
1012
+ **Proposed Fix:**
1013
+ Use a more reliable wait mechanism for browser initialization:
1014
+ ```typescript
1015
+ browser = await puppeteer.launch(launchParams);
1016
+
1017
+ // Wait for browser to be ready
1018
+ const pages = await browser.pages();
1019
+ if (pages.length === 0) {
1020
+ // Wait for default page to be created
1021
+ await browser.waitForTarget(target => target.type() === 'page');
1022
+ }
1023
+ ```
1024
+
1025
+ ---
1026
+
1027
+ ### Issue #64: Event listener memory leak risk in SAML response handling
1028
+
1029
+ **Labels:** `bug`, `priority: medium`
1030
+
1031
+ **Description:**
1032
+ The request event listener for capturing SAML responses is not explicitly cleaned up after use.
1033
+
1034
+ **Location:** `src/login.ts:761-790`
1035
+
1036
+ **Current Code:**
1037
+ ```typescript
1038
+ const samlResponsePromise = new Promise((resolve) => {
1039
+ page.on("request", (req: HTTPRequest) => {
1040
+ // Event listener may remain registered
1041
+ const reqUrl = req.url();
1042
+ if (reqUrl === AWS_SAML_ENDPOINT || reqUrl === AWS_GOV_SAML_ENDPOINT || reqUrl === AWS_CN_SAML_ENDPOINT) {
1043
+ // ...
1044
+ resolve(samlResponse);
1045
+ }
1046
+ });
1047
+ });
1048
+ ```
1049
+
1050
+ **Problem:**
1051
+ - Event listener is not explicitly cleaned up
1052
+ - Potential memory leak if page is reused or not properly closed
1053
+
1054
+ **Proposed Fix:**
1055
+ ```typescript
1056
+ const samlResponsePromise = new Promise((resolve) => {
1057
+ const requestHandler = (req: HTTPRequest) => {
1058
+ const reqUrl = req.url();
1059
+ if (reqUrl === AWS_SAML_ENDPOINT || reqUrl === AWS_GOV_SAML_ENDPOINT || reqUrl === AWS_CN_SAML_ENDPOINT) {
1060
+ // ...
1061
+ page.off("request", requestHandler); // Clean up listener
1062
+ resolve(samlResponse);
1063
+ }
1064
+ };
1065
+ page.on("request", requestHandler);
1066
+ });
1067
+ ```
1068
+
1069
+ Or use `page.once()` if appropriate for the use case.
1070
+
1071
+ ---
1072
+
1073
+ ### Issue #65: Typo in error message
1074
+
1075
+ **Labels:** `bug`, `priority: low`
1076
+
1077
+ **Description:**
1078
+ There is a typo in the error debug message.
1079
+
1080
+ **Location:** `src/login.ts:806`
1081
+
1082
+ **Current:** `"Error occured during loading the first page"`
1083
+ **Should be:** `"Error occurred during loading the first page"`
1084
+
1085
+ **Proposed Fix:**
1086
+ ```typescript
1087
+ debug(`Error occurred during loading the first page: ${err.message}`);
1088
+ ```
1089
+
1090
+ ---
1091
+
1092
+ ## Code Quality Improvements
1093
+
1094
+ ---
1095
+
1096
+ ### Issue #66: Reduce `any` type usage and eslint-disable comments
1097
+
1098
+ **Labels:** `code-quality`, `priority: medium`
1099
+
1100
+ **Description:**
1101
+ There are several places where `any` type and `eslint-disable` comments are used, reducing type safety.
1102
+
1103
+ **Locations:**
1104
+ - `src/awsConfig.ts:149` - `any` type for parsed INI
1105
+ - `src/login.ts:141-144` - eslint-disable comment
1106
+
1107
+ **Current Code:**
1108
+ ```typescript
1109
+ // awsConfig.ts:149
1110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1111
+ const parsedIni: any = ini.parse(data);
1112
+
1113
+ // login.ts:141-144
1114
+ const aadTileMessage: string = await page.evaluate(
1115
+ // eslint-disable-next-line
1116
+ (a) => a?.textContent ?? "",
1117
+ aadTile
1118
+ );
1119
+ ```
1120
+
1121
+ **Proposed Fix:**
1122
+ - Define proper TypeScript interfaces for INI parsing results
1123
+ - Use type guards instead of eslint-disable comments
1124
+ - Consider using a typed INI parser or create proper type definitions
1125
+
1126
+ ---
1127
+
1128
+ ### Issue #67: Add missing test coverage for critical functions
1129
+
1130
+ **Labels:** `testing`, `priority: medium`
1131
+
1132
+ **Description:**
1133
+ Several critical functions lack test coverage.
1134
+
1135
+ **Location:** `src/login.test.ts`
1136
+
1137
+ **Missing Tests:**
1138
+ - `_performLoginAsync()` - the most complex function with browser automation
1139
+ - `_parseRolesFromSamlResponse()` - SAML parsing logic
1140
+ - Edge cases: empty SAML response, zero roles, malformed XML, etc.
1141
+
1142
+ **Proposed Tests:**
1143
+ ```typescript
1144
+ describe("login._performLoginAsync", () => {
1145
+ // Mock puppeteer browser behavior
1146
+ // Test SAML session completion sequence
1147
+ });
1148
+
1149
+ describe("login._parseRolesFromSamlResponse", () => {
1150
+ it("should parse valid SAML response with multiple roles", () => {});
1151
+ it("should handle empty SAML response", () => {});
1152
+ it("should handle special characters in role names", () => {});
1153
+ it("should handle UTF-8 encoded content", () => {});
1154
+ });
1155
+ ```
1156
+
1157
+ ---
1158
+
1159
+ ### [RESOLVED] Issue #68: Prettier formatting issues in login files
1160
+
1161
+ **Labels:** `code-quality`, `priority: low`
1162
+
1163
+ **Description:**
1164
+ Prettier detected code style issues in `src/login.ts` and `src/login.test.ts` that caused `yarn lint` to fail.
1165
+
1166
+ **Location:** `src/login.ts`, `src/login.test.ts`
1167
+
1168
+ **Resolution:**
1169
+ Fixed by running `yarn prettier --write` on the affected files.
@@ -53,7 +53,7 @@ async function configureProfileAsync(profileName) {
53
53
  input = Number(input);
54
54
  if (input > 0 && input <= 12)
55
55
  return true;
56
- return "Duration hours must be between 0 and 12";
56
+ return "Duration hours must be between 1 and 12";
57
57
  },
58
58
  },
59
59
  {
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ program
17
17
  .option("-c, --configure", "Configure the profile")
18
18
  .option("-m, --mode <mode>", "'cli' to hide the login page and perform the login through the CLI (default behavior), 'gui' to perform the login through the Azure GUI (more reliable but only works on GUI operating system), 'debug' to show the login page but perform the login through the CLI (useful to debug issues with the CLI login)")
19
19
  .option("--no-sandbox", "Disable the Puppeteer sandbox (usually necessary on Linux)")
20
- .option("--no-prompt", "Do not prompt for input and accept the default choice", false)
20
+ .option("--no-prompt", "Do not prompt for input and accept the default choice")
21
21
  .option("--enable-chrome-network-service", "Enable Chromium's Network Service (needed when login provider redirects with 3XX)")
22
22
  .option("--no-verify-ssl", "Disable SSL Peer Verification for connections to AWS")
23
23
  .option("--enable-chrome-seamless-sso", "Enable Chromium's pass-through authentication with Azure Active Directory Seamless Single Sign-On")
package/lib/login.js CHANGED
@@ -21,6 +21,7 @@ const paths_1 = require("./paths");
21
21
  const mkdirp_1 = __importDefault(require("mkdirp"));
22
22
  const https_1 = require("https");
23
23
  const node_http_handler_1 = require("@smithy/node-http-handler");
24
+ const loginStates_1 = require("./loginStates");
24
25
  const debug = (0, debug_1.default)("az2aws");
25
26
  const WIDTH = 425;
26
27
  const HEIGHT = 550;
@@ -31,326 +32,6 @@ const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
31
32
  const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
32
33
  const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
33
34
  const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
34
- /**
35
- * To proxy the input/output of the Azure login page, it's easiest to run a loop that
36
- * monitors the state of the page and then perform the corresponding CLI behavior.
37
- * The states have a name that is used for the debug messages, a selector that is used
38
- * with puppeteer's page.$(selector) to determine if the state is active, and a handler
39
- * that is called if the state is active.
40
- */
41
- const states = [
42
- {
43
- name: "username input",
44
- selector: `input[name="loginfmt"]:not(.moveOffScreen)`,
45
- async handler(page, _selected, noPrompt, defaultUsername) {
46
- const error = await page.$(".alert-error");
47
- if (error) {
48
- debug("Found error message. Displaying");
49
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
50
- const errorMessage = await page.evaluate(
51
- // eslint-disable-next-line
52
- (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
53
- console.log(errorMessage);
54
- }
55
- let username;
56
- if (noPrompt && defaultUsername) {
57
- debug("Not prompting user for username");
58
- username = defaultUsername;
59
- }
60
- else {
61
- debug("Prompting user for username");
62
- ({ username } = await inquirer_1.default.prompt([
63
- {
64
- name: "username",
65
- message: "Username:",
66
- default: defaultUsername,
67
- },
68
- ]));
69
- }
70
- debug("Waiting for username input to be visible");
71
- await page.waitForSelector(`input[name="loginfmt"]`, {
72
- visible: true,
73
- timeout: 60000,
74
- });
75
- debug("Focusing on username input");
76
- await page.focus(`input[name="loginfmt"]`);
77
- debug("Clearing input");
78
- for (let i = 0; i < 100; i++) {
79
- await page.keyboard.press("Backspace");
80
- }
81
- debug("Typing username");
82
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
83
- await page.keyboard.type(username);
84
- await bluebird_1.default.delay(500);
85
- debug("Waiting for submit button to be visible");
86
- await page.waitForSelector(`input[type=submit]`, {
87
- visible: true,
88
- timeout: 60000,
89
- });
90
- debug("Submitting form");
91
- await page.click("input[type=submit]");
92
- await bluebird_1.default.delay(500);
93
- debug("Waiting for submission to finish");
94
- await Promise.race([
95
- page.waitForSelector(`input[name=loginfmt].has-error,input[name=loginfmt].moveOffScreen`, { timeout: 60000 }),
96
- (async () => {
97
- await bluebird_1.default.delay(1000);
98
- await page.waitForSelector(`input[name=loginfmt]`, {
99
- hidden: true,
100
- timeout: 60000,
101
- });
102
- })(),
103
- ]);
104
- },
105
- },
106
- {
107
- name: "account selection",
108
- selector: `#aadTile > div > div.table-cell.tile-img > img`,
109
- async handler(page) {
110
- debug("Multiple accounts associated with username.");
111
- const aadTile = await page.$("#aadTileTitle");
112
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
113
- const aadTileMessage = await page.evaluate(
114
- // eslint-disable-next-line
115
- (a) => { var _a; return (_a = a === null || a === void 0 ? void 0 : a.textContent) !== null && _a !== void 0 ? _a : ""; }, aadTile);
116
- const msaTile = await page.$("#msaTileTitle");
117
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
118
- const msaTileMessage = await page.evaluate(
119
- // eslint-disable-next-line
120
- (m) => { var _a; return (_a = m === null || m === void 0 ? void 0 : m.textContent) !== null && _a !== void 0 ? _a : ""; }, msaTile);
121
- const accounts = [
122
- aadTile ? { message: aadTileMessage, selector: "#aadTileTitle" } : null,
123
- msaTile ? { message: msaTileMessage, selector: "#msaTileTitle" } : null,
124
- ].filter((a) => a !== null);
125
- let account;
126
- if (accounts.length === 0) {
127
- throw new CLIError_1.CLIError("No accounts found on account selection screen.");
128
- }
129
- else if (accounts.length === 1) {
130
- account = accounts[0];
131
- }
132
- else {
133
- debug("Asking user to choose account");
134
- console.log("It looks like this Username is used with more than one account from Microsoft. Which one do you want to use?");
135
- const answers = await inquirer_1.default.prompt([
136
- {
137
- name: "account",
138
- message: "Account:",
139
- type: "list",
140
- choices: lodash_1.default.map(accounts, "message"),
141
- default: aadTileMessage,
142
- },
143
- ]);
144
- account = lodash_1.default.find(accounts, ["message", answers.account]);
145
- }
146
- if (!account) {
147
- throw new Error("Unable to find account");
148
- }
149
- debug(`Proceeding with account ${account.selector}`);
150
- await page.click(account.selector);
151
- await bluebird_1.default.delay(500);
152
- },
153
- },
154
- {
155
- name: "passwordless",
156
- selector: `input[value='Send notification']`,
157
- async handler(page) {
158
- debug("Sending notification");
159
- // eslint-disable-next-line
160
- await page.click("input[value='Send notification']");
161
- debug("Waiting for auth code");
162
- // eslint-disable-next-line
163
- await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
164
- visible: true,
165
- timeout: 60000,
166
- });
167
- debug("Printing the message displayed");
168
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
169
- const messageElement = await page.$("#idDiv_RemoteNGC_PollingDescription");
170
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
171
- const codeElement = await page.$("#idRemoteNGC_DisplaySign");
172
- // eslint-disable-next-line
173
- const message = await page.evaluate(
174
- // eslint-disable-next-line
175
- (el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, messageElement);
176
- console.log(message);
177
- debug("Printing the auth code");
178
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
179
- const authCode = await page.evaluate(
180
- // eslint-disable-next-line
181
- (el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, codeElement);
182
- console.log(authCode);
183
- debug("Waiting for response");
184
- await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
185
- hidden: true,
186
- timeout: 60000,
187
- });
188
- },
189
- },
190
- {
191
- name: "password input",
192
- selector: `input[name="Password"]:not(.moveOffScreen),input[name="passwd"]:not(.moveOffScreen)`,
193
- async handler(page, _selected, noPrompt, _defaultUsername, defaultPassword) {
194
- const error = await page.$(".alert-error");
195
- if (error) {
196
- debug("Found error message. Displaying");
197
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
198
- const errorMessage = await page.evaluate(
199
- // eslint-disable-next-line
200
- (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
201
- console.log(errorMessage);
202
- defaultPassword = ""; // Password error. Unset the default and allow user to enter it.
203
- }
204
- let password;
205
- if (noPrompt && defaultPassword) {
206
- debug("Not prompting user for password");
207
- password = defaultPassword;
208
- }
209
- else {
210
- debug("Prompting user for password");
211
- ({ password } = await inquirer_1.default.prompt([
212
- {
213
- name: "password",
214
- message: "Password:",
215
- type: "password",
216
- },
217
- ]));
218
- }
219
- debug("Focusing on password input");
220
- await page.focus(`input[name="Password"],input[name="passwd"]`);
221
- debug("Typing password");
222
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
223
- await page.keyboard.type(password);
224
- debug("Submitting form");
225
- await page.click("span[class=submit],input[type=submit]");
226
- debug("Waiting for a delay");
227
- await bluebird_1.default.delay(500);
228
- },
229
- },
230
- {
231
- name: "TFA instructions",
232
- selector: `#idDiv_SAOTCAS_Description`,
233
- async handler(page, selected) {
234
- const descriptionMessage = await page.evaluate(
235
- // eslint-disable-next-line
236
- (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
237
- console.log(descriptionMessage);
238
- try {
239
- debug("Waiting for authentication code to be displayed");
240
- await page.waitForSelector("#idRichContext_DisplaySign", {
241
- visible: true,
242
- timeout: 5000,
243
- });
244
- debug("Checking if authentication code is displayed");
245
- const authenticationCodeElement = await page.$("#idRichContext_DisplaySign");
246
- debug("Reading the authentication code");
247
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
248
- const authenticationCode = await page.evaluate(
249
- // eslint-disable-next-line
250
- (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, authenticationCodeElement);
251
- debug("Printing the authentication code to console");
252
- console.log(authenticationCode);
253
- }
254
- catch (_a) {
255
- debug("No authentication code found on page");
256
- }
257
- debug("Waiting for response");
258
- await page.waitForSelector(`#idDiv_SAOTCAS_Description`, {
259
- hidden: true,
260
- timeout: 60000,
261
- });
262
- },
263
- },
264
- {
265
- name: "TFA failed",
266
- selector: `#idDiv_SAASDS_Description,#idDiv_SAASTO_Description`,
267
- async handler(page, selected) {
268
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
269
- const descriptionMessage = await page.evaluate(
270
- // eslint-disable-next-line
271
- (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
272
- throw new CLIError_1.CLIError(descriptionMessage);
273
- },
274
- },
275
- {
276
- name: "TFA code input",
277
- selector: "input[name=otc]:not(.moveOffScreen)",
278
- async handler(page) {
279
- const error = await page.$(".alert-error");
280
- if (error) {
281
- debug("Found error message. Displaying");
282
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
283
- const errorMessage = await page.evaluate(
284
- // eslint-disable-next-line
285
- (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
286
- console.log(errorMessage);
287
- }
288
- else {
289
- const description = await page.$("#idDiv_SAOTCC_Description");
290
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
291
- const descriptionMessage = await page.evaluate(
292
- // eslint-disable-next-line
293
- (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
294
- console.log(descriptionMessage);
295
- }
296
- const { verificationCode } = await inquirer_1.default.prompt([
297
- {
298
- name: "verificationCode",
299
- message: "Verification Code:",
300
- },
301
- ]);
302
- debug("Focusing on verification code input");
303
- await page.focus(`input[name="otc"]`);
304
- debug("Clearing input");
305
- for (let i = 0; i < 100; i++) {
306
- await page.keyboard.press("Backspace");
307
- }
308
- debug("Typing verification code");
309
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
310
- await page.keyboard.type(verificationCode);
311
- debug("Submitting form");
312
- await page.click("input[type=submit]");
313
- debug("Waiting for submission to finish");
314
- await Promise.race([
315
- page.waitForSelector(`input[name=otc].has-error,input[name=otc].moveOffScreen`, { timeout: 60000 }),
316
- (async () => {
317
- await bluebird_1.default.delay(1000);
318
- await page.waitForSelector(`input[name=otc]`, {
319
- hidden: true,
320
- timeout: 60000,
321
- });
322
- })(),
323
- ]);
324
- },
325
- },
326
- {
327
- name: "Remember me",
328
- selector: `#KmsiDescription`,
329
- async handler(page, _selected, _noPrompt, _defaultUsername, _defaultPassword, rememberMe) {
330
- if (rememberMe) {
331
- debug("Clicking remember me button");
332
- await page.click("#idSIButton9");
333
- }
334
- else {
335
- debug("Clicking don't remember button");
336
- await page.click("#idBtn_Back");
337
- }
338
- debug("Waiting for a delay");
339
- await bluebird_1.default.delay(500);
340
- },
341
- },
342
- {
343
- name: "Service exception",
344
- selector: "#service_exception_message",
345
- async handler(page, selected) {
346
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
347
- const descriptionMessage = await page.evaluate(
348
- // eslint-disable-next-line
349
- (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
350
- throw new CLIError_1.CLIError(descriptionMessage);
351
- },
352
- },
353
- ];
354
35
  exports.login = {
355
36
  async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu) {
356
37
  let headless, cliProxy;
@@ -370,6 +51,12 @@ exports.login = {
370
51
  throw new CLIError_1.CLIError("Invalid mode");
371
52
  }
372
53
  const profile = await this._loadProfileAsync(profileName);
54
+ console.log(`Using AWS region ${profile.region || "(from AWS SDK defaults)"}`);
55
+ if (profile.region && profile.region.startsWith("us-gov")) {
56
+ console.warn("GovCloud region detected in profile. Note: Other AWS CLI operations " +
57
+ "will use your AWS CLI default region. If needed, set it to match " +
58
+ "this GovCloud region (us-gov-west-1 or us-gov-east-1).");
59
+ }
373
60
  let assertionConsumerServiceURL = AWS_SAML_ENDPOINT;
374
61
  if (profile.region && profile.region.startsWith("us-gov")) {
375
62
  assertionConsumerServiceURL = AWS_GOV_SAML_ENDPOINT;
@@ -594,7 +281,7 @@ exports.login = {
594
281
  if (err instanceof Error) {
595
282
  // An error will be thrown if you're still logged in cause the page.goto ot waitForNavigation
596
283
  // will be a redirect to AWS. That's usually OK
597
- debug(`Error occured during loading the first page: ${err.message}`);
284
+ debug(`Error occurred during loading the first page: ${err.message}`);
598
285
  }
599
286
  }
600
287
  if (cliProxy) {
@@ -604,8 +291,8 @@ exports.login = {
604
291
  if (samlResponseData)
605
292
  break;
606
293
  let foundState = false;
607
- for (let i = 0; i < states.length; i++) {
608
- const state = states[i];
294
+ for (let i = 0; i < loginStates_1.states.length; i++) {
295
+ const state = loginStates_1.states[i];
609
296
  let selected;
610
297
  try {
611
298
  selected = await page.$(state.selector);
@@ -710,7 +397,13 @@ exports.login = {
710
397
  */
711
398
  async _askUserForRoleAndDurationAsync(roles, noPrompt, defaultRoleArn, defaultDurationHours) {
712
399
  let role;
713
- let durationHours = parseInt(defaultDurationHours, 10) || 1;
400
+ let durationHours = 1;
401
+ if (defaultDurationHours) {
402
+ const parsedDuration = parseInt(defaultDurationHours, 10);
403
+ if (!Number.isNaN(parsedDuration) && parsedDuration > 0) {
404
+ durationHours = parsedDuration;
405
+ }
406
+ }
714
407
  const questions = [];
715
408
  if (roles.length === 0) {
716
409
  throw new CLIError_1.CLIError("No roles found in SAML response.");
@@ -720,10 +413,14 @@ exports.login = {
720
413
  role = roles[0];
721
414
  }
722
415
  else {
723
- if (noPrompt && defaultRoleArn) {
416
+ if (noPrompt) {
417
+ if (!defaultRoleArn) {
418
+ throw new CLIError_1.CLIError("--no-prompt requires azure_default_role_arn when multiple roles are available.");
419
+ }
724
420
  role = lodash_1.default.find(roles, ["roleArn", defaultRoleArn]);
725
- }
726
- if (role) {
421
+ if (!role) {
422
+ throw new CLIError_1.CLIError(`Default role ARN '${defaultRoleArn}' was not found in the SAML response.`);
423
+ }
727
424
  debug("Valid role found. No need to ask.");
728
425
  }
729
426
  else {
@@ -737,8 +434,13 @@ exports.login = {
737
434
  });
738
435
  }
739
436
  }
740
- if (noPrompt && defaultDurationHours) {
741
- debug("Default durationHours found. No need to ask.");
437
+ if (noPrompt) {
438
+ if (!defaultDurationHours) {
439
+ debug("No default durationHours set. Using 1 hour.");
440
+ }
441
+ else {
442
+ debug("Default durationHours found. No need to ask.");
443
+ }
742
444
  }
743
445
  else {
744
446
  questions.push({
@@ -750,7 +452,7 @@ exports.login = {
750
452
  input = Number(input);
751
453
  if (input > 0 && input <= 12)
752
454
  return true;
753
- return "Duration hours must be between 0 and 12";
455
+ return "Duration hours must be between 1 and 12";
754
456
  },
755
457
  });
756
458
  }
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.states = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const bluebird_1 = __importDefault(require("bluebird"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const debug_1 = __importDefault(require("debug"));
11
+ const CLIError_1 = require("./CLIError");
12
+ const debug = (0, debug_1.default)("az2aws");
13
+ /**
14
+ * To proxy the input/output of the Azure login page, it's easiest to run a loop that
15
+ * monitors the state of the page and then perform the corresponding CLI behavior.
16
+ * The states have a name that is used for the debug messages, a selector that is used
17
+ * with puppeteer's page.$(selector) to determine if the state is active, and a handler
18
+ * that is called if the state is active.
19
+ */
20
+ exports.states = [
21
+ {
22
+ name: "username input",
23
+ selector: `input[name="loginfmt"]:not(.moveOffScreen)`,
24
+ async handler(page, _selected, noPrompt, defaultUsername) {
25
+ const error = await page.$(".alert-error");
26
+ if (error) {
27
+ debug("Found error message. Displaying");
28
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
29
+ const errorMessage = await page.evaluate(
30
+ // eslint-disable-next-line
31
+ (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
32
+ console.log(errorMessage);
33
+ }
34
+ let username;
35
+ if (noPrompt && defaultUsername) {
36
+ debug("Not prompting user for username");
37
+ username = defaultUsername;
38
+ }
39
+ else {
40
+ debug("Prompting user for username");
41
+ ({ username } = await inquirer_1.default.prompt([
42
+ {
43
+ name: "username",
44
+ message: "Username:",
45
+ default: defaultUsername,
46
+ },
47
+ ]));
48
+ }
49
+ debug("Waiting for username input to be visible");
50
+ await page.waitForSelector(`input[name="loginfmt"]`, {
51
+ visible: true,
52
+ timeout: 60000,
53
+ });
54
+ debug("Focusing on username input");
55
+ await page.focus(`input[name="loginfmt"]`);
56
+ debug("Clearing input");
57
+ for (let i = 0; i < 100; i++) {
58
+ await page.keyboard.press("Backspace");
59
+ }
60
+ debug("Typing username");
61
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
62
+ await page.keyboard.type(username);
63
+ await bluebird_1.default.delay(500);
64
+ debug("Waiting for submit button to be visible");
65
+ await page.waitForSelector(`input[type=submit]`, {
66
+ visible: true,
67
+ timeout: 60000,
68
+ });
69
+ debug("Submitting form");
70
+ await page.click("input[type=submit]");
71
+ await bluebird_1.default.delay(500);
72
+ debug("Waiting for submission to finish");
73
+ await Promise.race([
74
+ page.waitForSelector(`input[name=loginfmt].has-error,input[name=loginfmt].moveOffScreen`, { timeout: 60000 }),
75
+ (async () => {
76
+ await bluebird_1.default.delay(1000);
77
+ await page.waitForSelector(`input[name=loginfmt]`, {
78
+ hidden: true,
79
+ timeout: 60000,
80
+ });
81
+ })(),
82
+ ]);
83
+ },
84
+ },
85
+ {
86
+ name: "account selection",
87
+ selector: `#aadTile > div > div.table-cell.tile-img > img`,
88
+ async handler(page) {
89
+ debug("Multiple accounts associated with username.");
90
+ const aadTile = await page.$("#aadTileTitle");
91
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
92
+ const aadTileMessage = await page.evaluate(
93
+ // eslint-disable-next-line
94
+ (a) => { var _a; return (_a = a === null || a === void 0 ? void 0 : a.textContent) !== null && _a !== void 0 ? _a : ""; }, aadTile);
95
+ const msaTile = await page.$("#msaTileTitle");
96
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
97
+ const msaTileMessage = await page.evaluate(
98
+ // eslint-disable-next-line
99
+ (m) => { var _a; return (_a = m === null || m === void 0 ? void 0 : m.textContent) !== null && _a !== void 0 ? _a : ""; }, msaTile);
100
+ const accounts = [
101
+ aadTile ? { message: aadTileMessage, selector: "#aadTileTitle" } : null,
102
+ msaTile ? { message: msaTileMessage, selector: "#msaTileTitle" } : null,
103
+ ].filter((a) => a !== null);
104
+ let account;
105
+ if (accounts.length === 0) {
106
+ throw new CLIError_1.CLIError("No accounts found on account selection screen.");
107
+ }
108
+ else if (accounts.length === 1) {
109
+ account = accounts[0];
110
+ }
111
+ else {
112
+ debug("Asking user to choose account");
113
+ console.log("It looks like this Username is used with more than one account from Microsoft. Which one do you want to use?");
114
+ const answers = await inquirer_1.default.prompt([
115
+ {
116
+ name: "account",
117
+ message: "Account:",
118
+ type: "list",
119
+ choices: lodash_1.default.map(accounts, "message"),
120
+ default: aadTileMessage,
121
+ },
122
+ ]);
123
+ account = lodash_1.default.find(accounts, ["message", answers.account]);
124
+ }
125
+ if (!account) {
126
+ throw new Error("Unable to find account");
127
+ }
128
+ debug(`Proceeding with account ${account.selector}`);
129
+ await page.click(account.selector);
130
+ await bluebird_1.default.delay(500);
131
+ },
132
+ },
133
+ {
134
+ name: "passwordless",
135
+ selector: `input[value='Send notification']`,
136
+ async handler(page) {
137
+ debug("Sending notification");
138
+ // eslint-disable-next-line
139
+ await page.click("input[value='Send notification']");
140
+ debug("Waiting for auth code");
141
+ // eslint-disable-next-line
142
+ await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
143
+ visible: true,
144
+ timeout: 60000,
145
+ });
146
+ debug("Printing the message displayed");
147
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
148
+ const messageElement = await page.$("#idDiv_RemoteNGC_PollingDescription");
149
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
150
+ const codeElement = await page.$("#idRemoteNGC_DisplaySign");
151
+ // eslint-disable-next-line
152
+ const message = await page.evaluate(
153
+ // eslint-disable-next-line
154
+ (el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, messageElement);
155
+ console.log(message);
156
+ debug("Printing the auth code");
157
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
158
+ const authCode = await page.evaluate(
159
+ // eslint-disable-next-line
160
+ (el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, codeElement);
161
+ console.log(authCode);
162
+ debug("Waiting for response");
163
+ await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
164
+ hidden: true,
165
+ timeout: 60000,
166
+ });
167
+ },
168
+ },
169
+ {
170
+ name: "password input",
171
+ selector: `input[name="Password"]:not(.moveOffScreen),input[name="passwd"]:not(.moveOffScreen)`,
172
+ async handler(page, _selected, noPrompt, _defaultUsername, defaultPassword) {
173
+ const error = await page.$(".alert-error");
174
+ if (error) {
175
+ debug("Found error message. Displaying");
176
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
177
+ const errorMessage = await page.evaluate(
178
+ // eslint-disable-next-line
179
+ (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
180
+ console.log(errorMessage);
181
+ defaultPassword = ""; // Password error. Unset the default and allow user to enter it.
182
+ }
183
+ let password;
184
+ if (noPrompt && defaultPassword) {
185
+ debug("Not prompting user for password");
186
+ password = defaultPassword;
187
+ }
188
+ else {
189
+ debug("Prompting user for password");
190
+ ({ password } = await inquirer_1.default.prompt([
191
+ {
192
+ name: "password",
193
+ message: "Password:",
194
+ type: "password",
195
+ },
196
+ ]));
197
+ }
198
+ debug("Focusing on password input");
199
+ await page.focus(`input[name="Password"],input[name="passwd"]`);
200
+ debug("Typing password");
201
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
202
+ await page.keyboard.type(password);
203
+ debug("Submitting form");
204
+ await page.click("span[class=submit],input[type=submit]");
205
+ debug("Waiting for a delay");
206
+ await bluebird_1.default.delay(500);
207
+ },
208
+ },
209
+ {
210
+ name: "TFA instructions",
211
+ selector: `#idDiv_SAOTCAS_Description`,
212
+ async handler(page, selected) {
213
+ const descriptionMessage = await page.evaluate(
214
+ // eslint-disable-next-line
215
+ (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
216
+ console.log(descriptionMessage);
217
+ try {
218
+ debug("Waiting for authentication code to be displayed");
219
+ await page.waitForSelector("#idRichContext_DisplaySign", {
220
+ visible: true,
221
+ timeout: 5000,
222
+ });
223
+ debug("Checking if authentication code is displayed");
224
+ const authenticationCodeElement = await page.$("#idRichContext_DisplaySign");
225
+ debug("Reading the authentication code");
226
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
227
+ const authenticationCode = await page.evaluate(
228
+ // eslint-disable-next-line
229
+ (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, authenticationCodeElement);
230
+ debug("Printing the authentication code to console");
231
+ console.log(authenticationCode);
232
+ }
233
+ catch (_a) {
234
+ debug("No authentication code found on page");
235
+ }
236
+ debug("Waiting for response");
237
+ await page.waitForSelector(`#idDiv_SAOTCAS_Description`, {
238
+ hidden: true,
239
+ timeout: 60000,
240
+ });
241
+ },
242
+ },
243
+ {
244
+ name: "TFA failed",
245
+ selector: `#idDiv_SAASDS_Description,#idDiv_SAASTO_Description`,
246
+ async handler(page, selected) {
247
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
248
+ const descriptionMessage = await page.evaluate(
249
+ // eslint-disable-next-line
250
+ (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
251
+ throw new CLIError_1.CLIError(descriptionMessage);
252
+ },
253
+ },
254
+ {
255
+ name: "TFA code input",
256
+ selector: "input[name=otc]:not(.moveOffScreen)",
257
+ async handler(page) {
258
+ const error = await page.$(".alert-error");
259
+ if (error) {
260
+ debug("Found error message. Displaying");
261
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
262
+ const errorMessage = await page.evaluate(
263
+ // eslint-disable-next-line
264
+ (err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
265
+ console.log(errorMessage);
266
+ }
267
+ else {
268
+ const description = await page.$("#idDiv_SAOTCC_Description");
269
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
270
+ const descriptionMessage = await page.evaluate(
271
+ // eslint-disable-next-line
272
+ (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
273
+ console.log(descriptionMessage);
274
+ }
275
+ const { verificationCode } = await inquirer_1.default.prompt([
276
+ {
277
+ name: "verificationCode",
278
+ message: "Verification Code:",
279
+ },
280
+ ]);
281
+ debug("Focusing on verification code input");
282
+ await page.focus(`input[name="otc"]`);
283
+ debug("Clearing input");
284
+ for (let i = 0; i < 100; i++) {
285
+ await page.keyboard.press("Backspace");
286
+ }
287
+ debug("Typing verification code");
288
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
289
+ await page.keyboard.type(verificationCode);
290
+ debug("Submitting form");
291
+ await page.click("input[type=submit]");
292
+ debug("Waiting for submission to finish");
293
+ await Promise.race([
294
+ page.waitForSelector(`input[name=otc].has-error,input[name=otc].moveOffScreen`, { timeout: 60000 }),
295
+ (async () => {
296
+ await bluebird_1.default.delay(1000);
297
+ await page.waitForSelector(`input[name=otc]`, {
298
+ hidden: true,
299
+ timeout: 60000,
300
+ });
301
+ })(),
302
+ ]);
303
+ },
304
+ },
305
+ {
306
+ name: "Remember me",
307
+ selector: `#KmsiDescription`,
308
+ async handler(page, _selected, _noPrompt, _defaultUsername, _defaultPassword, rememberMe) {
309
+ if (rememberMe) {
310
+ debug("Clicking remember me button");
311
+ await page.click("#idSIButton9");
312
+ }
313
+ else {
314
+ debug("Clicking don't remember button");
315
+ await page.click("#idBtn_Back");
316
+ }
317
+ debug("Waiting for a delay");
318
+ await bluebird_1.default.delay(500);
319
+ },
320
+ },
321
+ {
322
+ name: "Service exception",
323
+ selector: "#service_exception_message",
324
+ async handler(page, selected) {
325
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
326
+ const descriptionMessage = await page.evaluate(
327
+ // eslint-disable-next-line
328
+ (description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
329
+ throw new CLIError_1.CLIError(descriptionMessage);
330
+ },
331
+ },
332
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "az2aws",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Use Azure AD SSO to log into the AWS CLI. A modern, actively maintained alternative to aws-azure-login.",
5
5
  "main": "index.js",
6
6
  "author": {
@@ -57,16 +57,16 @@
57
57
  "@smithy/node-http-handler": "^2.5.0",
58
58
  "az2aws": "^1.1.0",
59
59
  "bluebird": "^3.7.2",
60
- "cheerio": "^1.0.0-rc.10",
60
+ "cheerio": "^1.0.0-rc.12",
61
61
  "commander": "^9.5.0",
62
62
  "debug": "^4.3.1",
63
63
  "https-proxy-agent": "^7.0.6",
64
64
  "ini": "^3.0.1",
65
65
  "inquirer": "^8.2.6",
66
66
  "lodash": "^4.17.21",
67
- "mkdirp": "^1.0.4",
67
+ "mkdirp": "^2.1.6",
68
68
  "puppeteer": "^24.34.0",
69
- "uuid": "^8.3.2"
69
+ "uuid": "^9.0.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/bluebird": "^3.5.42",