ag-awsauth 0.0.292 → 0.0.293

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,7 +9,7 @@ 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
15
  const puppeteer_1 = require("puppeteer");
@@ -83,15 +83,154 @@ const goToPage = (url) => __awaiter(void 0, void 0, void 0, function* () {
83
83
  }
84
84
  });
85
85
  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');
86
+ exports.allowAccessSelectors = [
87
+ '[data-testid="allow-access-button"]',
88
+ '[data-analytics="allow-access-button"]',
89
+ 'button[data-testid="allow-access-button"]',
90
+ ];
91
+ 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) {
92
+ try {
93
+ const el = yield page.waitForSelector(selector, {
94
+ timeout,
95
+ visible: true,
96
+ });
97
+ if (!el) {
98
+ return false;
99
+ }
100
+ try {
101
+ yield el.click();
102
+ }
103
+ catch (_a) {
104
+ // Element may detach between query and click (SPA re-render).
105
+ // Fall back to an in-page click which re-queries the selector.
106
+ yield page
107
+ .$eval(selector, (bn) => bn.click())
108
+ .catch(() => undefined);
109
+ }
110
+ return true;
111
+ }
112
+ catch (_b) {
113
+ return false;
114
+ }
115
+ });
116
+ const clickAllowAccessByText = (page) => __awaiter(void 0, void 0, void 0, function* () {
117
+ try {
118
+ return yield page.evaluate(() => {
119
+ const isVisible = (el) => {
120
+ const rect = el.getBoundingClientRect();
121
+ return (rect.width > 0 &&
122
+ rect.height > 0 &&
123
+ getComputedStyle(el).visibility !== 'hidden');
124
+ };
125
+ // Match button, link, or ARIA-button variants: AWS renders the
126
+ // "Allow access" consent action differently across releases
127
+ // (orange button vs link), so don't assume a <button>.
128
+ const candidates = Array.from(document.querySelectorAll('button, a, [role="button"], input[type="submit"], input[type="button"]')).filter((el) => isVisible(el));
129
+ const label = (el) => {
130
+ var _a, _b;
131
+ return ((_b = (_a = el.value) !== null && _a !== void 0 ? _a : el.textContent) !== null && _b !== void 0 ? _b : '')
132
+ .trim()
133
+ .toLowerCase();
134
+ };
135
+ const exact = candidates.find((el) => label(el) === 'allow access');
136
+ const target = exact !== null && exact !== void 0 ? exact : candidates.find((el) => label(el).includes('allow access'));
137
+ if (target) {
138
+ target.click();
139
+ return true;
140
+ }
141
+ return false;
142
+ });
143
+ }
144
+ catch (_a) {
145
+ return false;
90
146
  }
91
- (0, log_1.info)('click allow-access-button');
92
- yield allowAccess.click();
147
+ });
148
+ const dismissAllowAccessPrompt = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, perSelectorTimeout = 1000) {
149
+ for (const selector of exports.allowAccessSelectors) {
150
+ if (yield clickSelectorIfPresent(page, selector, perSelectorTimeout)) {
151
+ (0, log_1.info)(`click allow-access-button (${selector})`);
152
+ return true;
153
+ }
154
+ }
155
+ if (yield clickAllowAccessByText(page)) {
156
+ (0, log_1.info)('click allow-access-button (text match "Allow access")');
157
+ return true;
158
+ }
159
+ return false;
160
+ });
161
+ exports.dismissAllowAccessPrompt = dismissAllowAccessPrompt;
162
+ const allowAccessIfRequested = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, timeout = config_1.timeoutMs) {
163
+ const deadline = Date.now() + timeout;
164
+ // Poll so a slow-rendering consent dialog is not missed, while still
165
+ // returning quickly when the prompt appears (or never appears).
166
+ while (Date.now() < deadline) {
167
+ if (yield (0, exports.dismissAllowAccessPrompt)(page)) {
168
+ return true;
169
+ }
170
+ if (Date.now() + 500 >= deadline) {
171
+ break;
172
+ }
173
+ yield new Promise((resolve) => setTimeout(resolve, 500));
174
+ }
175
+ return false;
93
176
  });
94
177
  exports.allowAccessIfRequested = allowAccessIfRequested;
