az2aws 1.6.2 → 1.8.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/.gitattributes +1 -0
- package/CHANGELOG.md +37 -0
- package/CONTRIBUTING.md +20 -0
- package/README.md +86 -11
- package/lib/awsConfig.js +135 -10
- package/lib/configureProfileAsync.js +10 -8
- package/lib/index.js +16 -3
- package/lib/login.js +170 -104
- package/lib/loginStates.js +64 -81
- package/lib/sensitiveOutput.js +85 -0
- package/lib/sessionDuration.js +27 -0
- package/lib/updateNotifier.js +25 -11
- package/lib/validateCliOptions.js +12 -0
- package/package.json +5 -2
- package/pnpm-workspace.yaml +5 -0
package/lib/loginStates.js
CHANGED
|
@@ -9,6 +9,21 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
9
9
|
const debug_1 = __importDefault(require("debug"));
|
|
10
10
|
const CLIError_1 = require("./CLIError");
|
|
11
11
|
const debug = (0, debug_1.default)("az2aws");
|
|
12
|
+
async function readTextContent(page, element) {
|
|
13
|
+
if (!element) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
return page.evaluate((node) => { var _a; return (_a = node.textContent) !== null && _a !== void 0 ? _a : ""; }, element);
|
|
17
|
+
}
|
|
18
|
+
const PASSWORD_SELECTOR = 'input[name="Password"]:not(.moveOffScreen),input[name="passwd"]:not(.moveOffScreen)';
|
|
19
|
+
function printPageMessage(message, allowSensitiveOutput = true) {
|
|
20
|
+
if (allowSensitiveOutput) {
|
|
21
|
+
console.log(message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function createSensitiveCliError(message, sanitizedMessage, allowSensitiveOutput = true) {
|
|
25
|
+
return new CLIError_1.CLIError(allowSensitiveOutput ? message : sanitizedMessage);
|
|
26
|
+
}
|
|
12
27
|
/**
|
|
13
28
|
* To proxy the input/output of the Azure login page, it's easiest to run a loop that
|
|
14
29
|
* monitors the state of the page and then perform the corresponding CLI behavior.
|
|
@@ -20,15 +35,12 @@ exports.states = [
|
|
|
20
35
|
{
|
|
21
36
|
name: "username input",
|
|
22
37
|
selector: `input[name="loginfmt"]:not(.moveOffScreen)`,
|
|
23
|
-
async handler(page, _selected, noPrompt, defaultUsername) {
|
|
38
|
+
async handler(page, _selected, noPrompt, defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
24
39
|
const error = await page.$(".alert-error");
|
|
25
40
|
if (error) {
|
|
26
41
|
debug("Found error message. Displaying");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// eslint-disable-next-line
|
|
30
|
-
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
31
|
-
console.log(errorMessage);
|
|
42
|
+
const errorMessage = await readTextContent(page, error);
|
|
43
|
+
printPageMessage(errorMessage, allowSensitiveOutput);
|
|
32
44
|
}
|
|
33
45
|
let username;
|
|
34
46
|
if (noPrompt && defaultUsername) {
|
|
@@ -37,7 +49,6 @@ exports.states = [
|
|
|
37
49
|
}
|
|
38
50
|
else {
|
|
39
51
|
debug("Prompting user for username");
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
41
52
|
({ username } = await inquirer_1.default.prompt([
|
|
42
53
|
{
|
|
43
54
|
type: "input",
|
|
@@ -60,7 +71,6 @@ exports.states = [
|
|
|
60
71
|
});
|
|
61
72
|
await page.keyboard.press("Backspace");
|
|
62
73
|
debug("Typing username");
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
64
74
|
await page.keyboard.type(username);
|
|
65
75
|
await (0, promises_1.setTimeout)(500);
|
|
66
76
|
debug("Waiting for submit button to be visible");
|
|
@@ -87,22 +97,16 @@ exports.states = [
|
|
|
87
97
|
{
|
|
88
98
|
name: "account selection",
|
|
89
99
|
selector: `#aadTile > div > div.table-cell.tile-img > img`,
|
|
90
|
-
async handler(page) {
|
|
100
|
+
async handler(page, _selected, noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
91
101
|
debug("Multiple accounts associated with username.");
|
|
92
102
|
const aadTile = await page.$("#aadTileTitle");
|
|
93
|
-
|
|
94
|
-
const aadTileMessage = await page.evaluate(
|
|
95
|
-
// eslint-disable-next-line
|
|
96
|
-
(a) => { var _a; return (_a = a === null || a === void 0 ? void 0 : a.textContent) !== null && _a !== void 0 ? _a : ""; }, aadTile);
|
|
103
|
+
const aadTileMessage = await readTextContent(page, aadTile);
|
|
97
104
|
const msaTile = await page.$("#msaTileTitle");
|
|
98
|
-
|
|
99
|
-
const msaTileMessage = await page.evaluate(
|
|
100
|
-
// eslint-disable-next-line
|
|
101
|
-
(m) => { var _a; return (_a = m === null || m === void 0 ? void 0 : m.textContent) !== null && _a !== void 0 ? _a : ""; }, msaTile);
|
|
105
|
+
const msaTileMessage = await readTextContent(page, msaTile);
|
|
102
106
|
const accounts = [
|
|
103
107
|
aadTile ? { message: aadTileMessage, selector: "#aadTileTitle" } : null,
|
|
104
108
|
msaTile ? { message: msaTileMessage, selector: "#msaTileTitle" } : null,
|
|
105
|
-
].filter((
|
|
109
|
+
].filter((account) => account !== null);
|
|
106
110
|
let account;
|
|
107
111
|
if (accounts.length === 0) {
|
|
108
112
|
throw new CLIError_1.CLIError("No accounts found on account selection screen.");
|
|
@@ -110,10 +114,16 @@ exports.states = [
|
|
|
110
114
|
else if (accounts.length === 1) {
|
|
111
115
|
account = accounts[0];
|
|
112
116
|
}
|
|
117
|
+
else if (noPrompt) {
|
|
118
|
+
debug("Skipping account prompt and using default account");
|
|
119
|
+
account = accounts.find((candidate) => {
|
|
120
|
+
return candidate.message === aadTileMessage;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
113
123
|
else {
|
|
114
124
|
debug("Asking user to choose account");
|
|
115
|
-
|
|
116
|
-
const
|
|
125
|
+
printPageMessage("It looks like this Username is used with more than one account from Microsoft. Which one do you want to use?", allowSensitiveOutput);
|
|
126
|
+
const { account: selectedAccount } = await inquirer_1.default.prompt([
|
|
117
127
|
{
|
|
118
128
|
name: "account",
|
|
119
129
|
message: "Account:",
|
|
@@ -122,7 +132,9 @@ exports.states = [
|
|
|
122
132
|
default: aadTileMessage,
|
|
123
133
|
},
|
|
124
134
|
]);
|
|
125
|
-
account = accounts.find((
|
|
135
|
+
account = accounts.find((candidate) => {
|
|
136
|
+
return candidate.message === selectedAccount;
|
|
137
|
+
});
|
|
126
138
|
}
|
|
127
139
|
if (!account) {
|
|
128
140
|
throw new Error("Unable to find account");
|
|
@@ -135,32 +147,22 @@ exports.states = [
|
|
|
135
147
|
{
|
|
136
148
|
name: "passwordless",
|
|
137
149
|
selector: `input[value='Send notification']`,
|
|
138
|
-
async handler(page) {
|
|
150
|
+
async handler(page, _selected, _noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
139
151
|
debug("Sending notification");
|
|
140
|
-
// eslint-disable-next-line
|
|
141
152
|
await page.click("input[value='Send notification']");
|
|
142
153
|
debug("Waiting for auth code");
|
|
143
|
-
// eslint-disable-next-line
|
|
144
154
|
await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
|
|
145
155
|
visible: true,
|
|
146
156
|
timeout: 60000,
|
|
147
157
|
});
|
|
148
158
|
debug("Printing the message displayed");
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
150
159
|
const messageElement = await page.$("#idDiv_RemoteNGC_PollingDescription");
|
|
151
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
152
160
|
const codeElement = await page.$("#idRemoteNGC_DisplaySign");
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// eslint-disable-next-line
|
|
156
|
-
(el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, messageElement);
|
|
157
|
-
console.log(message);
|
|
161
|
+
const message = await readTextContent(page, messageElement);
|
|
162
|
+
printPageMessage(message, allowSensitiveOutput);
|
|
158
163
|
debug("Printing the auth code");
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// eslint-disable-next-line
|
|
162
|
-
(el) => { var _a; return (_a = el === null || el === void 0 ? void 0 : el.textContent) !== null && _a !== void 0 ? _a : ""; }, codeElement);
|
|
163
|
-
console.log(authCode);
|
|
164
|
+
const authCode = await readTextContent(page, codeElement);
|
|
165
|
+
printPageMessage(authCode, allowSensitiveOutput);
|
|
164
166
|
debug("Waiting for response");
|
|
165
167
|
await page.waitForSelector(`#idRemoteNGC_DisplaySign`, {
|
|
166
168
|
hidden: true,
|
|
@@ -170,16 +172,13 @@ exports.states = [
|
|
|
170
172
|
},
|
|
171
173
|
{
|
|
172
174
|
name: "password input",
|
|
173
|
-
selector:
|
|
174
|
-
async handler(page, _selected, noPrompt, _defaultUsername, defaultPassword) {
|
|
175
|
+
selector: PASSWORD_SELECTOR,
|
|
176
|
+
async handler(page, _selected, noPrompt, _defaultUsername, defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
175
177
|
const error = await page.$(".alert-error");
|
|
176
178
|
if (error) {
|
|
177
179
|
debug("Found error message. Displaying");
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// eslint-disable-next-line
|
|
181
|
-
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
182
|
-
console.log(errorMessage);
|
|
180
|
+
const errorMessage = await readTextContent(page, error);
|
|
181
|
+
printPageMessage(errorMessage, allowSensitiveOutput);
|
|
183
182
|
defaultPassword = ""; // Password error. Unset the default and allow user to enter it.
|
|
184
183
|
}
|
|
185
184
|
let password;
|
|
@@ -189,7 +188,6 @@ exports.states = [
|
|
|
189
188
|
}
|
|
190
189
|
else {
|
|
191
190
|
debug("Prompting user for password");
|
|
192
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
193
191
|
({ password } = await inquirer_1.default.prompt([
|
|
194
192
|
{
|
|
195
193
|
name: "password",
|
|
@@ -199,9 +197,13 @@ exports.states = [
|
|
|
199
197
|
]));
|
|
200
198
|
}
|
|
201
199
|
debug("Focusing on password input");
|
|
202
|
-
await page.focus(
|
|
200
|
+
await page.focus(PASSWORD_SELECTOR);
|
|
201
|
+
debug("Clearing input");
|
|
202
|
+
await page.$eval(PASSWORD_SELECTOR, (el) => {
|
|
203
|
+
el.select();
|
|
204
|
+
});
|
|
205
|
+
await page.keyboard.press("Backspace");
|
|
203
206
|
debug("Typing password");
|
|
204
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
205
207
|
await page.keyboard.type(password);
|
|
206
208
|
debug("Submitting form");
|
|
207
209
|
await page.click("span[class=submit],input[type=submit]");
|
|
@@ -212,11 +214,9 @@ exports.states = [
|
|
|
212
214
|
{
|
|
213
215
|
name: "TFA instructions",
|
|
214
216
|
selector: `#idDiv_SAOTCAS_Description`,
|
|
215
|
-
async handler(page, selected) {
|
|
216
|
-
const descriptionMessage = await page
|
|
217
|
-
|
|
218
|
-
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
219
|
-
console.log(descriptionMessage);
|
|
217
|
+
async handler(page, selected, _noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
218
|
+
const descriptionMessage = await readTextContent(page, selected);
|
|
219
|
+
printPageMessage(descriptionMessage, allowSensitiveOutput);
|
|
220
220
|
try {
|
|
221
221
|
debug("Waiting for authentication code to be displayed");
|
|
222
222
|
await page.waitForSelector("#idRichContext_DisplaySign", {
|
|
@@ -226,12 +226,9 @@ exports.states = [
|
|
|
226
226
|
debug("Checking if authentication code is displayed");
|
|
227
227
|
const authenticationCodeElement = await page.$("#idRichContext_DisplaySign");
|
|
228
228
|
debug("Reading the authentication code");
|
|
229
|
-
|
|
230
|
-
const authenticationCode = await page.evaluate(
|
|
231
|
-
// eslint-disable-next-line
|
|
232
|
-
(d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, authenticationCodeElement);
|
|
229
|
+
const authenticationCode = await readTextContent(page, authenticationCodeElement);
|
|
233
230
|
debug("Printing the authentication code to console");
|
|
234
|
-
|
|
231
|
+
printPageMessage(authenticationCode, allowSensitiveOutput);
|
|
235
232
|
}
|
|
236
233
|
catch (_a) {
|
|
237
234
|
debug("No authentication code found on page");
|
|
@@ -246,36 +243,26 @@ exports.states = [
|
|
|
246
243
|
{
|
|
247
244
|
name: "TFA failed",
|
|
248
245
|
selector: `#idDiv_SAASDS_Description,#idDiv_SAASTO_Description`,
|
|
249
|
-
async handler(page, selected) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// eslint-disable-next-line
|
|
253
|
-
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
254
|
-
throw new CLIError_1.CLIError(descriptionMessage);
|
|
246
|
+
async handler(page, selected, _noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
247
|
+
const descriptionMessage = await readTextContent(page, selected);
|
|
248
|
+
throw createSensitiveCliError(descriptionMessage, "Authentication failed during MFA challenge.", allowSensitiveOutput);
|
|
255
249
|
},
|
|
256
250
|
},
|
|
257
251
|
{
|
|
258
252
|
name: "TFA code input",
|
|
259
253
|
selector: "input[name=otc]:not(.moveOffScreen)",
|
|
260
|
-
async handler(page) {
|
|
254
|
+
async handler(page, _selected, _noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
261
255
|
const error = await page.$(".alert-error");
|
|
262
256
|
if (error) {
|
|
263
257
|
debug("Found error message. Displaying");
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// eslint-disable-next-line
|
|
267
|
-
(err) => { var _a; return (_a = err === null || err === void 0 ? void 0 : err.textContent) !== null && _a !== void 0 ? _a : ""; }, error);
|
|
268
|
-
console.log(errorMessage);
|
|
258
|
+
const errorMessage = await readTextContent(page, error);
|
|
259
|
+
printPageMessage(errorMessage, allowSensitiveOutput);
|
|
269
260
|
}
|
|
270
261
|
else {
|
|
271
262
|
const description = await page.$("#idDiv_SAOTCC_Description");
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// eslint-disable-next-line
|
|
275
|
-
(d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.textContent) !== null && _a !== void 0 ? _a : ""; }, description);
|
|
276
|
-
console.log(descriptionMessage);
|
|
263
|
+
const descriptionMessage = await readTextContent(page, description);
|
|
264
|
+
printPageMessage(descriptionMessage, allowSensitiveOutput);
|
|
277
265
|
}
|
|
278
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
279
266
|
const { verificationCode } = await inquirer_1.default.prompt([
|
|
280
267
|
{
|
|
281
268
|
type: "input",
|
|
@@ -291,7 +278,6 @@ exports.states = [
|
|
|
291
278
|
});
|
|
292
279
|
await page.keyboard.press("Backspace");
|
|
293
280
|
debug("Typing verification code");
|
|
294
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
295
281
|
await page.keyboard.type(verificationCode);
|
|
296
282
|
debug("Submitting form");
|
|
297
283
|
await page.click("input[type=submit]");
|
|
@@ -327,12 +313,9 @@ exports.states = [
|
|
|
327
313
|
{
|
|
328
314
|
name: "Service exception",
|
|
329
315
|
selector: "#service_exception_message",
|
|
330
|
-
async handler(page, selected) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
// eslint-disable-next-line
|
|
334
|
-
(description) => { var _a; return (_a = description === null || description === void 0 ? void 0 : description.textContent) !== null && _a !== void 0 ? _a : ""; }, selected);
|
|
335
|
-
throw new CLIError_1.CLIError(descriptionMessage);
|
|
316
|
+
async handler(page, selected, _noPrompt, _defaultUsername, _defaultPassword, _rememberMe, allowSensitiveOutput) {
|
|
317
|
+
const descriptionMessage = await readTextContent(page, selected);
|
|
318
|
+
throw createSensitiveCliError(descriptionMessage, "Login provider returned a service exception.", allowSensitiveOutput);
|
|
336
319
|
},
|
|
337
320
|
},
|
|
338
321
|
];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldAllowSensitiveOutput = shouldAllowSensitiveOutput;
|
|
4
|
+
exports.redactUrlForLogs = redactUrlForLogs;
|
|
5
|
+
exports.sanitizeMessage = sanitizeMessage;
|
|
6
|
+
exports.formatCliErrorMessage = formatCliErrorMessage;
|
|
7
|
+
exports.formatUnexpectedErrorMessage = formatUnexpectedErrorMessage;
|
|
8
|
+
exports.formatDebugErrorMessage = formatDebugErrorMessage;
|
|
9
|
+
const REDACTED = "[redacted]";
|
|
10
|
+
const SAFE_MICROSOFT_LOGIN_PATH_SEGMENTS = new Set([
|
|
11
|
+
"common",
|
|
12
|
+
"consumers",
|
|
13
|
+
"organizations",
|
|
14
|
+
"favicon.ico",
|
|
15
|
+
"kmsi",
|
|
16
|
+
]);
|
|
17
|
+
const URL_PATTERN = /\bhttps?:\/\/[^\s"'`<>]+/gi;
|
|
18
|
+
const AWS_ACCESS_KEY_ID_PATTERN = /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g;
|
|
19
|
+
const SENSITIVE_FIELD_NAMES = [
|
|
20
|
+
"AZURE_DEFAULT_PASSWORD",
|
|
21
|
+
"password",
|
|
22
|
+
"aws_access_key_id",
|
|
23
|
+
"aws_secret_access_key",
|
|
24
|
+
"aws_session_token",
|
|
25
|
+
"AccessKeyId",
|
|
26
|
+
"SecretAccessKey",
|
|
27
|
+
"SessionToken",
|
|
28
|
+
"SAMLAssertion",
|
|
29
|
+
"SAMLResponse",
|
|
30
|
+
"Authorization",
|
|
31
|
+
"Cookie",
|
|
32
|
+
"Set-Cookie",
|
|
33
|
+
];
|
|
34
|
+
const SENSITIVE_ASSIGNMENT_PATTERN = new RegExp(`((?:${SENSITIVE_FIELD_NAMES.join("|")})\\s*[=:]\\s*)(?:"[^"]*"|'[^']*'|.+)`, "gi");
|
|
35
|
+
const SENSITIVE_JSON_PATTERN = new RegExp(`((?:"(?:${SENSITIVE_FIELD_NAMES.join("|")})"\\s*:\\s*))(?:"[^"]*"|null|[^\\s,}]+)`, "gi");
|
|
36
|
+
function shouldAllowSensitiveOutput(env = process.env) {
|
|
37
|
+
return !env.CI && !env.GITHUB_ACTIONS;
|
|
38
|
+
}
|
|
39
|
+
function redactUrlForLogs(url) {
|
|
40
|
+
try {
|
|
41
|
+
const parsedUrl = new URL(url);
|
|
42
|
+
const pathSegments = parsedUrl.pathname.split("/");
|
|
43
|
+
if (parsedUrl.hostname === "login.microsoftonline.com" &&
|
|
44
|
+
pathSegments[1] &&
|
|
45
|
+
!SAFE_MICROSOFT_LOGIN_PATH_SEGMENTS.has(pathSegments[1])) {
|
|
46
|
+
pathSegments[1] = REDACTED;
|
|
47
|
+
parsedUrl.pathname = pathSegments.join("/");
|
|
48
|
+
}
|
|
49
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
50
|
+
parsedUrl.username = REDACTED;
|
|
51
|
+
parsedUrl.password = REDACTED;
|
|
52
|
+
}
|
|
53
|
+
for (const key of new Set(parsedUrl.searchParams.keys())) {
|
|
54
|
+
parsedUrl.searchParams.set(key, REDACTED);
|
|
55
|
+
}
|
|
56
|
+
if (parsedUrl.hash) {
|
|
57
|
+
parsedUrl.hash = REDACTED;
|
|
58
|
+
}
|
|
59
|
+
return parsedUrl.toString();
|
|
60
|
+
}
|
|
61
|
+
catch (_a) {
|
|
62
|
+
const questionMarkIndex = url.indexOf("?");
|
|
63
|
+
if (questionMarkIndex === -1) {
|
|
64
|
+
return url;
|
|
65
|
+
}
|
|
66
|
+
return `${url.slice(0, questionMarkIndex)}?${REDACTED}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function sanitizeMessage(message) {
|
|
70
|
+
return message
|
|
71
|
+
.replace(URL_PATTERN, (url) => redactUrlForLogs(url))
|
|
72
|
+
.replace(AWS_ACCESS_KEY_ID_PATTERN, REDACTED)
|
|
73
|
+
.replace(SENSITIVE_ASSIGNMENT_PATTERN, `$1${REDACTED}`)
|
|
74
|
+
.replace(SENSITIVE_JSON_PATTERN, `$1"${REDACTED}"`);
|
|
75
|
+
}
|
|
76
|
+
function formatCliErrorMessage(message, env = process.env) {
|
|
77
|
+
return shouldAllowSensitiveOutput(env) ? message : sanitizeMessage(message);
|
|
78
|
+
}
|
|
79
|
+
function formatUnexpectedErrorMessage(error, env = process.env) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
return shouldAllowSensitiveOutput(env) ? message : sanitizeMessage(message);
|
|
82
|
+
}
|
|
83
|
+
function formatDebugErrorMessage(error, env = process.env) {
|
|
84
|
+
return formatUnexpectedErrorMessage(error, env);
|
|
85
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sessionDurationHoursValidationMessage = void 0;
|
|
4
|
+
exports.parseSessionDurationHours = parseSessionDurationHours;
|
|
5
|
+
exports.validateSessionDurationHours = validateSessionDurationHours;
|
|
6
|
+
exports.sessionDurationHoursValidationMessage = "Duration hours must be a whole number between 1 and 12";
|
|
7
|
+
function parseSessionDurationHours(input) {
|
|
8
|
+
if (input === undefined) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
if (typeof input === "number") {
|
|
12
|
+
return Number.isInteger(input) && input > 0 && input <= 12 ? input : null;
|
|
13
|
+
}
|
|
14
|
+
const trimmed = input.trim();
|
|
15
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const parsed = Number(trimmed);
|
|
19
|
+
return parsed > 0 && parsed <= 12 ? parsed : null;
|
|
20
|
+
}
|
|
21
|
+
function validateSessionDurationHours(input) {
|
|
22
|
+
// Inquirer types `validate` input as string, but it may validate a numeric
|
|
23
|
+
// `default` value before coercing it to user input.
|
|
24
|
+
return parseSessionDurationHours(input) === null
|
|
25
|
+
? exports.sessionDurationHoursValidationMessage
|
|
26
|
+
: true;
|
|
27
|
+
}
|
package/lib/updateNotifier.js
CHANGED
|
@@ -13,12 +13,19 @@ const CACHE_TTL_MS = 1000 * 60 * 60 * 24; // 24 hours
|
|
|
13
13
|
const FAKE_LATEST_VERSION_ENV = "AZ2AWS_FAKE_LATEST_VERSION";
|
|
14
14
|
const ANSI_YELLOW = "\u001b[33m";
|
|
15
15
|
const ANSI_RESET = "\u001b[0m";
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const CONFIG_DIR_MODE = 0o700;
|
|
17
|
+
const CACHE_FILE_MODE = 0o600;
|
|
18
|
+
function getCachePath(env = process.env) {
|
|
19
|
+
var _a, _b;
|
|
20
|
+
if (process.platform === "win32") {
|
|
21
|
+
const cacheRoot = ((_a = env.LOCALAPPDATA) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = env.APPDATA) === null || _b === void 0 ? void 0 : _b.trim()) || os_1.default.homedir();
|
|
22
|
+
return path_1.default.win32.join(cacheRoot, PACKAGE_NAME, "update-check.json");
|
|
23
|
+
}
|
|
24
|
+
return path_1.default.join(os_1.default.homedir(), ".config", PACKAGE_NAME, "update-check.json");
|
|
18
25
|
}
|
|
19
|
-
function readCache() {
|
|
26
|
+
function readCache(env = process.env) {
|
|
20
27
|
try {
|
|
21
|
-
const data = fs_1.default.readFileSync(getCachePath(), "utf-8");
|
|
28
|
+
const data = fs_1.default.readFileSync(getCachePath(env), "utf-8");
|
|
22
29
|
const cache = JSON.parse(data);
|
|
23
30
|
if (Date.now() - cache.checkedAt < CACHE_TTL_MS) {
|
|
24
31
|
return cache;
|
|
@@ -29,12 +36,19 @@ function readCache() {
|
|
|
29
36
|
}
|
|
30
37
|
return null;
|
|
31
38
|
}
|
|
32
|
-
function writeCache(latestVersion) {
|
|
39
|
+
function writeCache(latestVersion, env = process.env) {
|
|
33
40
|
try {
|
|
34
|
-
const cachePath = getCachePath();
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
fs_1.default.
|
|
41
|
+
const cachePath = getCachePath(env);
|
|
42
|
+
const pathModule = process.platform === "win32" ? path_1.default.win32 : path_1.default;
|
|
43
|
+
const dir = pathModule.dirname(cachePath);
|
|
44
|
+
fs_1.default.mkdirSync(dir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
45
|
+
if (process.platform !== "win32") {
|
|
46
|
+
fs_1.default.chmodSync(dir, CONFIG_DIR_MODE);
|
|
47
|
+
}
|
|
48
|
+
fs_1.default.writeFileSync(cachePath, JSON.stringify({ latestVersion, checkedAt: Date.now() }), { mode: CACHE_FILE_MODE });
|
|
49
|
+
if (process.platform !== "win32") {
|
|
50
|
+
fs_1.default.chmodSync(cachePath, CACHE_FILE_MODE);
|
|
51
|
+
}
|
|
38
52
|
}
|
|
39
53
|
catch (_a) {
|
|
40
54
|
// Ignore cache write failures
|
|
@@ -126,13 +140,13 @@ async function checkForUpdate(currentVersion, options = {}) {
|
|
|
126
140
|
latestVersion = forcedLatestVersion;
|
|
127
141
|
}
|
|
128
142
|
else {
|
|
129
|
-
const cache = readCache();
|
|
143
|
+
const cache = readCache(env);
|
|
130
144
|
if (cache) {
|
|
131
145
|
latestVersion = cache.latestVersion;
|
|
132
146
|
}
|
|
133
147
|
else {
|
|
134
148
|
latestVersion = await fetchLatestVersion();
|
|
135
|
-
writeCache(latestVersion);
|
|
149
|
+
writeCache(latestVersion, env);
|
|
136
150
|
}
|
|
137
151
|
}
|
|
138
152
|
if (compareVersions(currentVersion, latestVersion) > 0) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateCliOptions = validateCliOptions;
|
|
4
|
+
const CLIError_1 = require("./CLIError");
|
|
5
|
+
function validateCliOptions(options) {
|
|
6
|
+
if (options.allProfiles && options.credentialProcess) {
|
|
7
|
+
throw new CLIError_1.CLIError("--credential-process cannot be used with --all-profiles.");
|
|
8
|
+
}
|
|
9
|
+
if (options.configure && options.credentialProcess) {
|
|
10
|
+
throw new CLIError_1.CLIError("--credential-process cannot be used with --configure.");
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "az2aws",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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": "lib/index.js",
|
|
6
6
|
"author": {
|
|
@@ -37,13 +37,15 @@
|
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=24.0"
|
|
39
39
|
},
|
|
40
|
-
"packageManager": "pnpm@10.
|
|
40
|
+
"packageManager": "pnpm@10.33.2",
|
|
41
41
|
"pnpm": {
|
|
42
42
|
"onlyBuiltDependencies": [
|
|
43
43
|
"esbuild",
|
|
44
44
|
"puppeteer"
|
|
45
45
|
],
|
|
46
46
|
"overrides": {
|
|
47
|
+
"basic-ftp@<5.3.0": "5.3.0",
|
|
48
|
+
"brace-expansion@<1.1.13": "1.1.13",
|
|
47
49
|
"yauzl@<3.2.1": "3.2.1"
|
|
48
50
|
}
|
|
49
51
|
},
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
"prettier:write": "prettier --write \"src/**/*.{ts,json}\"",
|
|
59
61
|
"prettier:check": "prettier --check \"src/**/*.ts\"",
|
|
60
62
|
"test": "vitest run",
|
|
63
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
61
64
|
"test:watch": "vitest",
|
|
62
65
|
"test:coverage": "vitest run --coverage",
|
|
63
66
|
"lint": "pnpm eslint && pnpm prettier:check"
|