ag-awsauth 0.0.292 → 0.0.294

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.
@@ -2,7 +2,9 @@ import type { Page } from 'puppeteer';
2
2
  export declare const closeBrowser: () => Promise<void>;
3
3
  export declare const launchBrowser: () => Promise<void>;
4
4
  export declare const goToPage: (url: string) => Promise<Page>;
5
- export declare const allowAccessIfRequested: (page: Page) => Promise<void>;
5
+ export declare const allowAccessSelectors: string[];
6
+ export declare const dismissAllowAccessPrompt: (page: Page, perSelectorTimeout?: number) => Promise<boolean>;
7
+ export declare const allowAccessIfRequested: (page: Page, timeout?: number) => Promise<boolean>;
6
8
  export declare function getMFA(p: {
7
9
  verificationUriComplete: string;
8
10
  creds: {
@@ -9,9 +9,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.allowAccessIfRequested = exports.goToPage = exports.launchBrowser = exports.closeBrowser = void 0;
12
+ exports.allowAccessIfRequested = exports.dismissAllowAccessPrompt = exports.allowAccessSelectors = exports.goToPage = exports.launchBrowser = exports.closeBrowser = void 0;
13
13
  exports.getMFA = getMFA;
14
14
  const log_1 = require("ag-common/dist/common/helpers/log");
15
+ const sleep_1 = require("ag-common/dist/common/helpers/sleep");
15
16
  const puppeteer_1 = require("puppeteer");
16
17
  const __1 = require("..");
17
18
  const config_1 = require("../config");
@@ -83,15 +84,173 @@ const goToPage = (url) => __awaiter(void 0, void 0, void 0, function* () {
83
84
  }
84
85
  });
85
86
  exports.goToPage = goToPage;
86
- const allowAccessIfRequested = (page) => __awaiter(void 0, void 0, void 0, function* () {
87
- const allowAccess = yield page.waitForSelector('[data-testid="allow-access-button"]', { timeout: config_1.timeoutMs, visible: true });
88
- if (!allowAccess) {
89
- throw new Error('allow access button not found');
87
+ exports.allowAccessSelectors = [
88
+ '[data-testid="allow-access-button"]',
89
+ '[data-analytics="allow-access-button"]',
90
+ 'button[data-testid="allow-access-button"]',
91
+ ];
92
+ const clickSelectorIfPresent = (page_1, selector_1, ...args_1) => __awaiter(void 0, [page_1, selector_1, ...args_1], void 0, function* (page, selector, timeout = config_1.timeoutShortMs) {
93
+ try {
94
+ const el = yield page.waitForSelector(selector, {
95
+ timeout,
96
+ visible: true,
97
+ });
98
+ if (!el) {
99
+ return false;
100
+ }
101
+ try {
102
+ yield el.click();
103
+ }
104
+ catch (_a) {
105
+ // Element may detach between query and click (SPA re-render).
106
+ // Fall back to an in-page click which re-queries the selector.
107
+ yield page.$eval(selector, (bn) => bn.click()).catch(() => undefined);
108
+ }
109
+ return true;
110
+ }
111
+ catch (_b) {
112
+ return false;
113
+ }
114
+ });
115
+ const clickAllowAccessByText = (page) => {
116
+ try {
117
+ const clicked = page.evaluate(() => {
118
+ const isVisible = (el) => {
119
+ const rect = el.getBoundingClientRect();
120
+ return rect.width > 0 && rect.height > 0 && getComputedStyle(el).visibility !== 'hidden';
121
+ };
122
+ // Match button, link, or ARIA-button variants: AWS renders the
123
+ // "Allow access" consent action differently across releases
124
+ // (orange button vs link), so don't assume a <button>.
125
+ const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="submit"], input[type="button"]')).filter((el) => isVisible(el));
126
+ const label = (el) => {
127
+ var _a;
128
+ const inputValue = el.value;
129
+ // oxlint-disable-next-line typescript/no-unnecessary-condition -- value is undefined on non-inputs
130
+ const text = typeof inputValue === 'string' ? inputValue : ((_a = el.textContent) !== null && _a !== void 0 ? _a : '');
131
+ return text.trim().toLowerCase();
132
+ };
133
+ const exact = candidates.find((el) => label(el) === 'allow access');
134
+ const target = exact !== null && exact !== void 0 ? exact : candidates.find((el) => label(el).includes('allow access'));
135
+ if (target) {
136
+ target.click();
137
+ return true;
138
+ }
139
+ return false;
140
+ });
141
+ return clicked;
142
+ }
143
+ catch (_a) {
144
+ return Promise.resolve(false);
145
+ }
146
+ };
147
+ const dismissAllowAccessPrompt = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, perSelectorTimeout = 1000) {
148
+ const clicked = yield Promise.all(exports.allowAccessSelectors.map((selector) => clickSelectorIfPresent(page, selector, perSelectorTimeout)));
149
+ if (clicked.some(Boolean)) {
150
+ (0, log_1.info)('click allow-access-button');
151
+ return true;
152
+ }
153
+ if (yield clickAllowAccessByText(page)) {
154
+ (0, log_1.info)('click allow-access-button (text match "Allow access")');
155
+ return true;
156
+ }
157
+ return false;
158
+ });
159
+ exports.dismissAllowAccessPrompt = dismissAllowAccessPrompt;
160
+ const pollDelayMs = 500;
161
+ const sleepUntilDeadline = (deadline) => __awaiter(void 0, void 0, void 0, function* () {
162
+ const remaining = deadline - Date.now();
163
+ if (remaining <= 0) {
164
+ return;
165
+ }
166
+ yield (0, sleep_1.sleep)(Math.min(pollDelayMs, remaining));
167
+ });
168
+ const allowAccessIfRequested = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, timeout = config_1.timeoutMs) {
169
+ const deadline = Date.now() + timeout;
170
+ // Poll so a slow-rendering consent dialog is not missed, while still
171
+ // returning quickly when the prompt appears (or never appears).
172
+ // oxlint-disable-next-line no-await-in-loop -- polling must stay sequential to match dialog timing
173
+ for (;;) {
174
+ // oxlint-disable-next-line no-await-in-loop -- each poll depends on the current page state
175
+ if (yield (0, exports.dismissAllowAccessPrompt)(page)) {
176
+ return true;
177
+ }
178
+ if (Date.now() >= deadline) {
179
+ return false;
180
+ }
181
+ // oxlint-disable-next-line no-await-in-loop -- delay must elapse before the next poll
182
+ yield sleepUntilDeadline(deadline);
90
183
  }
91
- (0, log_1.info)('click allow-access-button');
92
- yield allowAccess.click();
93
184
  });