178
+ const waitForSuccessDefensively = (page_1, ...args_1) => __awaiter(void 0, [page_1, ...args_1], void 0, function* (page, timeout = config_1.timeoutMs) {
179
+ var _a;
180
+ const deadline = Date.now() + timeout;
181
+ let lastError;
182
+ while (Date.now() < deadline) {
183
+ // Success already visible?
184
+ try {
185
+ const success = yield page.waitForSelector('[data-analytics-alert="success"]', { timeout: 2000 });
186
+ if (success) {
187
+ return;
188
+ }
189
+ }
190
+ catch (e) {
191
+ lastError = e;
192
+ }
193
+ // Defensively dismiss the consent prompt if it is (still) showing.
194
+ // This covers the "Allow <user> to access your data? / Allow access"
195
+ // dialog which otherwise blocks success indefinitely.
196
+ try {
197
+ if (yield (0, exports.dismissAllowAccessPrompt)(page)) {
198
+ (0, log_1.info)('dismissed allow-access prompt while waiting for success');
199
+ continue;
200
+ }
201
+ }
202
+ catch (e) {
203
+ (0, log_1.debug)('error dismissing allow-access prompt:', e);
204
+ }
205
+ // Also re-check the other intermediate confirmations while polling.
206
+ try {
207
+ if (yield clickSelectorIfPresent(page, '[data-analytics="accept-user-code"]', 1000)) {
208
+ (0, log_1.info)('click accept-user-code (while waiting for success)');
209
+ continue;
210
+ }
211
+ }
212
+ catch (e) {
213
+ (0, log_1.debug)('error clicking accept-user-code while waiting:', e);
214
+ }
215
+ try {
216
+ if (yield clickSelectorIfPresent(page, '#cli_verification_btn', 1000)) {
217
+ (0, log_1.info)('clicking auth request button (while waiting for success)');
218
+ }
219
+ }
220
+ catch (e) {
221
+ (0, log_1.debug)('error clicking auth request button while waiting:', e);
222
+ }
223
+ }
224
+ // Final wait to surface a clear TimeoutError if success never arrived.
225
+ try {
226
+ yield page.waitForSelector('[data-analytics-alert="success"]', {
227
+ timeout: config_1.timeoutShortMs,
228
+ });
229
+ }
230
+ catch (_b) {
231
+ throw ((_a = lastError) !== null && _a !== void 0 ? _a : new Error('Timed out waiting for success'));
232
+ }
233
+ });
95
234
  function getMFA(p) {
96
235
  return __awaiter(this, void 0, void 0, function* () {
97
236
  var _a;
@@ -203,15 +342,11 @@ function getMFA(p) {
203
342
  }
204
343
  try {
205
344
  (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) {
345
+ if (yield clickSelectorIfPresent(page, '[data-analytics="accept-user-code"]', config_1.timeoutMs)) {
210
346
  (0, log_1.info)('click accept-user-code');
211
- yield messageDiv.click();
212
347
  }
213
348
  else {
214
- throw new Error('access prompt not found');
349
+ (0, log_1.debug)('accept-user-code prompt not present, continuing');
215
350
  }
216
351
  }
217
352
  catch (e) {
@@ -220,8 +355,10 @@ function getMFA(p) {
220
355
  let accessStateFound = false;
221
356
  try {
222
357
  (0, log_1.info)('allow-access-button');
223
- yield (0, exports.allowAccessIfRequested)(page);
224
- accessStateFound = true;
358
+ accessStateFound = yield (0, exports.allowAccessIfRequested)(page, config_1.timeoutMedMs);
359
+ if (!accessStateFound) {
360
+ (0, log_1.debug)('allow-access prompt not present, continuing');
361
+ }
225
362
  }
226
363
  catch (e) {
227
364
  (0, log_1.debug)('error clicking allow-access-button:', e);
@@ -242,9 +379,7 @@ function getMFA(p) {
242
379
  }
243
380
  //
244
381
  (0, log_1.info)('waiting for success');
245
- yield page.waitForSelector('[data-analytics-alert="success"]', {
246
- timeout: config_1.timeoutMs,
247
- });
382
+ yield waitForSuccessDefensively(page, config_1.timeoutMs);
248
383
  (0, log_1.warn)('mfa success');
249
384
  const cookies = yield page.cookies();
250
385
  const ssoAuthn = (_a = cookies.find((c) => c.name === 'x-amz-sso_authn')) === null || _a === void 0 ? void 0 : _a.value;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "author": "andreigec@hotmail.com",
6
6
  "license": "ISC",
7
7
  "private": false,
8
- "version": "0.0.292",
8
+ "version": "0.0.293",
9
9
  "preferGlobal": true,
10
10
  "bin": {
11
11
  "ag-awsauth": "./bin/awsauth.js"