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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +16 -0
- package/issue/issues.md +441 -1
- package/lib/configureProfileAsync.js +1 -1
- package/lib/index.js +1 -1
- package/lib/login.js +32 -330
- package/lib/loginStates.js +332 -0
- package/package.json +4 -4
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.
|
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"
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
741
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
67
|
+
"mkdirp": "^2.1.6",
|
|
68
68
|
"puppeteer": "^24.34.0",
|
|
69
|
-
"uuid": "^
|
|
69
|
+
"uuid": "^9.0.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/bluebird": "^3.5.42",
|