94
185
  exports.allowAccessIfRequested = allowAccessIfRequested;
186
+ const waitForSuccessDefensively = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, timeout = config_1.timeoutMs) {
187
+ const deadline = Date.now() + timeout;
188
+ let lastError;
189
+ // oxlint-disable-next-line no-await-in-loop -- success and consent checks are order-dependent
190
+ for (;;) {
191
+ // Success already visible?
192
+ try {
193
+ // oxlint-disable-next-line no-await-in-loop -- must check before dismissing prompts
194
+ const success = yield page.waitForSelector('[data-analytics-alert="success"]', { timeout: 2000 });
195
+ if (success) {
196
+ return;
197
+ }
198
+ }
199
+ catch (e) {
200
+ lastError = e;
201
+ }
202
+ // Defensively dismiss the consent prompt if it is (still) showing.
203
+ // This covers the "Allow <user> to access your data? / Allow access"
204
+ // dialog which otherwise blocks success indefinitely.
205
+ try {
206
+ // oxlint-disable-next-line no-await-in-loop -- prompt check follows the success check
207
+ if (yield (0, exports.dismissAllowAccessPrompt)(page)) {
208
+ (0, log_1.info)('dismissed allow-access prompt while waiting for success');
209
+ continue;
210
+ }
211
+ }
212
+ catch (e) {
213
+ (0, log_1.debug)('error dismissing allow-access prompt:', e);
214
+ }
215
+ // Also re-check the other intermediate confirmations while polling.
216
+ try {
217
+ // oxlint-disable-next-line no-await-in-loop -- confirmation check follows the success check
218
+ if (yield clickSelectorIfPresent(page, '[data-analytics="accept-user-code"]', 1000)) {
219
+ (0, log_1.info)('click accept-user-code (while waiting for success)');
220
+ continue;
221
+ }
222
+ }
223
+ catch (e) {
224
+ (0, log_1.debug)('error clicking accept-user-code while waiting:', e);
225
+ }
226
+ try {
227
+ // oxlint-disable-next-line no-await-in-loop -- auth request check follows the success check
228
+ if (yield clickSelectorIfPresent(page, '#cli_verification_btn', 1000)) {
229
+ (0, log_1.info)('clicking auth request button (while waiting for success)');
230
+ }
231
+ }
232
+ catch (e) {
233
+ (0, log_1.debug)('error clicking auth request button while waiting:', e);
234
+ }
235
+ if (Date.now() >= deadline) {
236
+ break;
237
+ }
238
+ // oxlint-disable-next-line no-await-in-loop -- delay must elapse before the next poll
239
+ yield sleepUntilDeadline(deadline);
240
+ }
241
+ // Final wait to surface a clear TimeoutError if success never arrived.
242
+ try {
243
+ yield page.waitForSelector('[data-analytics-alert="success"]', {
244
+ timeout: config_1.timeoutShortMs,
245
+ });
246
+ }
247
+ catch (_a) {
248
+ if (lastError instanceof Error) {
249
+ throw lastError;
250
+ }
251
+ throw new Error('Timed out waiting for success');
252
+ }
253
+ });
95
254
  function getMFA(p) {
96
255
  return __awaiter(this, void 0, void 0, function* () {
97
256
  var _a;
@@ -203,15 +362,11 @@ function getMFA(p) {
203
362
  }
204
363
  try {
205
364
  (0, log_1.info)('accept-user-code');
206
- const messageDiv = yield page.waitForSelector('[data-analytics="accept-user-code"]', {
207
- timeout: config_1.timeoutMs,
208
- });
209
- if (messageDiv) {
365
+ if (yield clickSelectorIfPresent(page, '[data-analytics="accept-user-code"]', config_1.timeoutMs)) {
210
366
  (0, log_1.info)('click accept-user-code');
211
- yield messageDiv.click();
212
367
  }
213
368
  else {
214
- throw new Error('access prompt not found');
369
+ (0, log_1.debug)('accept-user-code prompt not present, continuing');
215
370
  }
216
371
  }
217
372
  catch (e) {
@@ -220,8 +375,10 @@ function getMFA(p) {
220
375
  let accessStateFound = false;
221
376
  try {
222
377
  (0, log_1.info)('allow-access-button');
223
- yield (0, exports.allowAccessIfRequested)(page);
224
- accessStateFound = true;
378
+ accessStateFound = yield (0, exports.allowAccessIfRequested)(page, config_1.timeoutMedMs);
379
+ if (!accessStateFound) {
380
+ (0, log_1.debug)('allow-access prompt not present, continuing');
381
+ }
225
382
  }
226
383
  catch (e) {
227
384
  (0, log_1.debug)('error clicking allow-access-button:', e);
@@ -242,9 +399,7 @@ function getMFA(p) {
242
399
  }
243
400
  //
244
401
  (0, log_1.info)('waiting for success');
245
- yield page.waitForSelector('[data-analytics-alert="success"]', {
246
- timeout: config_1.timeoutMs,
247
- });
402
+ yield waitForSuccessDefensively(page, config_1.timeoutMs);
248
403
  (0, log_1.warn)('mfa success');
249
404
  const cookies = yield page.cookies();
250
405
  const ssoAuthn = (_a = cookies.find((c) => c.name === 'x-amz-sso_authn')) === null || _a === void 0 ? void 0 : _a.value;
package/package.json CHANGED
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "name": "ag-awsauth",
3
+ "version": "0.0.294",
4
+ "private": false,
3
5
  "description": "auth to aws sso/iamv2 easily",
4
- "main": "dist/index.js",
5
- "author": "andreigec@hotmail.com",
6
6
  "license": "ISC",
7
- "private": false,
8
- "version": "0.0.292",
9
- "preferGlobal": true,
7
+ "author": "andreigec@hotmail.com",
10
8
  "bin": {
11
9
  "ag-awsauth": "./bin/awsauth.js"
12
10
  },
@@ -16,18 +14,21 @@
16
14
  "README.md",
17
15
  "LICENSE.md"
18
16
  ],
17
+ "main": "dist/index.js",
19
18
  "dependencies": {
20
19
  "@aws-sdk/client-sso": "3.972.0",
21
20
  "@aws-sdk/client-sso-oidc": "3.972.0",
22
21
  "@aws-sdk/client-sts": "3.972.0",
23
22
  "@aws-sdk/shared-ini-file-loader": "3.374.0",
24
- "ag-common": "0.0.861",
23
+ "ag-common": "0.0.911",
25
24
  "cli-select": "1.1.2",
26
25
  "dotenv": "17.2.3",
27
26
  "envfile": "7.1.0",
28
- "eslint-config-e7npm": "0.1.31",
27
+ "eslint-config-e7npm": "0.1.68",
29
28
  "ini": "5.0.0",
30
29
  "node-fetch": "3.3.2",
30
+ "oxfmt": "^0.65.0",
31
+ "oxlint": "^1.83.0",
31
32
  "puppeteer": "24.35.0",
32
33
  "readline-sync": "1.4.10",
33
34
  "typescript": "5.9.3",
@@ -43,12 +44,14 @@
43
44
  "resolutions": {
44
45
  "ws": ">=8.17.1"
45
46
  },
47
+ "preferGlobal": true,
46
48
  "engines": {
47
49
  "node": ">=20"
48
50
  },
49
51
  "scripts": {
50
- "lint": "eslint src",
51
- "format": "eslint src --fix",
52
+ "format": "e7-oxfmt --config .oxfmtrc.json --disable-nested-config .",
53
+ "lint": "e7-oxlint --config .oxlintrc.json --deny-warnings src && pnpm run typecheck",
54
+ "typecheck": "tsc --noEmit",
52
55
  "test": "tsc && node --test test/*.test.js",
53
56
  "start": "tsc && node bin/awsauth.js",
54
57
  "build": "tsc"