Adds the one-session Scaleway IAM grant script, merges the publisher bucket probes into one statement to fit the 10-statement limit, keeps the Playwright cache under the account home during attended login, and records the publisher-gate runs through the num_uses rejection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 49552@bnt-lap001 Assistant-Session: 575802ee-c0c0-42a9-b2a1-9204c9f5b3f4
80 lines
3.5 KiB
JavaScript
80 lines
3.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/** One attended Console login. The helper compares and corrects the MAS IAM rule. */
|
|
import {spawn} from 'node:child_process';
|
|
import {readFileSync} from 'node:fs';
|
|
import {dirname, join, resolve} from 'node:path';
|
|
import {fileURLToPath, pathToFileURL} from 'node:url';
|
|
import {parseArgs} from 'node:util';
|
|
import {createInterface} from 'node:readline/promises';
|
|
|
|
const {values} = parseArgs({options: {'playwright-module': {type: 'string'}}});
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const grant = join(root, 'scripts', 'mas_evidence_iam_grant.py');
|
|
let browser;
|
|
let child;
|
|
|
|
async function sessionAuth(page) {
|
|
if (new URL(page.url()).origin !== 'https://console.scaleway.com') {
|
|
throw new Error('console_page_required');
|
|
}
|
|
return page.evaluate(() => {
|
|
const names = Object.keys(localStorage).filter((name) => name.startsWith('_scw_session_console_scaleway_com_'));
|
|
if (names.length !== 1) throw new Error('unambiguous_session_required');
|
|
const session = JSON.parse(localStorage.getItem(names[0]));
|
|
if (new Date(session.jwt.expiresAt).getTime() < Date.now() + 30000) {
|
|
throw new Error('fresh_console_session_required');
|
|
}
|
|
return {session_token: session.token, user_agent: session.jwt.userAgent};
|
|
});
|
|
}
|
|
|
|
function runGrant(auth) {
|
|
return new Promise((resolveResult, reject) => {
|
|
child = spawn('python3', [grant, '--execute'], {stdio: ['pipe', 'pipe', 'pipe']});
|
|
let output = '';
|
|
child.stdout.on('data', (data) => {
|
|
if (output.length < 65536) output += data.toString();
|
|
});
|
|
child.stderr.on('data', () => {});
|
|
child.on('error', () => reject(new Error('grant_child_failed')));
|
|
child.on('close', (code) => {
|
|
child = null;
|
|
resolveResult({exit_code: code, output});
|
|
});
|
|
child.stdin.end(JSON.stringify(auth));
|
|
});
|
|
}
|
|
|
|
try {
|
|
const modulePath = resolve(values['playwright-module'] || '');
|
|
const pkg = JSON.parse(readFileSync(join(dirname(modulePath), 'package.json'), 'utf8'));
|
|
if (pkg.name !== 'playwright' || pkg.version !== '1.62.1') throw new Error('locked_playwright_required');
|
|
const imported = await import(pathToFileURL(modulePath).href);
|
|
const playwright = imported.chromium ? imported : imported.default;
|
|
if (!playwright?.chromium?.launch) throw new Error('playwright_export_unusable');
|
|
const {chromium} = playwright;
|
|
browser = await chromium.launch({headless: false});
|
|
const context = await browser.newContext();
|
|
const page = await context.newPage();
|
|
await page.goto('https://console.scaleway.com/', {waitUntil: 'domcontentloaded', timeout: 60000});
|
|
const reader = createInterface({input: process.stdin, output: process.stdout});
|
|
console.log('Console opened. Sign in, then press Enter. The session token stays in this process.');
|
|
await reader.question('');
|
|
reader.close();
|
|
const outcome = await runGrant(await sessionAuth(page));
|
|
process.stdout.write(outcome.output.endsWith('\n') ? outcome.output : `${outcome.output}\n`);
|
|
process.exitCode = outcome.exit_code === 0 ? 0 : 1;
|
|
try {
|
|
await page.goto('https://account.scaleway.com/logout', {waitUntil: 'domcontentloaded', timeout: 30000});
|
|
} catch {}
|
|
await context.clearCookies();
|
|
} catch (error) {
|
|
const code = typeof error?.message === 'string' && /^[a-z0-9_]+$/.test(error.message)
|
|
? error.message
|
|
: (error?.name || 'Error');
|
|
console.error(`IAM grant session failed: ${code}; no credential values printed.`);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
if (child) child.kill('SIGTERM');
|
|
if (browser) await browser.close();
|
|
}
|