init
This commit is contained in:
230
.opencode/skills/chrome-devtools/scripts/inject-auth.js
Executable file
230
.opencode/skills/chrome-devtools/scripts/inject-auth.js
Executable file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Inject authentication cookies/tokens into browser session
|
||||
* Usage: node inject-auth.js --url https://example.com --cookies '[{"name":"token","value":"xxx","domain":".example.com"}]'
|
||||
* node inject-auth.js --url https://example.com --token "Bearer xxx" [--header Authorization]
|
||||
* node inject-auth.js --url https://example.com --local-storage '{"key":"value"}'
|
||||
* node inject-auth.js --url https://example.com --session-storage '{"key":"value"}'
|
||||
*
|
||||
* This script injects authentication data into browser session for testing protected routes.
|
||||
* The session persists across script executions until --close true is used.
|
||||
*
|
||||
* Workflow for testing protected routes:
|
||||
* 1. User manually logs into the site in their browser
|
||||
* 2. User extracts cookies/tokens from browser DevTools
|
||||
* 3. Run this script to inject auth into puppeteer session
|
||||
* 4. Run other scripts (screenshot, navigate, etc.) which will use authenticated session
|
||||
*
|
||||
* Session behavior:
|
||||
* --close false : Keep browser running (default for chaining)
|
||||
* --close true : Close browser completely and clear session
|
||||
*/
|
||||
import { getBrowser, getPage, closeBrowser, disconnectBrowser, parseArgs, outputJSON, outputError, saveAuthSession, clearAuthSession } from './lib/browser.js';
|
||||
|
||||
/**
|
||||
* Parse cookies from JSON string or file
|
||||
* @param {string} cookiesInput - JSON string or file path
|
||||
* @returns {Array} - Array of cookie objects
|
||||
*/
|
||||
function parseCookies(cookiesInput) {
|
||||
try {
|
||||
// Try parsing as JSON string
|
||||
return JSON.parse(cookiesInput);
|
||||
} catch {
|
||||
throw new Error(`Invalid cookies format. Expected JSON array: [{"name":"cookie_name","value":"cookie_value","domain":".example.com"}]`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse storage data from JSON string
|
||||
* @param {string} storageInput - JSON string
|
||||
* @returns {Object} - Storage key-value pairs
|
||||
*/
|
||||
function parseStorage(storageInput) {
|
||||
try {
|
||||
return JSON.parse(storageInput);
|
||||
} catch {
|
||||
throw new Error(`Invalid storage format. Expected JSON object: {"key":"value"}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function injectAuth() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
if (!args.url) {
|
||||
outputError(new Error('--url is required (base URL for the protected site)'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate at least one auth method provided
|
||||
if (!args.cookies && !args.token && !args['local-storage'] && !args['session-storage']) {
|
||||
outputError(new Error('At least one auth method required: --cookies, --token, --local-storage, or --session-storage'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const browser = await getBrowser({
|
||||
headless: args.headless
|
||||
});
|
||||
|
||||
const page = await getPage(browser);
|
||||
|
||||
// Navigate to the URL first to set the domain context
|
||||
await page.goto(args.url, {
|
||||
waitUntil: args['wait-until'] || 'networkidle2',
|
||||
timeout: parseInt(args.timeout || '30000')
|
||||
});
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
url: args.url,
|
||||
injected: []
|
||||
};
|
||||
|
||||
// Inject cookies
|
||||
if (args.cookies) {
|
||||
const cookies = parseCookies(args.cookies);
|
||||
|
||||
// Validate and normalize cookies
|
||||
const normalizedCookies = cookies.map(cookie => {
|
||||
if (!cookie.name || !cookie.value) {
|
||||
throw new Error(`Cookie must have 'name' and 'value' properties`);
|
||||
}
|
||||
|
||||
// Extract domain from URL if not provided
|
||||
if (!cookie.domain) {
|
||||
const urlObj = new URL(args.url);
|
||||
cookie.domain = urlObj.hostname;
|
||||
}
|
||||
|
||||
return {
|
||||
name: cookie.name,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain,
|
||||
path: cookie.path || '/',
|
||||
httpOnly: cookie.httpOnly !== undefined ? cookie.httpOnly : false,
|
||||
secure: cookie.secure !== undefined ? cookie.secure : args.url.startsWith('https'),
|
||||
sameSite: cookie.sameSite || 'Lax',
|
||||
...(cookie.expires && { expires: cookie.expires })
|
||||
};
|
||||
});
|
||||
|
||||
await page.setCookie(...normalizedCookies);
|
||||
result.injected.push({
|
||||
type: 'cookies',
|
||||
count: normalizedCookies.length,
|
||||
names: normalizedCookies.map(c => c.name)
|
||||
});
|
||||
}
|
||||
|
||||
// Inject Bearer token via localStorage (common pattern)
|
||||
if (args.token) {
|
||||
const tokenKey = args['token-key'] || 'access_token';
|
||||
const token = args.token.startsWith('Bearer ') ? args.token.slice(7) : args.token;
|
||||
|
||||
await page.evaluate((key, value) => {
|
||||
localStorage.setItem(key, value);
|
||||
}, tokenKey, token);
|
||||
|
||||
result.injected.push({
|
||||
type: 'token',
|
||||
key: tokenKey,
|
||||
storage: 'localStorage'
|
||||
});
|
||||
|
||||
// Also set Authorization header for future requests if header option provided
|
||||
if (args.header) {
|
||||
await page.setExtraHTTPHeaders({
|
||||
[args.header]: args.token.startsWith('Bearer ') ? args.token : `Bearer ${args.token}`
|
||||
});
|
||||
result.injected.push({
|
||||
type: 'header',
|
||||
name: args.header
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Inject localStorage items
|
||||
if (args['local-storage']) {
|
||||
const storageData = parseStorage(args['local-storage']);
|
||||
|
||||
await page.evaluate((data) => {
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
localStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value));
|
||||
});
|
||||
}, storageData);
|
||||
|
||||
result.injected.push({
|
||||
type: 'localStorage',
|
||||
keys: Object.keys(storageData)
|
||||
});
|
||||
}
|
||||
|
||||
// Inject sessionStorage items
|
||||
if (args['session-storage']) {
|
||||
const storageData = parseStorage(args['session-storage']);
|
||||
|
||||
await page.evaluate((data) => {
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
sessionStorage.setItem(key, typeof value === 'string' ? value : JSON.stringify(value));
|
||||
});
|
||||
}, storageData);
|
||||
|
||||
result.injected.push({
|
||||
type: 'sessionStorage',
|
||||
keys: Object.keys(storageData)
|
||||
});
|
||||
}
|
||||
|
||||
// Reload page to apply auth (optional, use --reload true)
|
||||
if (args.reload === 'true') {
|
||||
await page.reload({ waitUntil: 'networkidle2' });
|
||||
result.reloaded = true;
|
||||
}
|
||||
|
||||
// Save auth session to file for persistence across script executions
|
||||
const authSessionData = {};
|
||||
|
||||
if (args.cookies) {
|
||||
authSessionData.cookies = parseCookies(args.cookies);
|
||||
}
|
||||
if (args['local-storage']) {
|
||||
authSessionData.localStorage = parseStorage(args['local-storage']);
|
||||
}
|
||||
if (args['session-storage']) {
|
||||
authSessionData.sessionStorage = parseStorage(args['session-storage']);
|
||||
}
|
||||
if (args.token && args.header) {
|
||||
authSessionData.headers = {
|
||||
[args.header]: args.token.startsWith('Bearer ') ? args.token : `Bearer ${args.token}`
|
||||
};
|
||||
}
|
||||
|
||||
// Clear existing auth if --clear flag used
|
||||
if (args.clear === 'true') {
|
||||
clearAuthSession();
|
||||
result.cleared = true;
|
||||
} else if (Object.keys(authSessionData).length > 0) {
|
||||
saveAuthSession(authSessionData);
|
||||
result.persisted = true;
|
||||
}
|
||||
|
||||
// Verify auth by checking page title and URL after injection
|
||||
result.finalUrl = page.url();
|
||||
result.title = await page.title();
|
||||
|
||||
outputJSON(result);
|
||||
|
||||
// Default: disconnect to keep browser running for session persistence
|
||||
if (args.close === 'true') {
|
||||
await closeBrowser();
|
||||
} else {
|
||||
await disconnectBrowser();
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
outputError(error);
|
||||
}
|
||||
}
|
||||
|
||||
injectAuth();
|
||||
Reference in New Issue
Block a user