az2aws 1.4.0 → 1.6.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/CHANGELOG.md +26 -0
- package/README.md +16 -0
- package/eslint.config.cjs +36 -0
- package/lib/awsConfig.js +2 -2
- package/lib/configureProfileAsync.js +11 -4
- package/lib/index.js +39 -17
- package/lib/login.js +54 -28
- package/lib/loginStates.js +6 -1
- package/lib/updateNotifier.js +147 -0
- package/package.json +29 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0](https://github.com/kuma0128/az2aws/compare/v1.5.0...v1.6.0) (2026-03-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add incognito mode option ([#97](https://github.com/kuma0128/az2aws/issues/97)) ([01d5f98](https://github.com/kuma0128/az2aws/commit/01d5f98897795eca9cef1b372284ac0a8c24b840))
|
|
9
|
+
* notify new available version ([#137](https://github.com/kuma0128/az2aws/issues/137)) ([c204bae](https://github.com/kuma0128/az2aws/commit/c204bae4ec57ea0b63d01d5d6c963951f8341318))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* update dependencies and add Dependabot config ([#120](https://github.com/kuma0128/az2aws/issues/120)) ([c500dd0](https://github.com/kuma0128/az2aws/commit/c500dd06fd4c799bde467d069ec03ac244967542))
|
|
15
|
+
* update workflows to use pnpm cache and remove standalone option ([#119](https://github.com/kuma0128/az2aws/issues/119)) ([8d759c3](https://github.com/kuma0128/az2aws/commit/8d759c34d8d04211325cfba56692e2cbc55cfe51))
|
|
16
|
+
|
|
17
|
+
## [1.5.0](https://github.com/kuma0128/az2aws/compare/v1.4.0...v1.5.0) (2026-02-12)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* optimize Docker image build ([#105](https://github.com/kuma0128/az2aws/issues/105)) ([fc7f491](https://github.com/kuma0128/az2aws/commit/fc7f491a3d3029f3dbca750b984cbb3b5ea4bd3a))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* handle TargetCloseError from incompatible browser profile ([#117](https://github.com/kuma0128/az2aws/issues/117)) ([d5c1752](https://github.com/kuma0128/az2aws/commit/d5c17521cd179891a13c8285d451ad353b47bc71))
|
|
28
|
+
|
|
3
29
|
## [1.4.0](https://github.com/kuma0128/az2aws/compare/v1.3.0...v1.4.0) (2026-02-12)
|
|
4
30
|
|
|
5
31
|
|
package/README.md
CHANGED
|
@@ -100,6 +100,7 @@ https://snapcraft.io/az2aws
|
|
|
100
100
|
| `--enable-chrome-seamless-sso` | Enable Azure AD Seamless SSO |
|
|
101
101
|
| `--no-disable-extensions` | Keep browser extensions enabled |
|
|
102
102
|
| `--disable-gpu` | Disable GPU acceleration |
|
|
103
|
+
| `--incognito` | Open the login flow in an incognito browser context |
|
|
103
104
|
| `--version (-v)` | Show version number |
|
|
104
105
|
|
|
105
106
|
## Usage
|
|
@@ -132,6 +133,10 @@ Enable "Stay logged in" during configuration to use `--no-prompt` without storin
|
|
|
132
133
|
az2aws --no-prompt
|
|
133
134
|
az2aws --profile foo --no-prompt
|
|
134
135
|
|
|
136
|
+
`--incognito` starts the login flow in a fresh incognito browser context. This
|
|
137
|
+
helps avoid reusing an existing browser session, and it overrides any saved
|
|
138
|
+
"Stay logged in" browser state for that run.
|
|
139
|
+
|
|
135
140
|
#### Environment Variables
|
|
136
141
|
|
|
137
142
|
You can set defaults via environment variables (use with `--no-prompt`):
|
|
@@ -176,6 +181,7 @@ Example:
|
|
|
176
181
|
az2aws # Default profile
|
|
177
182
|
az2aws --profile foo # Named profile
|
|
178
183
|
az2aws --mode gui # Use browser UI (more reliable)
|
|
184
|
+
az2aws --mode gui --incognito # Open a fresh incognito login window
|
|
179
185
|
|
|
180
186
|
You'll be prompted for username, password, and MFA if required. After login, use AWS CLI/SDKs as usual.
|
|
181
187
|
|
|
@@ -186,6 +192,16 @@ You'll be prompted for username, password, and MFA if required. After login, use
|
|
|
186
192
|
|
|
187
193
|
#### Troubleshooting
|
|
188
194
|
|
|
195
|
+
If you see `TargetCloseError: Protocol error (Target.setAutoAttach): Target closed`,
|
|
196
|
+
the browser profile may be incompatible with the bundled Chromium version
|
|
197
|
+
(e.g., after upgrading or downgrading az2aws). When using the default
|
|
198
|
+
managed profile (`~/.aws/chromium`) with "Stay logged in" enabled, az2aws
|
|
199
|
+
will automatically reset the profile and retry. If you have set
|
|
200
|
+
`BROWSER_USER_DATA_DIR` to point to an existing Chrome profile, az2aws
|
|
201
|
+
will **not** modify that directory — you will need to resolve the
|
|
202
|
+
incompatibility manually (e.g., update az2aws, or use a different
|
|
203
|
+
`BROWSER_USER_DATA_DIR`).
|
|
204
|
+
|
|
189
205
|
If you see device compliance errors (e.g., "Device UnSecured Or Non-Compliant"),
|
|
190
206
|
Try:
|
|
191
207
|
`--mode gui` and use your system Chrome via `BROWSER_CHROME_BIN`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const tseslint = require("@typescript-eslint/eslint-plugin");
|
|
2
|
+
const prettier = require("eslint-config-prettier");
|
|
3
|
+
const sourceFiles = ["src/**/*.ts", "typings/**/*.ts"];
|
|
4
|
+
|
|
5
|
+
module.exports = [
|
|
6
|
+
{
|
|
7
|
+
ignores: [
|
|
8
|
+
"**/*.test.ts",
|
|
9
|
+
"vitest.config.ts",
|
|
10
|
+
"eslint.config.cjs",
|
|
11
|
+
"lib/**",
|
|
12
|
+
"coverage/**",
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
linterOptions: {
|
|
17
|
+
reportUnusedDisableDirectives: "off",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
...tseslint.configs["flat/recommended-type-checked"].map((config) => ({
|
|
21
|
+
...config,
|
|
22
|
+
files: sourceFiles,
|
|
23
|
+
})),
|
|
24
|
+
{
|
|
25
|
+
files: sourceFiles,
|
|
26
|
+
languageOptions: {
|
|
27
|
+
parserOptions: {
|
|
28
|
+
project: "./tsconfig.json",
|
|
29
|
+
tsconfigRootDir: __dirname,
|
|
30
|
+
ecmaVersion: 2018,
|
|
31
|
+
sourceType: "module",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
prettier,
|
|
36
|
+
];
|
package/lib/awsConfig.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.awsConfig = void 0;
|
|
|
7
7
|
const ini_1 = __importDefault(require("ini"));
|
|
8
8
|
const debug_1 = __importDefault(require("debug"));
|
|
9
9
|
const paths_1 = require("./paths");
|
|
10
|
-
const mkdirp_1 =
|
|
10
|
+
const mkdirp_1 = require("mkdirp");
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const util_1 = __importDefault(require("util"));
|
|
13
13
|
const debug = (0, debug_1.default)("az2aws");
|
|
@@ -101,7 +101,7 @@ exports.awsConfig = {
|
|
|
101
101
|
debug(`Stringifying ${type} INI data`);
|
|
102
102
|
const text = ini_1.default.stringify(data);
|
|
103
103
|
debug(`Creating AWS config directory '${paths_1.paths.awsDir}' if not exists.`);
|
|
104
|
-
await (0, mkdirp_1.
|
|
104
|
+
await (0, mkdirp_1.mkdirp)(paths_1.paths.awsDir);
|
|
105
105
|
debug(`Writing '${type}' INI to file '${paths_1.paths[type]}'`);
|
|
106
106
|
await writeFile(paths_1.paths[type], text);
|
|
107
107
|
},
|
|
@@ -11,23 +11,27 @@ async function configureProfileAsync(profileName) {
|
|
|
11
11
|
const profile = await awsConfig_1.awsConfig.getProfileConfigAsync(profileName);
|
|
12
12
|
const questions = [
|
|
13
13
|
{
|
|
14
|
+
type: "input",
|
|
14
15
|
name: "tenantId",
|
|
15
16
|
message: "Azure Tenant ID:",
|
|
16
|
-
validate: (input) =>
|
|
17
|
+
validate: (input) => input.trim().length > 0,
|
|
17
18
|
default: profile && profile.azure_tenant_id,
|
|
18
19
|
},
|
|
19
20
|
{
|
|
21
|
+
type: "input",
|
|
20
22
|
name: "appIdUri",
|
|
21
23
|
message: "Azure App ID URI:",
|
|
22
|
-
validate: (input) =>
|
|
24
|
+
validate: (input) => input.trim().length > 0,
|
|
23
25
|
default: profile && profile.azure_app_id_uri,
|
|
24
26
|
},
|
|
25
27
|
{
|
|
28
|
+
type: "input",
|
|
26
29
|
name: "username",
|
|
27
30
|
message: "Default Username:",
|
|
28
31
|
default: profile && profile.azure_default_username,
|
|
29
32
|
},
|
|
30
33
|
{
|
|
34
|
+
type: "input",
|
|
31
35
|
name: "rememberMe",
|
|
32
36
|
message: "Stay logged in: skip authentication while refreshing aws credentials (true|false)",
|
|
33
37
|
default: (profile &&
|
|
@@ -41,22 +45,25 @@ async function configureProfileAsync(profileName) {
|
|
|
41
45
|
},
|
|
42
46
|
},
|
|
43
47
|
{
|
|
48
|
+
type: "input",
|
|
44
49
|
name: "defaultRoleArn",
|
|
45
50
|
message: "Default Role ARN (if multiple):",
|
|
46
51
|
default: profile && profile.azure_default_role_arn,
|
|
47
52
|
},
|
|
48
53
|
{
|
|
54
|
+
type: "input",
|
|
49
55
|
name: "defaultDurationHours",
|
|
50
56
|
message: "Default Session Duration Hours (up to 12):",
|
|
51
57
|
default: (profile && profile.azure_default_duration_hours) || 1,
|
|
52
58
|
validate: (input) => {
|
|
53
|
-
|
|
54
|
-
if (
|
|
59
|
+
const num = Number(input);
|
|
60
|
+
if (num > 0 && num <= 12)
|
|
55
61
|
return true;
|
|
56
62
|
return "Duration hours must be between 1 and 12";
|
|
57
63
|
},
|
|
58
64
|
},
|
|
59
65
|
{
|
|
66
|
+
type: "input",
|
|
60
67
|
name: "region",
|
|
61
68
|
message: "AWS Region:",
|
|
62
69
|
default: profile && profile.region,
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,8 @@ process.on("SIGTERM", () => process.exit(1));
|
|
|
6
6
|
const commander_1 = require("commander");
|
|
7
7
|
const configureProfileAsync_1 = require("./configureProfileAsync");
|
|
8
8
|
const login_1 = require("./login");
|
|
9
|
-
|
|
9
|
+
const updateNotifier_1 = require("./updateNotifier");
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
10
11
|
const { version } = require("../package.json");
|
|
11
12
|
const program = new commander_1.Command();
|
|
12
13
|
program
|
|
@@ -23,6 +24,7 @@ program
|
|
|
23
24
|
.option("--enable-chrome-seamless-sso", "Enable Chromium's pass-through authentication with Azure Active Directory Seamless Single Sign-On")
|
|
24
25
|
.option("--no-disable-extensions", "Tell Puppeteer not to pass the --disable-extensions flag to Chromium")
|
|
25
26
|
.option("--disable-gpu", "Tell Puppeteer to pass the --disable-gpu flag to Chromium")
|
|
27
|
+
.option("--incognito", "Launch Chromium in incognito mode")
|
|
26
28
|
.parse(process.argv);
|
|
27
29
|
const options = program.opts();
|
|
28
30
|
const profileName = options.profile ||
|
|
@@ -37,22 +39,42 @@ const enableChromeSeamlessSso = !!options.enableChromeSeamlessSso;
|
|
|
37
39
|
const forceRefresh = !!options.forceRefresh;
|
|
38
40
|
const noDisableExtensions = !options.disableExtensions;
|
|
39
41
|
const disableGpu = !!options.disableGpu;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const incognito = !!options.incognito;
|
|
43
|
+
// Start the update lookup immediately, but only print after the main flow ends.
|
|
44
|
+
const updateCheckPromise = (0, updateNotifier_1.checkForUpdate)(version, {
|
|
45
|
+
useColor: process.stderr.isTTY && !process.env.NO_COLOR,
|
|
46
|
+
});
|
|
47
|
+
async function runAsync() {
|
|
48
|
+
let exitCode = 0;
|
|
49
|
+
try {
|
|
50
|
+
if (options.allProfiles) {
|
|
51
|
+
await login_1.login.loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito);
|
|
52
|
+
}
|
|
53
|
+
else if (options.configure) {
|
|
54
|
+
await (0, configureProfileAsync_1.configureProfileAsync)(profileName);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await login_1.login.loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito);
|
|
58
|
+
}
|
|
44
59
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err instanceof Error && err.name === "CLIError") {
|
|
62
|
+
console.error(err.message);
|
|
63
|
+
exitCode = 2;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error(err);
|
|
67
|
+
exitCode = 1;
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
if (exitCode === 0) {
|
|
71
|
+
const updateMessage = await updateCheckPromise;
|
|
72
|
+
if (updateMessage) {
|
|
73
|
+
process.stderr.write(updateMessage);
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
|
-
|
|
76
|
+
if (exitCode !== 0) {
|
|
77
|
+
process.exit(exitCode);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
void runAsync();
|
package/lib/login.js
CHANGED
|
@@ -15,9 +15,8 @@ const querystring_1 = __importDefault(require("querystring"));
|
|
|
15
15
|
const debug_1 = __importDefault(require("debug"));
|
|
16
16
|
const CLIError_1 = require("./CLIError");
|
|
17
17
|
const awsConfig_1 = require("./awsConfig");
|
|
18
|
-
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
19
18
|
const paths_1 = require("./paths");
|
|
20
|
-
const mkdirp_1 =
|
|
19
|
+
const mkdirp_1 = require("mkdirp");
|
|
21
20
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
22
21
|
const https_1 = require("https");
|
|
23
22
|
const node_http_handler_1 = require("@smithy/node-http-handler");
|
|
@@ -32,12 +31,20 @@ const AZURE_AD_SSO = "autologon.microsoftazuread-sso.com";
|
|
|
32
31
|
const AWS_SAML_ENDPOINT = "https://signin.aws.amazon.com/saml";
|
|
33
32
|
const AWS_CN_SAML_ENDPOINT = "https://signin.amazonaws.cn/saml";
|
|
34
33
|
const AWS_GOV_SAML_ENDPOINT = "https://signin.amazonaws-us-gov.com/saml";
|
|
34
|
+
// Keep the runtime import as native `import()` so CommonJS output can load
|
|
35
|
+
// the ESM-only https-proxy-agent package.
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
37
|
+
const importHttpsProxyAgent = Function('return import("https-proxy-agent")');
|
|
35
38
|
const getProxyUrl = () => process.env.https_proxy ||
|
|
36
39
|
process.env.HTTPS_PROXY ||
|
|
37
40
|
process.env.http_proxy ||
|
|
38
41
|
process.env.HTTP_PROXY;
|
|
39
42
|
exports.login = {
|
|
40
|
-
async
|
|
43
|
+
async _createHttpsProxyAgentAsync(proxyUrl, proxyOptions) {
|
|
44
|
+
const { HttpsProxyAgent } = await importHttpsProxyAgent();
|
|
45
|
+
return new HttpsProxyAgent(proxyUrl, proxyOptions);
|
|
46
|
+
},
|
|
47
|
+
async loginAsync(profileName, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito = false) {
|
|
41
48
|
let headless, cliProxy;
|
|
42
49
|
if (mode === "cli") {
|
|
43
50
|
headless = true;
|
|
@@ -70,16 +77,13 @@ exports.login = {
|
|
|
70
77
|
}
|
|
71
78
|
console.log("Using AWS SAML endpoint", assertionConsumerServiceURL);
|
|
72
79
|
const loginUrl = await this._createLoginUrlAsync(profile.azure_app_id_uri, profile.azure_tenant_id, assertionConsumerServiceURL);
|
|
73
|
-
const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu);
|
|
80
|
+
const samlResponse = await this._performLoginAsync(loginUrl, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, profile.azure_default_username, profile.azure_default_password, enableChromeSeamlessSso, profile.azure_default_remember_me, noDisableExtensions, disableGpu, incognito);
|
|
74
81
|
const roles = this._parseRolesFromSamlResponse(samlResponse);
|
|
75
82
|
const { role, durationHours } = await this._askUserForRoleAndDurationAsync(roles, noPrompt, profile.azure_default_role_arn, profile.azure_default_duration_hours);
|
|
76
83
|
await this._assumeRoleAsync(profileName, samlResponse, role, durationHours, awsNoVerifySsl, profile.region);
|
|
77
84
|
},
|
|
78
|
-
async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu) {
|
|
85
|
+
async loginAll(mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, forceRefresh, noDisableExtensions, disableGpu, incognito = false) {
|
|
79
86
|
const profiles = await awsConfig_1.awsConfig.getAllProfileNames();
|
|
80
|
-
if (!profiles) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
87
|
for (const profile of profiles) {
|
|
84
88
|
debug(`Check if profile ${profile} is expired or is about to expire`);
|
|
85
89
|
if (!forceRefresh &&
|
|
@@ -88,7 +92,7 @@ exports.login = {
|
|
|
88
92
|
continue;
|
|
89
93
|
}
|
|
90
94
|
debug(`Run login for profile: ${profile}`);
|
|
91
|
-
await this.loginAsync(profile, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu);
|
|
95
|
+
await this.loginAsync(profile, mode, disableSandbox, noPrompt, enableChromeNetworkService, awsNoVerifySsl, enableChromeSeamlessSso, noDisableExtensions, disableGpu, incognito);
|
|
92
96
|
}
|
|
93
97
|
},
|
|
94
98
|
// Gather data from environment variables
|
|
@@ -182,16 +186,20 @@ exports.login = {
|
|
|
182
186
|
* @param {bool} [rememberMe] - Enable remembering the session
|
|
183
187
|
* @param {bool} [noDisableExtensions] - True to prevent Puppeteer from disabling Chromium extensions
|
|
184
188
|
* @param {bool} [disableGpu] - Disables GPU Acceleration
|
|
189
|
+
* @param {bool} [incognito] - Launch the login flow in an incognito browser context
|
|
185
190
|
* @returns {Promise.<string>} The SAML response.
|
|
186
191
|
* @private
|
|
187
192
|
*/
|
|
188
|
-
async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu) {
|
|
193
|
+
async _performLoginAsync(url, headless, disableSandbox, cliProxy, noPrompt, enableChromeNetworkService, defaultUsername, defaultPassword, enableChromeSeamlessSso, rememberMe, noDisableExtensions, disableGpu, incognito = false) {
|
|
189
194
|
debug("Loading login page in Chrome");
|
|
190
195
|
let browser;
|
|
196
|
+
const useRememberMe = rememberMe && !incognito;
|
|
191
197
|
try {
|
|
192
198
|
const args = headless
|
|
193
199
|
? []
|
|
194
|
-
:
|
|
200
|
+
: incognito
|
|
201
|
+
? [`--window-size=${WIDTH},${HEIGHT}`]
|
|
202
|
+
: [`--app=${url}`, `--window-size=${WIDTH},${HEIGHT}`];
|
|
195
203
|
if (disableSandbox)
|
|
196
204
|
args.push("--no-sandbox");
|
|
197
205
|
if (enableChromeNetworkService)
|
|
@@ -199,12 +207,12 @@ exports.login = {
|
|
|
199
207
|
if (enableChromeSeamlessSso)
|
|
200
208
|
args.push(`--auth-server-whitelist=${AZURE_AD_SSO}`, `--auth-negotiate-delegate-whitelist=${AZURE_AD_SSO}`);
|
|
201
209
|
debug(`rememberMe value: ${rememberMe} (type: ${typeof rememberMe})`);
|
|
202
|
-
if (
|
|
210
|
+
if (useRememberMe) {
|
|
203
211
|
if (paths_1.paths.userDataDir) {
|
|
204
212
|
args.push(`--user-data-dir=${paths_1.paths.userDataDir}`);
|
|
205
213
|
}
|
|
206
214
|
else {
|
|
207
|
-
await (0, mkdirp_1.
|
|
215
|
+
await (0, mkdirp_1.mkdirp)(paths_1.paths.chromium);
|
|
208
216
|
args.push(`--user-data-dir=${paths_1.paths.chromium}`);
|
|
209
217
|
}
|
|
210
218
|
// --profile-directory requires --user-data-dir to work properly
|
|
@@ -212,6 +220,9 @@ exports.login = {
|
|
|
212
220
|
args.push(`--profile-directory=${paths_1.paths.profileDir}`);
|
|
213
221
|
}
|
|
214
222
|
}
|
|
223
|
+
if (incognito && rememberMe) {
|
|
224
|
+
console.warn("WARNING: Incognito mode overrides 'Stay logged in' and ignores saved Chrome profiles.");
|
|
225
|
+
}
|
|
215
226
|
const proxyUrl = getProxyUrl();
|
|
216
227
|
if (proxyUrl) {
|
|
217
228
|
args.push(`--proxy-server=${proxyUrl}`);
|
|
@@ -235,13 +246,13 @@ exports.login = {
|
|
|
235
246
|
}
|
|
236
247
|
catch (e) {
|
|
237
248
|
if (e instanceof Error &&
|
|
238
|
-
e.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
debug(`Browser launch failed with TargetCloseError. Resetting profile at ${
|
|
249
|
+
e.name === "TargetCloseError" &&
|
|
250
|
+
useRememberMe &&
|
|
251
|
+
!paths_1.paths.userDataDir) {
|
|
252
|
+
debug(`Browser launch failed with TargetCloseError. Resetting profile at ${paths_1.paths.chromium}`);
|
|
242
253
|
console.warn("Browser profile appears incompatible. Resetting profile data and retrying...");
|
|
243
|
-
await promises_1.default.rm(
|
|
244
|
-
await (0, mkdirp_1.
|
|
254
|
+
await promises_1.default.rm(paths_1.paths.chromium, { recursive: true, force: true });
|
|
255
|
+
await (0, mkdirp_1.mkdirp)(paths_1.paths.chromium);
|
|
245
256
|
browser = await puppeteer_1.default.launch(launchParams);
|
|
246
257
|
}
|
|
247
258
|
else {
|
|
@@ -250,8 +261,20 @@ exports.login = {
|
|
|
250
261
|
}
|
|
251
262
|
// Wait for a bit as sometimes the browser isn't ready.
|
|
252
263
|
await bluebird_1.default.delay(200);
|
|
253
|
-
|
|
254
|
-
|
|
264
|
+
let page;
|
|
265
|
+
if (incognito) {
|
|
266
|
+
const existingPages = await browser.pages();
|
|
267
|
+
const context = await browser.createBrowserContext();
|
|
268
|
+
page = await context.newPage();
|
|
269
|
+
await Promise.all(existingPages.map((existingPage) => existingPage.close()));
|
|
270
|
+
if (!headless) {
|
|
271
|
+
await page.bringToFront();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const pages = await browser.pages();
|
|
276
|
+
page = pages[0];
|
|
277
|
+
}
|
|
255
278
|
await page.setExtraHTTPHeaders({
|
|
256
279
|
"Accept-Language": "en",
|
|
257
280
|
});
|
|
@@ -290,7 +313,7 @@ exports.login = {
|
|
|
290
313
|
debug("Enabling request interception");
|
|
291
314
|
await page.setRequestInterception(true);
|
|
292
315
|
try {
|
|
293
|
-
if (
|
|
316
|
+
if (incognito || headless || cliProxy) {
|
|
294
317
|
debug("Going to login page");
|
|
295
318
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
296
319
|
}
|
|
@@ -332,7 +355,7 @@ exports.login = {
|
|
|
332
355
|
debug(`Found state: ${state.name}`);
|
|
333
356
|
await Promise.race([
|
|
334
357
|
samlResponsePromise,
|
|
335
|
-
state.handler(page, selected, noPrompt, defaultUsername, defaultPassword,
|
|
358
|
+
state.handler(page, selected, noPrompt, defaultUsername, defaultPassword, useRememberMe),
|
|
336
359
|
]);
|
|
337
360
|
debug(`Finished state: ${state.name}`);
|
|
338
361
|
break;
|
|
@@ -422,10 +445,13 @@ exports.login = {
|
|
|
422
445
|
let durationHours = 1;
|
|
423
446
|
if (defaultDurationHours) {
|
|
424
447
|
const parsedDuration = parseInt(defaultDurationHours, 10);
|
|
425
|
-
if (!Number.isNaN(parsedDuration) &&
|
|
448
|
+
if (!Number.isNaN(parsedDuration) &&
|
|
449
|
+
parsedDuration > 0 &&
|
|
450
|
+
parsedDuration <= 12) {
|
|
426
451
|
durationHours = parsedDuration;
|
|
427
452
|
}
|
|
428
453
|
}
|
|
454
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
429
455
|
const questions = [];
|
|
430
456
|
if (roles.length === 0) {
|
|
431
457
|
throw new CLIError_1.CLIError("No roles found in SAML response.");
|
|
@@ -450,7 +476,7 @@ exports.login = {
|
|
|
450
476
|
questions.push({
|
|
451
477
|
name: "role",
|
|
452
478
|
message: "Role:",
|
|
453
|
-
type: "
|
|
479
|
+
type: "select",
|
|
454
480
|
choices: roles.map((r) => r.roleArn).sort(),
|
|
455
481
|
default: defaultRoleArn,
|
|
456
482
|
});
|
|
@@ -471,8 +497,8 @@ exports.login = {
|
|
|
471
497
|
type: "input",
|
|
472
498
|
default: defaultDurationHours || 1,
|
|
473
499
|
validate: (input) => {
|
|
474
|
-
|
|
475
|
-
if (
|
|
500
|
+
const num = Number(input);
|
|
501
|
+
if (num > 0 && num <= 12)
|
|
476
502
|
return true;
|
|
477
503
|
return "Duration hours must be between 1 and 12";
|
|
478
504
|
},
|
|
@@ -519,7 +545,7 @@ exports.login = {
|
|
|
519
545
|
stsOptions = {
|
|
520
546
|
...stsOptions,
|
|
521
547
|
requestHandler: new node_http_handler_1.NodeHttpHandler({
|
|
522
|
-
httpsAgent:
|
|
548
|
+
httpsAgent: await this._createHttpsProxyAgentAsync(proxyUrl, proxyOptions),
|
|
523
549
|
}),
|
|
524
550
|
};
|
|
525
551
|
}
|
package/lib/loginStates.js
CHANGED
|
@@ -37,8 +37,10 @@ exports.states = [
|
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
39
|
debug("Prompting user for username");
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
40
41
|
({ username } = await inquirer_1.default.prompt([
|
|
41
42
|
{
|
|
43
|
+
type: "input",
|
|
42
44
|
name: "username",
|
|
43
45
|
message: "Username:",
|
|
44
46
|
default: defaultUsername,
|
|
@@ -115,7 +117,7 @@ exports.states = [
|
|
|
115
117
|
{
|
|
116
118
|
name: "account",
|
|
117
119
|
message: "Account:",
|
|
118
|
-
type: "
|
|
120
|
+
type: "select",
|
|
119
121
|
choices: accounts.map((a) => a.message),
|
|
120
122
|
default: aadTileMessage,
|
|
121
123
|
},
|
|
@@ -187,6 +189,7 @@ exports.states = [
|
|
|
187
189
|
}
|
|
188
190
|
else {
|
|
189
191
|
debug("Prompting user for password");
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
190
193
|
({ password } = await inquirer_1.default.prompt([
|
|
191
194
|
{
|
|
192
195
|
name: "password",
|
|
@@ -272,8 +275,10 @@ exports.states = [
|
|
|
272
275
|
(d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
|
|
273
276
|
console.log(descriptionMessage);
|
|
274
277
|
}
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
275
279
|
const { verificationCode } = await inquirer_1.default.prompt([
|
|
276
280
|
{
|
|
281
|
+
type: "input",
|
|
277
282
|
name: "verificationCode",
|
|
278
283
|
message: "Verification Code:",
|
|
279
284
|
},
|
|
@@ -0,0 +1,147 @@
|
|
|
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.checkForUpdate = checkForUpdate;
|
|
7
|
+
const https_1 = __importDefault(require("https"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const PACKAGE_NAME = "az2aws";
|
|
12
|
+
const CACHE_TTL_MS = 1000 * 60 * 60 * 24; // 24 hours
|
|
13
|
+
const FAKE_LATEST_VERSION_ENV = "AZ2AWS_FAKE_LATEST_VERSION";
|
|
14
|
+
const ANSI_YELLOW = "\u001b[33m";
|
|
15
|
+
const ANSI_RESET = "\u001b[0m";
|
|
16
|
+
function getCachePath() {
|
|
17
|
+
return path_1.default.join(os_1.default.homedir(), ".config", "az2aws", "update-check.json");
|
|
18
|
+
}
|
|
19
|
+
function readCache() {
|
|
20
|
+
try {
|
|
21
|
+
const data = fs_1.default.readFileSync(getCachePath(), "utf-8");
|
|
22
|
+
const cache = JSON.parse(data);
|
|
23
|
+
if (Date.now() - cache.checkedAt < CACHE_TTL_MS) {
|
|
24
|
+
return cache;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (_a) {
|
|
28
|
+
// Cache doesn't exist or is invalid
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function writeCache(latestVersion) {
|
|
33
|
+
try {
|
|
34
|
+
const cachePath = getCachePath();
|
|
35
|
+
const dir = path_1.default.dirname(cachePath);
|
|
36
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
37
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify({ latestVersion, checkedAt: Date.now() }));
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
// Ignore cache write failures
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function compareVersions(current, latest) {
|
|
44
|
+
const currentParts = current.split(".").map(Number);
|
|
45
|
+
const latestParts = latest.split(".").map(Number);
|
|
46
|
+
for (let i = 0; i < 3; i++) {
|
|
47
|
+
const c = currentParts[i] || 0;
|
|
48
|
+
const l = latestParts[i] || 0;
|
|
49
|
+
if (l > c)
|
|
50
|
+
return 1;
|
|
51
|
+
if (l < c)
|
|
52
|
+
return -1;
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
function fetchLatestVersion() {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
59
|
+
const req = https_1.default.get(url, { timeout: 3000 }, (res) => {
|
|
60
|
+
if (res.statusCode !== 200) {
|
|
61
|
+
res.resume();
|
|
62
|
+
reject(new Error(`Unexpected npm registry status code: ${res.statusCode}`));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let body = "";
|
|
66
|
+
res.on("data", (chunk) => {
|
|
67
|
+
body += chunk.toString();
|
|
68
|
+
});
|
|
69
|
+
res.on("end", () => {
|
|
70
|
+
try {
|
|
71
|
+
const data = JSON.parse(body);
|
|
72
|
+
resolve(data.version);
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
reject(new Error("Failed to parse npm registry response"));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
req.on("error", reject);
|
|
80
|
+
req.on("timeout", () => {
|
|
81
|
+
req.destroy();
|
|
82
|
+
reject(new Error("Request timed out"));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function detectInstallMethod(env, executablePath) {
|
|
87
|
+
if (env.SNAP) {
|
|
88
|
+
return "snap";
|
|
89
|
+
}
|
|
90
|
+
if (executablePath === null || executablePath === void 0 ? void 0 : executablePath.includes(`${path_1.default.sep}mise${path_1.default.sep}`)) {
|
|
91
|
+
return "mise";
|
|
92
|
+
}
|
|
93
|
+
return "unknown";
|
|
94
|
+
}
|
|
95
|
+
function getForcedLatestVersion(env) {
|
|
96
|
+
var _a;
|
|
97
|
+
const forcedLatestVersion = (_a = env[FAKE_LATEST_VERSION_ENV]) === null || _a === void 0 ? void 0 : _a.trim();
|
|
98
|
+
return forcedLatestVersion ? forcedLatestVersion : null;
|
|
99
|
+
}
|
|
100
|
+
function getUpdateInstructions(installMethod) {
|
|
101
|
+
switch (installMethod) {
|
|
102
|
+
case "mise":
|
|
103
|
+
return `Run: mise use -g npm:${PACKAGE_NAME}`;
|
|
104
|
+
case "snap":
|
|
105
|
+
return `Run: sudo snap refresh ${PACKAGE_NAME}`;
|
|
106
|
+
default:
|
|
107
|
+
return `Run: npm install -g ${PACKAGE_NAME}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function createUpdateMessage(currentVersion, latestVersion, installMethod, useColor = false) {
|
|
111
|
+
const message = [
|
|
112
|
+
"",
|
|
113
|
+
`Update available! ${currentVersion} -> ${latestVersion}`,
|
|
114
|
+
getUpdateInstructions(installMethod),
|
|
115
|
+
"",
|
|
116
|
+
].join("\n");
|
|
117
|
+
return useColor ? `${ANSI_YELLOW}${message}${ANSI_RESET}` : message;
|
|
118
|
+
}
|
|
119
|
+
async function checkForUpdate(currentVersion, options = {}) {
|
|
120
|
+
var _a, _b, _c, _d;
|
|
121
|
+
try {
|
|
122
|
+
const env = (_a = options.env) !== null && _a !== void 0 ? _a : process.env;
|
|
123
|
+
const forcedLatestVersion = getForcedLatestVersion(env);
|
|
124
|
+
let latestVersion;
|
|
125
|
+
if (forcedLatestVersion) {
|
|
126
|
+
latestVersion = forcedLatestVersion;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const cache = readCache();
|
|
130
|
+
if (cache) {
|
|
131
|
+
latestVersion = cache.latestVersion;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
latestVersion = await fetchLatestVersion();
|
|
135
|
+
writeCache(latestVersion);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (compareVersions(currentVersion, latestVersion) > 0) {
|
|
139
|
+
const installMethod = (_b = options.installMethod) !== null && _b !== void 0 ? _b : detectInstallMethod(env, (_c = options.executablePath) !== null && _c !== void 0 ? _c : process.argv[1]);
|
|
140
|
+
return createUpdateMessage(currentVersion, latestVersion, installMethod, (_d = options.useColor) !== null && _d !== void 0 ? _d : false);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (_e) {
|
|
144
|
+
// Silently ignore update check failures
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "az2aws",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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": {
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
"onlyBuiltDependencies": [
|
|
43
43
|
"esbuild",
|
|
44
44
|
"puppeteer"
|
|
45
|
-
]
|
|
45
|
+
],
|
|
46
|
+
"overrides": {
|
|
47
|
+
"minimatch@>=9.0.0 <9.0.7": "9.0.7",
|
|
48
|
+
"yauzl@<3.2.1": "3.2.1"
|
|
49
|
+
}
|
|
46
50
|
},
|
|
47
51
|
"bin": {
|
|
48
52
|
"az2aws": "lib/index.js"
|
|
@@ -51,7 +55,7 @@
|
|
|
51
55
|
"start": "ts-node-dev src/index.ts",
|
|
52
56
|
"build": "tsc",
|
|
53
57
|
"watch": "tsc -w",
|
|
54
|
-
"eslint": "eslint .
|
|
58
|
+
"eslint": "eslint .",
|
|
55
59
|
"prettier:write": "prettier --write \"src/**/*.{ts,json}\"",
|
|
56
60
|
"prettier:check": "prettier --check \"src/**/*.ts\"",
|
|
57
61
|
"test": "vitest run",
|
|
@@ -60,37 +64,32 @@
|
|
|
60
64
|
"lint": "pnpm eslint && pnpm prettier:check"
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
|
-
"@aws-sdk/client-sts": "^3.
|
|
64
|
-
"@smithy/node-http-handler": "^
|
|
65
|
-
"az2aws": "^1.1.0",
|
|
67
|
+
"@aws-sdk/client-sts": "^3.1009.0",
|
|
68
|
+
"@smithy/node-http-handler": "^4.4.16",
|
|
66
69
|
"bluebird": "^3.7.2",
|
|
67
|
-
"cheerio": "^1.
|
|
68
|
-
"commander": "^
|
|
69
|
-
"debug": "^4.3
|
|
70
|
-
"https-proxy-agent": "^
|
|
71
|
-
"ini": "^
|
|
72
|
-
"inquirer": "^
|
|
73
|
-
"mkdirp": "^
|
|
74
|
-
"puppeteer": "^24.
|
|
75
|
-
"uuid": "^
|
|
70
|
+
"cheerio": "^1.2.0",
|
|
71
|
+
"commander": "^14.0.3",
|
|
72
|
+
"debug": "^4.4.3",
|
|
73
|
+
"https-proxy-agent": "^8.0.0",
|
|
74
|
+
"ini": "^6.0.0",
|
|
75
|
+
"inquirer": "^13.3.2",
|
|
76
|
+
"mkdirp": "^3.0.1",
|
|
77
|
+
"puppeteer": "^24.39.1",
|
|
78
|
+
"uuid": "^13.0.0"
|
|
76
79
|
},
|
|
77
80
|
"devDependencies": {
|
|
78
81
|
"@types/bluebird": "^3.5.42",
|
|
79
|
-
"@types/cheerio": "^0.22.35",
|
|
80
82
|
"@types/debug": "^4.1.12",
|
|
81
|
-
"@types/ini": "^1.
|
|
82
|
-
"@types/
|
|
83
|
-
"@
|
|
84
|
-
"@
|
|
85
|
-
"@
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"ts-node-dev": "^1.1.8",
|
|
93
|
-
"typescript": "^5.4.3",
|
|
94
|
-
"vitest": "^4.0.16"
|
|
83
|
+
"@types/ini": "^4.1.1",
|
|
84
|
+
"@types/node": "^25.5.0",
|
|
85
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
86
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
87
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
88
|
+
"eslint": "^10.0.3",
|
|
89
|
+
"eslint-config-prettier": "^10.1.8",
|
|
90
|
+
"prettier": "^3.8.1",
|
|
91
|
+
"ts-node-dev": "^2.0.0",
|
|
92
|
+
"typescript": "^5.9.3",
|
|
93
|
+
"vitest": "^4.1.0"
|
|
95
94
|
}
|
|
96
95
|
}
|