Samuel commited on
Commit ·
15f7aec
1
Parent(s): cbbacff
update
Browse files- .replit +39 -0
- Dockerfile +26 -0
- bun.lock +0 -0
- data/fakePage.html +30 -0
- endpoints/getSource.js +60 -0
- endpoints/proxyRequest.js +146 -0
- endpoints/solveTurnstile.max.js +80 -0
- endpoints/solveTurnstile.min.js +80 -0
- endpoints/wafSession.js +80 -0
- index.js +85 -0
- module/createBrowser.js +35 -0
- module/openapi.js +297 -0
- module/reqValidate.js +87 -0
- package-lock.json +0 -0
- package.json +42 -0
- replit.md +145 -0
- test-autz-gmail.js +122 -0
- test-autz-signup.js +292 -0
- test-tempmail.js +163 -0
.replit
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
modules = ["nodejs-20"]
|
| 2 |
+
[nix]
|
| 3 |
+
channel = "stable-25_05"
|
| 4 |
+
packages = ["unzip", "chromium", "xvfb-run", "xorg.libX11", "xorg.libXcomposite", "xorg.libXcursor", "xorg.libXdamage", "xorg.libXext", "xorg.libXi", "xorg.libXrandr", "xorg.libXtst", "xorg.libxcb", "nss", "nspr", "alsa-lib", "at-spi2-atk", "cups", "dbus", "libdrm", "mesa", "pango", "jq"]
|
| 5 |
+
|
| 6 |
+
[agent]
|
| 7 |
+
expertMode = true
|
| 8 |
+
|
| 9 |
+
[workflows]
|
| 10 |
+
runButton = "Project"
|
| 11 |
+
|
| 12 |
+
[[workflows.workflow]]
|
| 13 |
+
name = "Project"
|
| 14 |
+
mode = "parallel"
|
| 15 |
+
author = "agent"
|
| 16 |
+
|
| 17 |
+
[[workflows.workflow.tasks]]
|
| 18 |
+
task = "workflow.run"
|
| 19 |
+
args = "CF Bypass Server"
|
| 20 |
+
|
| 21 |
+
[[workflows.workflow]]
|
| 22 |
+
name = "CF Bypass Server"
|
| 23 |
+
author = "agent"
|
| 24 |
+
|
| 25 |
+
[workflows.workflow.metadata]
|
| 26 |
+
outputType = "console"
|
| 27 |
+
|
| 28 |
+
[[workflows.workflow.tasks]]
|
| 29 |
+
task = "shell.exec"
|
| 30 |
+
args = "xvfb-run --server-args=\"-screen 0 1280x720x24\" bash -c 'PORT=3000 node index.js'"
|
| 31 |
+
waitForPort = 3000
|
| 32 |
+
|
| 33 |
+
[[ports]]
|
| 34 |
+
localPort = 3000
|
| 35 |
+
externalPort = 3000
|
| 36 |
+
|
| 37 |
+
[[ports]]
|
| 38 |
+
localPort = 45359
|
| 39 |
+
externalPort = 80
|
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:latest
|
| 2 |
+
|
| 3 |
+
RUN apt-get update && apt-get install -y \
|
| 4 |
+
wget \
|
| 5 |
+
gnupg \
|
| 6 |
+
ca-certificates \
|
| 7 |
+
apt-transport-https \
|
| 8 |
+
chromium \
|
| 9 |
+
chromium-driver \
|
| 10 |
+
xvfb \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
ENV CHROME_BIN=/usr/bin/chromium
|
| 14 |
+
|
| 15 |
+
WORKDIR /app
|
| 16 |
+
|
| 17 |
+
COPY package*.json ./
|
| 18 |
+
|
| 19 |
+
RUN npm update
|
| 20 |
+
RUN npm install
|
| 21 |
+
RUN npm i -g pm2
|
| 22 |
+
COPY . .
|
| 23 |
+
|
| 24 |
+
EXPOSE 3000
|
| 25 |
+
|
| 26 |
+
CMD ["pm2-runtime", "index.js"]
|
bun.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/fakePage.html
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title></title>
|
| 8 |
+
</head>
|
| 9 |
+
|
| 10 |
+
<body>
|
| 11 |
+
<div class="turnstile"></div>
|
| 12 |
+
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
|
| 13 |
+
<script>
|
| 14 |
+
window.onloadTurnstileCallback = function () {
|
| 15 |
+
turnstile.render('.turnstile', {
|
| 16 |
+
sitekey: '<site-key>',
|
| 17 |
+
callback: function (token) {
|
| 18 |
+
var c = document.createElement('input');
|
| 19 |
+
c.type = 'hidden';
|
| 20 |
+
c.name = 'cf-response';
|
| 21 |
+
c.value = token;
|
| 22 |
+
document.body.appendChild(c);
|
| 23 |
+
},
|
| 24 |
+
});
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
</script>
|
| 28 |
+
</body>
|
| 29 |
+
|
| 30 |
+
</html>
|
endpoints/getSource.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function getSource({ url, proxy }) {
|
| 2 |
+
return new Promise(async (resolve, reject) => {
|
| 3 |
+
if (!url) return reject("Missing url parameter");
|
| 4 |
+
const context = await global.browser
|
| 5 |
+
.createBrowserContext({
|
| 6 |
+
proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
|
| 7 |
+
})
|
| 8 |
+
.catch(() => null);
|
| 9 |
+
if (!context) return reject("Failed to create browser context");
|
| 10 |
+
|
| 11 |
+
let isResolved = false;
|
| 12 |
+
|
| 13 |
+
var cl = setTimeout(async () => {
|
| 14 |
+
if (!isResolved) {
|
| 15 |
+
await context.close();
|
| 16 |
+
reject("Timeout Error");
|
| 17 |
+
}
|
| 18 |
+
}, global.timeOut || 60000);
|
| 19 |
+
|
| 20 |
+
try {
|
| 21 |
+
const page = await context.newPage();
|
| 22 |
+
|
| 23 |
+
if (proxy?.username && proxy?.password)
|
| 24 |
+
await page.authenticate({
|
| 25 |
+
username: proxy.username,
|
| 26 |
+
password: proxy.password,
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
await page.setRequestInterception(true);
|
| 30 |
+
page.on("request", async (request) => request.continue());
|
| 31 |
+
page.on("response", async (res) => {
|
| 32 |
+
try {
|
| 33 |
+
if (
|
| 34 |
+
[200, 302].includes(res.status()) &&
|
| 35 |
+
[url, url + "/"].includes(res.url())
|
| 36 |
+
) {
|
| 37 |
+
await page
|
| 38 |
+
.waitForNavigation({ waitUntil: "load", timeout: 5000 })
|
| 39 |
+
.catch(() => {});
|
| 40 |
+
const html = await page.content();
|
| 41 |
+
await context.close();
|
| 42 |
+
isResolved = true;
|
| 43 |
+
clearInterval(cl);
|
| 44 |
+
resolve(html);
|
| 45 |
+
}
|
| 46 |
+
} catch (e) {}
|
| 47 |
+
});
|
| 48 |
+
await page.goto(url, {
|
| 49 |
+
waitUntil: "domcontentloaded",
|
| 50 |
+
});
|
| 51 |
+
} catch (e) {
|
| 52 |
+
if (!isResolved) {
|
| 53 |
+
await context.close();
|
| 54 |
+
clearInterval(cl);
|
| 55 |
+
reject(e.message);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
module.exports = getSource;
|
endpoints/proxyRequest.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function proxyRequest({ url, proxy, method = 'GET', body = null, headers = {}, cookies = [], sessionHeaders = {} }) {
|
| 2 |
+
return new Promise(async (resolve, reject) => {
|
| 3 |
+
if (!url) return reject("Missing url parameter");
|
| 4 |
+
|
| 5 |
+
const context = await global.browser
|
| 6 |
+
.createBrowserContext({
|
| 7 |
+
proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined,
|
| 8 |
+
})
|
| 9 |
+
.catch(() => null);
|
| 10 |
+
|
| 11 |
+
if (!context) return reject("Failed to create browser context");
|
| 12 |
+
|
| 13 |
+
let isResolved = false;
|
| 14 |
+
|
| 15 |
+
var cl = setTimeout(async () => {
|
| 16 |
+
if (!isResolved) {
|
| 17 |
+
await context.close();
|
| 18 |
+
reject("Timeout Error");
|
| 19 |
+
}
|
| 20 |
+
}, global.timeOut || 60000);
|
| 21 |
+
|
| 22 |
+
try {
|
| 23 |
+
const page = await context.newPage();
|
| 24 |
+
|
| 25 |
+
if (proxy?.username && proxy?.password)
|
| 26 |
+
await page.authenticate({
|
| 27 |
+
username: proxy.username,
|
| 28 |
+
password: proxy.password,
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
const targetUrl = new URL(url);
|
| 32 |
+
|
| 33 |
+
if (cookies && cookies.length > 0) {
|
| 34 |
+
const cookiesToSet = cookies.map(cookie => ({
|
| 35 |
+
name: cookie.name,
|
| 36 |
+
value: cookie.value,
|
| 37 |
+
domain: cookie.domain || targetUrl.hostname,
|
| 38 |
+
path: cookie.path || '/',
|
| 39 |
+
secure: cookie.secure !== undefined ? cookie.secure : targetUrl.protocol === 'https:',
|
| 40 |
+
httpOnly: cookie.httpOnly || false,
|
| 41 |
+
sameSite: cookie.sameSite || 'Lax'
|
| 42 |
+
}));
|
| 43 |
+
await page.setCookie(...cookiesToSet);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
if (sessionHeaders && sessionHeaders['user-agent']) {
|
| 47 |
+
await page.setUserAgent(sessionHeaders['user-agent']);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
const sanitizedHeaders = { ...headers };
|
| 51 |
+
const headersToRemove = ['host', 'content-length', 'connection', 'accept-encoding', 'transfer-encoding'];
|
| 52 |
+
headersToRemove.forEach(h => {
|
| 53 |
+
delete sanitizedHeaders[h];
|
| 54 |
+
delete sanitizedHeaders[h.toLowerCase()];
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
if (sessionHeaders) {
|
| 58 |
+
if (sessionHeaders['accept-language'] && !sanitizedHeaders['accept-language']) {
|
| 59 |
+
sanitizedHeaders['accept-language'] = sessionHeaders['accept-language'];
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
await page.goto(targetUrl.origin, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(() => {});
|
| 64 |
+
|
| 65 |
+
const result = await page.evaluate(async (options) => {
|
| 66 |
+
try {
|
| 67 |
+
const fetchOptions = {
|
| 68 |
+
method: options.method,
|
| 69 |
+
headers: options.headers || {},
|
| 70 |
+
credentials: 'include'
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
if (options.body && ['POST', 'PUT', 'PATCH'].includes(options.method.toUpperCase())) {
|
| 74 |
+
fetchOptions.body = typeof options.body === 'string'
|
| 75 |
+
? options.body
|
| 76 |
+
: JSON.stringify(options.body);
|
| 77 |
+
if (typeof options.body === 'object' && !fetchOptions.headers['content-type']) {
|
| 78 |
+
fetchOptions.headers['content-type'] = 'application/json';
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
const response = await fetch(options.url, fetchOptions);
|
| 83 |
+
|
| 84 |
+
const responseHeaders = {};
|
| 85 |
+
response.headers.forEach((value, key) => {
|
| 86 |
+
responseHeaders[key] = value;
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
let responseBody;
|
| 90 |
+
const contentType = response.headers.get('content-type') || '';
|
| 91 |
+
|
| 92 |
+
if (contentType.includes('application/json')) {
|
| 93 |
+
responseBody = await response.json();
|
| 94 |
+
} else {
|
| 95 |
+
responseBody = await response.text();
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
return {
|
| 99 |
+
status: response.status,
|
| 100 |
+
statusText: response.statusText,
|
| 101 |
+
headers: responseHeaders,
|
| 102 |
+
body: responseBody,
|
| 103 |
+
ok: response.ok
|
| 104 |
+
};
|
| 105 |
+
} catch (e) {
|
| 106 |
+
return { error: e.message };
|
| 107 |
+
}
|
| 108 |
+
}, { url, method, body, headers: sanitizedHeaders });
|
| 109 |
+
|
| 110 |
+
if (result.error) {
|
| 111 |
+
await context.close();
|
| 112 |
+
isResolved = true;
|
| 113 |
+
clearTimeout(cl);
|
| 114 |
+
return reject(result.error);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
const updatedCookies = await page.cookies();
|
| 118 |
+
|
| 119 |
+
const browserHeaders = await page.evaluate(() => {
|
| 120 |
+
return {
|
| 121 |
+
'user-agent': navigator.userAgent,
|
| 122 |
+
'accept-language': navigator.language || navigator.languages.join(',')
|
| 123 |
+
};
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
await context.close();
|
| 127 |
+
isResolved = true;
|
| 128 |
+
clearInterval(cl);
|
| 129 |
+
|
| 130 |
+
resolve({
|
| 131 |
+
response: result,
|
| 132 |
+
cookies: updatedCookies,
|
| 133 |
+
browserHeaders
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
} catch (e) {
|
| 137 |
+
if (!isResolved) {
|
| 138 |
+
await context.close();
|
| 139 |
+
clearTimeout(cl);
|
| 140 |
+
reject(e.message);
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
module.exports = proxyRequest;
|
endpoints/solveTurnstile.max.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const fs = require("fs");
|
| 2 |
+
function solveTurnstileMin({ url, proxy }) {
|
| 3 |
+
return new Promise(async (resolve, reject) => {
|
| 4 |
+
if (!url) return reject("Missing url parameter");
|
| 5 |
+
|
| 6 |
+
const context = await global.browser
|
| 7 |
+
.createBrowserContext({
|
| 8 |
+
proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
|
| 9 |
+
})
|
| 10 |
+
.catch(() => null);
|
| 11 |
+
|
| 12 |
+
if (!context) return reject("Failed to create browser context");
|
| 13 |
+
|
| 14 |
+
let isResolved = false;
|
| 15 |
+
|
| 16 |
+
var cl = setTimeout(async () => {
|
| 17 |
+
if (!isResolved) {
|
| 18 |
+
await context.close();
|
| 19 |
+
reject("Timeout Error");
|
| 20 |
+
}
|
| 21 |
+
}, global.timeOut || 60000);
|
| 22 |
+
|
| 23 |
+
try {
|
| 24 |
+
const page = await context.newPage();
|
| 25 |
+
|
| 26 |
+
if (proxy?.username && proxy?.password)
|
| 27 |
+
await page.authenticate({
|
| 28 |
+
username: proxy.username,
|
| 29 |
+
password: proxy.password,
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
await page.evaluateOnNewDocument(() => {
|
| 33 |
+
let token = null;
|
| 34 |
+
async function waitForToken() {
|
| 35 |
+
while (!token) {
|
| 36 |
+
try {
|
| 37 |
+
token = window.turnstile.getResponse();
|
| 38 |
+
} catch (e) {}
|
| 39 |
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
| 40 |
+
}
|
| 41 |
+
var c = document.createElement("input");
|
| 42 |
+
c.type = "hidden";
|
| 43 |
+
c.name = "cf-response";
|
| 44 |
+
c.value = token;
|
| 45 |
+
document.body.appendChild(c);
|
| 46 |
+
}
|
| 47 |
+
waitForToken();
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
await page.goto(url, {
|
| 51 |
+
waitUntil: "domcontentloaded",
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
await page.waitForSelector('[name="cf-response"]', {
|
| 55 |
+
timeout: 60000,
|
| 56 |
+
});
|
| 57 |
+
const token = await page.evaluate(() => {
|
| 58 |
+
try {
|
| 59 |
+
return document.querySelector('[name="cf-response"]').value;
|
| 60 |
+
} catch (e) {
|
| 61 |
+
return null;
|
| 62 |
+
}
|
| 63 |
+
});
|
| 64 |
+
isResolved = true;
|
| 65 |
+
clearInterval(cl);
|
| 66 |
+
await context.close();
|
| 67 |
+
if (!token || token.length < 10) return reject("Failed to get token");
|
| 68 |
+
return resolve(token);
|
| 69 |
+
} catch (e) {
|
| 70 |
+
console.log(e);
|
| 71 |
+
|
| 72 |
+
if (!isResolved) {
|
| 73 |
+
await context.close();
|
| 74 |
+
clearInterval(cl);
|
| 75 |
+
reject(e.message);
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
module.exports = solveTurnstileMin;
|
endpoints/solveTurnstile.min.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function solveTurnstileMin({ url, proxy, siteKey }) {
|
| 2 |
+
return new Promise(async (resolve, reject) => {
|
| 3 |
+
if (!url) return reject("Missing url parameter");
|
| 4 |
+
if (!siteKey) return reject("Missing siteKey parameter");
|
| 5 |
+
|
| 6 |
+
const context = await global.browser
|
| 7 |
+
.createBrowserContext({
|
| 8 |
+
proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
|
| 9 |
+
})
|
| 10 |
+
.catch(() => null);
|
| 11 |
+
if (!context) return reject("Failed to create browser context");
|
| 12 |
+
|
| 13 |
+
let isResolved = false;
|
| 14 |
+
|
| 15 |
+
var cl = setTimeout(async () => {
|
| 16 |
+
if (!isResolved) {
|
| 17 |
+
await context.close();
|
| 18 |
+
reject("Timeout Error");
|
| 19 |
+
}
|
| 20 |
+
}, global.timeOut || 60000);
|
| 21 |
+
|
| 22 |
+
try {
|
| 23 |
+
const page = await context.newPage();
|
| 24 |
+
|
| 25 |
+
if (proxy?.username && proxy?.password)
|
| 26 |
+
await page.authenticate({
|
| 27 |
+
username: proxy.username,
|
| 28 |
+
password: proxy.password,
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
await page.setRequestInterception(true);
|
| 32 |
+
|
| 33 |
+
page.on("request", async (request) => {
|
| 34 |
+
if (
|
| 35 |
+
[url, url + "/"].includes(request.url()) &&
|
| 36 |
+
request.resourceType() === "document"
|
| 37 |
+
) {
|
| 38 |
+
const response = await request.respond({
|
| 39 |
+
status: 200,
|
| 40 |
+
contentType: "text/html",
|
| 41 |
+
body: String(
|
| 42 |
+
require("fs").readFileSync("./data/fakePage.html")
|
| 43 |
+
).replace(/<site-key>/g, siteKey),
|
| 44 |
+
});
|
| 45 |
+
} else {
|
| 46 |
+
await request.continue();
|
| 47 |
+
}
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
await page.goto(url, {
|
| 51 |
+
waitUntil: "domcontentloaded",
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
await page.waitForSelector('[name="cf-response"]', {
|
| 55 |
+
timeout: 60000,
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
const token = await page.evaluate(() => {
|
| 59 |
+
try {
|
| 60 |
+
return document.querySelector('[name="cf-response"]').value;
|
| 61 |
+
} catch (e) {
|
| 62 |
+
return null;
|
| 63 |
+
}
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
+
isResolved = true;
|
| 67 |
+
clearInterval(cl);
|
| 68 |
+
await context.close();
|
| 69 |
+
if (!token || token.length < 10) return reject("Failed to get token");
|
| 70 |
+
return resolve(token);
|
| 71 |
+
} catch (e) {
|
| 72 |
+
if (!isResolved) {
|
| 73 |
+
await context.close();
|
| 74 |
+
clearInterval(cl);
|
| 75 |
+
reject(e.message);
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
module.exports = solveTurnstileMin;
|
endpoints/wafSession.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
async function findAcceptLanguage(page) {
|
| 2 |
+
return await page.evaluate(async () => {
|
| 3 |
+
const result = await fetch("https://httpbin.org/get")
|
| 4 |
+
.then((res) => res.json())
|
| 5 |
+
.then(
|
| 6 |
+
(res) =>
|
| 7 |
+
res.headers["Accept-Language"] || res.headers["accept-language"]
|
| 8 |
+
)
|
| 9 |
+
.catch(() => null);
|
| 10 |
+
return result;
|
| 11 |
+
});
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function getSource({ url, proxy }) {
|
| 15 |
+
return new Promise(async (resolve, reject) => {
|
| 16 |
+
if (!url) return reject("Missing url parameter");
|
| 17 |
+
const context = await global.browser
|
| 18 |
+
.createBrowserContext({
|
| 19 |
+
proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined, // https://pptr.dev/api/puppeteer.browsercontextoptions
|
| 20 |
+
})
|
| 21 |
+
.catch(() => null);
|
| 22 |
+
if (!context) return reject("Failed to create browser context");
|
| 23 |
+
|
| 24 |
+
let isResolved = false;
|
| 25 |
+
|
| 26 |
+
var cl = setTimeout(async () => {
|
| 27 |
+
if (!isResolved) {
|
| 28 |
+
await context.close();
|
| 29 |
+
reject("Timeout Error");
|
| 30 |
+
}
|
| 31 |
+
}, global.timeOut || 60000);
|
| 32 |
+
|
| 33 |
+
try {
|
| 34 |
+
const page = await context.newPage();
|
| 35 |
+
|
| 36 |
+
if (proxy?.username && proxy?.password)
|
| 37 |
+
await page.authenticate({
|
| 38 |
+
username: proxy.username,
|
| 39 |
+
password: proxy.password,
|
| 40 |
+
});
|
| 41 |
+
let acceptLanguage = await findAcceptLanguage(page);
|
| 42 |
+
await page.setRequestInterception(true);
|
| 43 |
+
page.on("request", async (request) => request.continue());
|
| 44 |
+
page.on("response", async (res) => {
|
| 45 |
+
try {
|
| 46 |
+
if (
|
| 47 |
+
[200, 302].includes(res.status()) &&
|
| 48 |
+
[url, url + "/"].includes(res.url())
|
| 49 |
+
) {
|
| 50 |
+
await page
|
| 51 |
+
.waitForNavigation({ waitUntil: "load", timeout: 5000 })
|
| 52 |
+
.catch(() => {});
|
| 53 |
+
const cookies = await page.cookies();
|
| 54 |
+
let headers = await res.request().headers();
|
| 55 |
+
delete headers["content-type"];
|
| 56 |
+
delete headers["accept-encoding"];
|
| 57 |
+
delete headers["accept"];
|
| 58 |
+
delete headers["content-length"];
|
| 59 |
+
headers["accept-language"] = acceptLanguage;
|
| 60 |
+
await context.close();
|
| 61 |
+
isResolved = true;
|
| 62 |
+
clearInterval(cl);
|
| 63 |
+
resolve({ cookies, headers });
|
| 64 |
+
}
|
| 65 |
+
} catch (e) {}
|
| 66 |
+
});
|
| 67 |
+
|
| 68 |
+
await page.goto(url, {
|
| 69 |
+
waitUntil: "domcontentloaded",
|
| 70 |
+
});
|
| 71 |
+
} catch (e) {
|
| 72 |
+
if (!isResolved) {
|
| 73 |
+
await context.close();
|
| 74 |
+
clearInterval(cl);
|
| 75 |
+
reject(e.message);
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
});
|
| 79 |
+
}
|
| 80 |
+
module.exports = getSource;
|
index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const express = require('express')
|
| 2 |
+
const app = express()
|
| 3 |
+
const port = process.env.PORT || 3939
|
| 4 |
+
const bodyParser = require('body-parser')
|
| 5 |
+
const authToken = process.env.authToken || null
|
| 6 |
+
const cors = require('cors')
|
| 7 |
+
const reqValidate = require('./module/reqValidate')
|
| 8 |
+
const swaggerUi = require('swagger-ui-express')
|
| 9 |
+
const openApiSpec = require('./module/openapi')
|
| 10 |
+
|
| 11 |
+
global.browserLength = 0
|
| 12 |
+
global.browserLimit = Number(process.env.browserLimit) || 20
|
| 13 |
+
global.timeOut = Number(process.env.timeOut || 60000)
|
| 14 |
+
|
| 15 |
+
app.set('json spaces', 2);
|
| 16 |
+
app.use(bodyParser.json({}))
|
| 17 |
+
app.use(bodyParser.urlencoded({ extended: true }))
|
| 18 |
+
app.use(cors())
|
| 19 |
+
if (process.env.NODE_ENV !== 'development') {
|
| 20 |
+
let server = app.listen(port, () => { console.log(`Server running on port ${port}`) })
|
| 21 |
+
try {
|
| 22 |
+
server.timeout = global.timeOut
|
| 23 |
+
} catch (e) { }
|
| 24 |
+
}
|
| 25 |
+
if (process.env.SKIP_LAUNCH != 'true') require('./module/createBrowser')
|
| 26 |
+
|
| 27 |
+
const getSource = require('./endpoints/getSource')
|
| 28 |
+
const solveTurnstileMin = require('./endpoints/solveTurnstile.min')
|
| 29 |
+
const solveTurnstileMax = require('./endpoints/solveTurnstile.max')
|
| 30 |
+
const wafSession = require('./endpoints/wafSession')
|
| 31 |
+
const proxyRequest = require('./endpoints/proxyRequest')
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
async function handleSolverRequest(req, res) {
|
| 35 |
+
const data = req.method === 'POST' ? { ...req.query, ...req.body } : req.query;
|
| 36 |
+
|
| 37 |
+
const check = reqValidate(data)
|
| 38 |
+
|
| 39 |
+
if (check !== true) return res.status(400).json({ code: 400, message: 'Bad Request', schema: check })
|
| 40 |
+
|
| 41 |
+
if (authToken && data.authToken !== authToken) return res.status(401).json({ code: 401, message: 'Unauthorized' })
|
| 42 |
+
|
| 43 |
+
if (global.browserLength >= global.browserLimit) return res.status(429).json({ code: 429, message: 'Too Many Requests' })
|
| 44 |
+
|
| 45 |
+
if (process.env.SKIP_LAUNCH != 'true' && !global.browser) return res.status(500).json({ code: 500, message: 'The scanner is not ready yet. Please try again a little later.' })
|
| 46 |
+
|
| 47 |
+
var result = { code: 500 }
|
| 48 |
+
|
| 49 |
+
global.browserLength++
|
| 50 |
+
|
| 51 |
+
switch (data.mode) {
|
| 52 |
+
case "source":
|
| 53 |
+
result = await getSource(data).then(res => { return { source: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
|
| 54 |
+
break;
|
| 55 |
+
case "turnstile-min":
|
| 56 |
+
result = await solveTurnstileMin(data).then(res => { return { token: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
|
| 57 |
+
break;
|
| 58 |
+
case "turnstile-max":
|
| 59 |
+
result = await solveTurnstileMax(data).then(res => { return { token: res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
|
| 60 |
+
break;
|
| 61 |
+
case "waf-session":
|
| 62 |
+
result = await wafSession(data).then(res => { return { ...res, code: 200 } }).catch(err => { return { code: 500, message: err.message } })
|
| 63 |
+
break;
|
| 64 |
+
case "proxy-request":
|
| 65 |
+
result = await proxyRequest(data).then(res => { return { ...res, code: 200 } }).catch(err => { return { code: 500, message: err.message || err } })
|
| 66 |
+
break;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
global.browserLength--
|
| 70 |
+
|
| 71 |
+
res.status(result.code ?? 500).send(result)
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
app.get('/solver', handleSolverRequest);
|
| 75 |
+
app.post('/solver', handleSolverRequest);
|
| 76 |
+
|
| 77 |
+
app.get('/api-docs.json', (req, res) => {
|
| 78 |
+
res.json(openApiSpec);
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
| 82 |
+
|
| 83 |
+
app.use((req, res) => { res.status(404).json({ code: 404, message: 'Not Found' }) })
|
| 84 |
+
|
| 85 |
+
if (process.env.NODE_ENV == 'development') module.exports = app
|
module/createBrowser.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const { connect } = require("puppeteer-real-browser")
|
| 2 |
+
async function createBrowser() {
|
| 3 |
+
try {
|
| 4 |
+
if (global.finished == true) return
|
| 5 |
+
|
| 6 |
+
global.browser = null
|
| 7 |
+
|
| 8 |
+
// console.log('Launching the browser...');
|
| 9 |
+
|
| 10 |
+
const { browser } = await connect({
|
| 11 |
+
headless: false,
|
| 12 |
+
turnstile: true,
|
| 13 |
+
connectOption: { defaultViewport: null },
|
| 14 |
+
disableXvfb: false,
|
| 15 |
+
})
|
| 16 |
+
|
| 17 |
+
// console.log('Browser launched');
|
| 18 |
+
|
| 19 |
+
global.browser = browser;
|
| 20 |
+
|
| 21 |
+
browser.on('disconnected', async () => {
|
| 22 |
+
if (global.finished == true) return
|
| 23 |
+
console.log('Browser disconnected');
|
| 24 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 25 |
+
await createBrowser();
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
} catch (e) {
|
| 29 |
+
console.log(e.message);
|
| 30 |
+
if (global.finished == true) return
|
| 31 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 32 |
+
await createBrowser();
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
createBrowser()
|
module/openapi.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const openApiSpec = {
|
| 2 |
+
openapi: "3.0.3",
|
| 3 |
+
info: {
|
| 4 |
+
title: "CF Bypass Solver API",
|
| 5 |
+
description: "API for solving Cloudflare challenges including Turnstile captchas, WAF sessions, and proxy requests.",
|
| 6 |
+
version: "1.0.0"
|
| 7 |
+
},
|
| 8 |
+
servers: [
|
| 9 |
+
{
|
| 10 |
+
url: "/",
|
| 11 |
+
description: "Current server"
|
| 12 |
+
}
|
| 13 |
+
],
|
| 14 |
+
paths: {
|
| 15 |
+
"/solver": {
|
| 16 |
+
get: {
|
| 17 |
+
summary: "Solver endpoint (GET)",
|
| 18 |
+
description: "Handle solver requests via GET method. The mode parameter determines which solver to use.",
|
| 19 |
+
parameters: [
|
| 20 |
+
{ $ref: "#/components/parameters/mode" },
|
| 21 |
+
{ $ref: "#/components/parameters/url" },
|
| 22 |
+
{ $ref: "#/components/parameters/authToken" },
|
| 23 |
+
{ $ref: "#/components/parameters/siteKey" }
|
| 24 |
+
],
|
| 25 |
+
responses: {
|
| 26 |
+
200: { $ref: "#/components/responses/SuccessResponse" },
|
| 27 |
+
400: { $ref: "#/components/responses/BadRequest" },
|
| 28 |
+
401: { $ref: "#/components/responses/Unauthorized" },
|
| 29 |
+
429: { $ref: "#/components/responses/TooManyRequests" },
|
| 30 |
+
500: { $ref: "#/components/responses/ServerError" }
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
post: {
|
| 34 |
+
summary: "Solver endpoint (POST)",
|
| 35 |
+
description: "Handle solver requests via POST method. The mode parameter determines which solver to use.",
|
| 36 |
+
requestBody: {
|
| 37 |
+
required: true,
|
| 38 |
+
content: {
|
| 39 |
+
"application/json": {
|
| 40 |
+
schema: { $ref: "#/components/schemas/SolverRequest" }
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
responses: {
|
| 45 |
+
200: { $ref: "#/components/responses/SuccessResponse" },
|
| 46 |
+
400: { $ref: "#/components/responses/BadRequest" },
|
| 47 |
+
401: { $ref: "#/components/responses/Unauthorized" },
|
| 48 |
+
429: { $ref: "#/components/responses/TooManyRequests" },
|
| 49 |
+
500: { $ref: "#/components/responses/ServerError" }
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"/api-docs": {
|
| 54 |
+
get: {
|
| 55 |
+
summary: "OpenAPI Documentation UI",
|
| 56 |
+
description: "Interactive Swagger UI documentation for the API.",
|
| 57 |
+
responses: {
|
| 58 |
+
200: {
|
| 59 |
+
description: "HTML page with Swagger UI"
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"/api-docs.json": {
|
| 65 |
+
get: {
|
| 66 |
+
summary: "OpenAPI JSON Specification",
|
| 67 |
+
description: "Returns the raw OpenAPI specification in JSON format.",
|
| 68 |
+
responses: {
|
| 69 |
+
200: {
|
| 70 |
+
description: "OpenAPI specification",
|
| 71 |
+
content: {
|
| 72 |
+
"application/json": {
|
| 73 |
+
schema: {
|
| 74 |
+
type: "object"
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
components: {
|
| 84 |
+
parameters: {
|
| 85 |
+
mode: {
|
| 86 |
+
name: "mode",
|
| 87 |
+
in: "query",
|
| 88 |
+
required: true,
|
| 89 |
+
description: "The solver mode to use",
|
| 90 |
+
schema: {
|
| 91 |
+
type: "string",
|
| 92 |
+
enum: ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"]
|
| 93 |
+
}
|
| 94 |
+
},
|
| 95 |
+
url: {
|
| 96 |
+
name: "url",
|
| 97 |
+
in: "query",
|
| 98 |
+
required: true,
|
| 99 |
+
description: "Target URL to process",
|
| 100 |
+
schema: {
|
| 101 |
+
type: "string",
|
| 102 |
+
format: "uri"
|
| 103 |
+
}
|
| 104 |
+
},
|
| 105 |
+
authToken: {
|
| 106 |
+
name: "authToken",
|
| 107 |
+
in: "query",
|
| 108 |
+
required: false,
|
| 109 |
+
description: "Authentication token (required if server has authToken configured)",
|
| 110 |
+
schema: {
|
| 111 |
+
type: "string"
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
siteKey: {
|
| 115 |
+
name: "siteKey",
|
| 116 |
+
in: "query",
|
| 117 |
+
required: false,
|
| 118 |
+
description: "Turnstile site key (required for turnstile modes)",
|
| 119 |
+
schema: {
|
| 120 |
+
type: "string"
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
schemas: {
|
| 125 |
+
SolverRequest: {
|
| 126 |
+
type: "object",
|
| 127 |
+
required: ["mode", "url"],
|
| 128 |
+
properties: {
|
| 129 |
+
mode: {
|
| 130 |
+
type: "string",
|
| 131 |
+
enum: ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"],
|
| 132 |
+
description: "The solver mode to use"
|
| 133 |
+
},
|
| 134 |
+
url: {
|
| 135 |
+
type: "string",
|
| 136 |
+
format: "uri",
|
| 137 |
+
description: "Target URL to process"
|
| 138 |
+
},
|
| 139 |
+
authToken: {
|
| 140 |
+
type: "string",
|
| 141 |
+
description: "Authentication token (required if server has authToken configured)"
|
| 142 |
+
},
|
| 143 |
+
siteKey: {
|
| 144 |
+
type: "string",
|
| 145 |
+
description: "Turnstile site key (required for turnstile modes)"
|
| 146 |
+
},
|
| 147 |
+
proxy: {
|
| 148 |
+
type: "object",
|
| 149 |
+
description: "Proxy configuration",
|
| 150 |
+
properties: {
|
| 151 |
+
host: { type: "string", description: "Proxy host" },
|
| 152 |
+
port: { type: "integer", description: "Proxy port" },
|
| 153 |
+
username: { type: "string", description: "Proxy username" },
|
| 154 |
+
password: { type: "string", description: "Proxy password" }
|
| 155 |
+
}
|
| 156 |
+
},
|
| 157 |
+
method: {
|
| 158 |
+
type: "string",
|
| 159 |
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
|
| 160 |
+
description: "HTTP method for proxy-request mode"
|
| 161 |
+
},
|
| 162 |
+
body: {
|
| 163 |
+
oneOf: [
|
| 164 |
+
{ type: "string" },
|
| 165 |
+
{ type: "object" }
|
| 166 |
+
],
|
| 167 |
+
description: "Request body for proxy-request mode"
|
| 168 |
+
},
|
| 169 |
+
headers: {
|
| 170 |
+
type: "object",
|
| 171 |
+
description: "Custom headers for the request"
|
| 172 |
+
},
|
| 173 |
+
cookies: {
|
| 174 |
+
type: "array",
|
| 175 |
+
description: "Cookies to set for the request",
|
| 176 |
+
items: {
|
| 177 |
+
type: "object",
|
| 178 |
+
required: ["name", "value"],
|
| 179 |
+
properties: {
|
| 180 |
+
name: { type: "string" },
|
| 181 |
+
value: { type: "string" },
|
| 182 |
+
domain: { type: "string" },
|
| 183 |
+
path: { type: "string" },
|
| 184 |
+
secure: { type: "boolean" },
|
| 185 |
+
httpOnly: { type: "boolean" },
|
| 186 |
+
sameSite: { type: "string" }
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
sessionHeaders: {
|
| 191 |
+
type: "object",
|
| 192 |
+
description: "Session headers for waf-session mode"
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
},
|
| 196 |
+
SourceResponse: {
|
| 197 |
+
type: "object",
|
| 198 |
+
properties: {
|
| 199 |
+
code: { type: "integer", example: 200 },
|
| 200 |
+
source: { type: "string", description: "Page source HTML" }
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
TurnstileResponse: {
|
| 204 |
+
type: "object",
|
| 205 |
+
properties: {
|
| 206 |
+
code: { type: "integer", example: 200 },
|
| 207 |
+
token: { type: "string", description: "Solved Turnstile token" }
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
WafSessionResponse: {
|
| 211 |
+
type: "object",
|
| 212 |
+
properties: {
|
| 213 |
+
code: { type: "integer", example: 200 },
|
| 214 |
+
cookies: { type: "array", items: { type: "object" } },
|
| 215 |
+
headers: { type: "object" }
|
| 216 |
+
}
|
| 217 |
+
},
|
| 218 |
+
ProxyRequestResponse: {
|
| 219 |
+
type: "object",
|
| 220 |
+
properties: {
|
| 221 |
+
code: { type: "integer", example: 200 },
|
| 222 |
+
status: { type: "integer" },
|
| 223 |
+
headers: { type: "object" },
|
| 224 |
+
body: { type: "string" }
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
ErrorResponse: {
|
| 228 |
+
type: "object",
|
| 229 |
+
properties: {
|
| 230 |
+
code: { type: "integer" },
|
| 231 |
+
message: { type: "string" }
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
},
|
| 235 |
+
responses: {
|
| 236 |
+
SuccessResponse: {
|
| 237 |
+
description: "Successful response. Content varies based on mode used.",
|
| 238 |
+
content: {
|
| 239 |
+
"application/json": {
|
| 240 |
+
schema: {
|
| 241 |
+
oneOf: [
|
| 242 |
+
{ $ref: "#/components/schemas/SourceResponse" },
|
| 243 |
+
{ $ref: "#/components/schemas/TurnstileResponse" },
|
| 244 |
+
{ $ref: "#/components/schemas/WafSessionResponse" },
|
| 245 |
+
{ $ref: "#/components/schemas/ProxyRequestResponse" }
|
| 246 |
+
]
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
},
|
| 251 |
+
BadRequest: {
|
| 252 |
+
description: "Invalid request parameters",
|
| 253 |
+
content: {
|
| 254 |
+
"application/json": {
|
| 255 |
+
schema: {
|
| 256 |
+
type: "object",
|
| 257 |
+
properties: {
|
| 258 |
+
code: { type: "integer", example: 400 },
|
| 259 |
+
message: { type: "string", example: "Bad Request" },
|
| 260 |
+
schema: { type: "array", items: { type: "object" } }
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
},
|
| 266 |
+
Unauthorized: {
|
| 267 |
+
description: "Invalid or missing authentication token",
|
| 268 |
+
content: {
|
| 269 |
+
"application/json": {
|
| 270 |
+
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
| 271 |
+
example: { code: 401, message: "Unauthorized" }
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
},
|
| 275 |
+
TooManyRequests: {
|
| 276 |
+
description: "Browser limit reached",
|
| 277 |
+
content: {
|
| 278 |
+
"application/json": {
|
| 279 |
+
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
| 280 |
+
example: { code: 429, message: "Too Many Requests" }
|
| 281 |
+
}
|
| 282 |
+
}
|
| 283 |
+
},
|
| 284 |
+
ServerError: {
|
| 285 |
+
description: "Internal server error",
|
| 286 |
+
content: {
|
| 287 |
+
"application/json": {
|
| 288 |
+
schema: { $ref: "#/components/schemas/ErrorResponse" },
|
| 289 |
+
example: { code: 500, message: "Error message" }
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
};
|
| 296 |
+
|
| 297 |
+
module.exports = openApiSpec;
|
module/reqValidate.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const Ajv = require("ajv")
|
| 2 |
+
const addFormats = require("ajv-formats")
|
| 3 |
+
|
| 4 |
+
const ajv = new Ajv()
|
| 5 |
+
addFormats(ajv)
|
| 6 |
+
|
| 7 |
+
const schema = {
|
| 8 |
+
"type": "object",
|
| 9 |
+
"properties": {
|
| 10 |
+
"mode": {
|
| 11 |
+
"type": "string",
|
| 12 |
+
"enum": ["source", "turnstile-min", "turnstile-max", "waf-session", "proxy-request"],
|
| 13 |
+
},
|
| 14 |
+
"proxy": {
|
| 15 |
+
"type": "object",
|
| 16 |
+
"properties": {
|
| 17 |
+
"host": { "type": "string" },
|
| 18 |
+
"port": { "type": "integer" },
|
| 19 |
+
"username": { "type": "string" },
|
| 20 |
+
"password": { "type": "string" }
|
| 21 |
+
},
|
| 22 |
+
"additionalProperties": false
|
| 23 |
+
},
|
| 24 |
+
"url": {
|
| 25 |
+
"type": "string",
|
| 26 |
+
"format": "uri",
|
| 27 |
+
},
|
| 28 |
+
"authToken": {
|
| 29 |
+
"type": "string"
|
| 30 |
+
},
|
| 31 |
+
"siteKey": {
|
| 32 |
+
"type": "string"
|
| 33 |
+
},
|
| 34 |
+
"method": {
|
| 35 |
+
"type": "string",
|
| 36 |
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
|
| 37 |
+
},
|
| 38 |
+
"body": {
|
| 39 |
+
"type": ["string", "object"]
|
| 40 |
+
},
|
| 41 |
+
"headers": {
|
| 42 |
+
"type": "object"
|
| 43 |
+
},
|
| 44 |
+
"cookies": {
|
| 45 |
+
"type": "array",
|
| 46 |
+
"items": {
|
| 47 |
+
"type": "object",
|
| 48 |
+
"properties": {
|
| 49 |
+
"name": { "type": "string" },
|
| 50 |
+
"value": { "type": "string" },
|
| 51 |
+
"domain": { "type": "string" },
|
| 52 |
+
"path": { "type": "string" },
|
| 53 |
+
"secure": { "type": "boolean" },
|
| 54 |
+
"httpOnly": { "type": "boolean" },
|
| 55 |
+
"sameSite": { "type": "string" }
|
| 56 |
+
},
|
| 57 |
+
"required": ["name", "value"]
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"sessionHeaders": {
|
| 61 |
+
"type": "object"
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"required": ["mode", "url"],
|
| 65 |
+
"additionalProperties": false
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// const data = {
|
| 69 |
+
// mode: "source",
|
| 70 |
+
// url: "https://example.com",
|
| 71 |
+
// proxy: {
|
| 72 |
+
// host: "localhost",
|
| 73 |
+
// port: 8080,
|
| 74 |
+
// username: "test",
|
| 75 |
+
// password: "test"
|
| 76 |
+
// },
|
| 77 |
+
// authToken: "123456"
|
| 78 |
+
// }
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
function validate(data) {
|
| 82 |
+
const valid = ajv.validate(schema, data)
|
| 83 |
+
if (!valid) return ajv.errors
|
| 84 |
+
else return true
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
module.exports = validate
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "cf-solver-scraper",
|
| 3 |
+
"version": "2.1.3",
|
| 4 |
+
"main": "index.js",
|
| 5 |
+
"scripts": {
|
| 6 |
+
"start": "node index.js",
|
| 7 |
+
"test": "node --experimental-vm-modules ./node_modules/.bin/jest --detectOpenHandles --verbose"
|
| 8 |
+
},
|
| 9 |
+
"jest": {
|
| 10 |
+
"testMatch": [
|
| 11 |
+
"**/tests/**/*.js"
|
| 12 |
+
],
|
| 13 |
+
"verbose": true
|
| 14 |
+
},
|
| 15 |
+
"keywords": [
|
| 16 |
+
"cf-clearance",
|
| 17 |
+
"cloudflare",
|
| 18 |
+
"waf",
|
| 19 |
+
"scraper",
|
| 20 |
+
"puppeteer",
|
| 21 |
+
"xvfb",
|
| 22 |
+
"turnstile",
|
| 23 |
+
"bypass",
|
| 24 |
+
"undetected",
|
| 25 |
+
"stealth"
|
| 26 |
+
],
|
| 27 |
+
"author": "zfcsoftware",
|
| 28 |
+
"license": "ISC",
|
| 29 |
+
"description": "This package is an experimental and educational package created for Cloudflare protections.",
|
| 30 |
+
"dependencies": {
|
| 31 |
+
"ajv": "^8.17.1",
|
| 32 |
+
"ajv-formats": "^3.0.1",
|
| 33 |
+
"body-parser": "^1.20.3",
|
| 34 |
+
"cors": "^2.8.5",
|
| 35 |
+
"dotenv": "^16.4.5",
|
| 36 |
+
"express": "^4.21.0",
|
| 37 |
+
"jest": "^29.7.0",
|
| 38 |
+
"puppeteer-real-browser": "^1.4.0",
|
| 39 |
+
"supertest": "^7.0.0",
|
| 40 |
+
"swagger-ui-express": "^5.0.1"
|
| 41 |
+
}
|
| 42 |
+
}
|
replit.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Overview
|
| 2 |
+
|
| 3 |
+
This is a Cloudflare protection bypass service that provides web scraping and bot protection circumvention capabilities. The application acts as an API service that uses headless browser automation (via Puppeteer) to solve Cloudflare challenges, including Turnstile CAPTCHAs and WAF (Web Application Firewall) sessions. It exposes multiple endpoints for different bypass strategies and can operate with or without proxy support.
|
| 4 |
+
|
| 5 |
+
# User Preferences
|
| 6 |
+
|
| 7 |
+
Preferred communication style: Simple, everyday language.
|
| 8 |
+
|
| 9 |
+
# System Architecture
|
| 10 |
+
|
| 11 |
+
## Backend Architecture
|
| 12 |
+
|
| 13 |
+
**Framework**: Express.js REST API server
|
| 14 |
+
- **Problem**: Need to provide multiple browser automation endpoints as HTTP services
|
| 15 |
+
- **Solution**: Express server with JSON body parsing, CORS support, and configurable timeout handling
|
| 16 |
+
- **Rationale**: Express provides a lightweight, flexible framework for creating API endpoints with middleware support for authentication and request validation
|
| 17 |
+
|
| 18 |
+
**Request Handling Pattern**: Unified handler with mode-based routing
|
| 19 |
+
- **Problem**: Multiple similar endpoints with shared authentication and rate limiting logic
|
| 20 |
+
- **Solution**: Single `handleSolverRequest` function that routes based on `mode` parameter
|
| 21 |
+
- **Alternatives**: Separate route handlers per endpoint
|
| 22 |
+
- **Pros**: Centralized validation, authentication, and rate limiting; reduced code duplication
|
| 23 |
+
- **Cons**: Single handler must accommodate different parameter requirements per mode
|
| 24 |
+
|
| 25 |
+
**Global State Management**: Module-level globals for browser instance and rate limiting
|
| 26 |
+
- **Problem**: Need to share browser instance and track concurrent requests across all handlers
|
| 27 |
+
- **Solution**: Global variables (`global.browser`, `global.browserLength`, `global.browserLimit`)
|
| 28 |
+
- **Rationale**: Browser instance is expensive to create; rate limiting requires shared state
|
| 29 |
+
- **Cons**: Not horizontally scalable; state is lost on restart
|
| 30 |
+
|
| 31 |
+
## Browser Automation
|
| 32 |
+
|
| 33 |
+
**Browser Management**: Puppeteer with automatic reconnection
|
| 34 |
+
- **Problem**: Headless browsers can crash or disconnect unpredictably
|
| 35 |
+
- **Solution**: Auto-reconnection logic in `createBrowser.js` with exponential backoff
|
| 36 |
+
- **Technology**: `puppeteer-real-browser` package for enhanced stealth capabilities
|
| 37 |
+
- **Features**: Turnstile-ready configuration, XVFB support for headless environments, real browser fingerprinting
|
| 38 |
+
|
| 39 |
+
**Context Isolation**: Browser contexts per request
|
| 40 |
+
- **Problem**: Need to isolate each request's cookies, sessions, and proxy settings
|
| 41 |
+
- **Solution**: Create new browser context for each request with independent proxy configuration
|
| 42 |
+
- **Pros**: Complete isolation between concurrent requests; clean state per request
|
| 43 |
+
- **Cons**: Higher resource usage than tab-based isolation
|
| 44 |
+
|
| 45 |
+
**Timeout Management**: Per-request timeout with cleanup
|
| 46 |
+
- **Problem**: Browser automation can hang indefinitely on problematic sites
|
| 47 |
+
- **Solution**: Configurable timeout (`global.timeOut`) with context cleanup in all endpoints
|
| 48 |
+
- **Rationale**: Prevents resource leaks and ensures predictable response times
|
| 49 |
+
|
| 50 |
+
## API Endpoints & Modes
|
| 51 |
+
|
| 52 |
+
**Mode-Based Architecture**: Six distinct bypass strategies
|
| 53 |
+
1. **source**: Fetch page HTML after Cloudflare challenge resolution
|
| 54 |
+
2. **turnstile-min**: Solve Turnstile CAPTCHA using injected fake page with real site key
|
| 55 |
+
3. **turnstile-max**: Solve Turnstile on actual target page
|
| 56 |
+
4. **waf-session**: Create authenticated session with Cloudflare cookies and headers
|
| 57 |
+
5. **proxy-request**: Make authenticated requests using cookies/headers from waf-session (session reuse)
|
| 58 |
+
|
| 59 |
+
**Session Reuse Workflow** (proxy-request):
|
| 60 |
+
- First call `waf-session` to get cookies + headers
|
| 61 |
+
- Pass those to `proxy-request` via `cookies` and `sessionHeaders` parameters
|
| 62 |
+
- Requests are made through the same browser (Chrome fingerprint), maintaining CF clearance
|
| 63 |
+
|
| 64 |
+
**Request Validation**: JSON Schema validation with AJV
|
| 65 |
+
- **Problem**: Need to validate complex nested request structures (proxy configs, cookies, headers)
|
| 66 |
+
- **Solution**: AJV with format validation for URIs and enums for modes/HTTP methods
|
| 67 |
+
- **Rationale**: Schema-based validation provides clear error messages and type safety
|
| 68 |
+
|
| 69 |
+
## Security & Rate Limiting
|
| 70 |
+
|
| 71 |
+
**Authentication**: Optional token-based authentication
|
| 72 |
+
- **Implementation**: `authToken` environment variable compared against request parameter
|
| 73 |
+
- **Design**: Simple bearer-style token authentication
|
| 74 |
+
- **Rationale**: Lightweight protection for self-hosted deployments
|
| 75 |
+
|
| 76 |
+
**Rate Limiting**: Browser instance concurrency limits
|
| 77 |
+
- **Problem**: Too many concurrent browser contexts can exhaust system resources
|
| 78 |
+
- **Solution**: `browserLimit` (default 20) enforced via `browserLength` counter
|
| 79 |
+
- **Mechanism**: Request rejected with 429 if limit exceeded
|
| 80 |
+
- **Cons**: In-memory counter doesn't work across multiple instances
|
| 81 |
+
|
| 82 |
+
**Proxy Authentication**: Built-in proxy credential handling
|
| 83 |
+
- **Problem**: Many proxies require username/password authentication
|
| 84 |
+
- **Solution**: Puppeteer's `page.authenticate()` for proxy credentials
|
| 85 |
+
- **Support**: Configurable per-request proxy with optional authentication
|
| 86 |
+
|
| 87 |
+
## Environment Configuration
|
| 88 |
+
|
| 89 |
+
**Configuration Strategy**: Environment variables with sensible defaults
|
| 90 |
+
- `PORT`: Server port (default: 3939)
|
| 91 |
+
- `authToken`: Optional API authentication
|
| 92 |
+
- `browserLimit`: Max concurrent browser contexts (default: 20)
|
| 93 |
+
- `timeOut`: Request timeout in milliseconds (default: 60000)
|
| 94 |
+
- `SKIP_LAUNCH`: Skip browser initialization for testing
|
| 95 |
+
- `NODE_ENV`: Environment mode (development vs production)
|
| 96 |
+
|
| 97 |
+
# External Dependencies
|
| 98 |
+
|
| 99 |
+
## Core Dependencies
|
| 100 |
+
|
| 101 |
+
**puppeteer-real-browser** (v1.4.0)
|
| 102 |
+
- **Purpose**: Enhanced Puppeteer wrapper with anti-detection features
|
| 103 |
+
- **Key Features**: Turnstile challenge solving, real browser fingerprinting, XVFB support
|
| 104 |
+
- **Integration**: Global browser instance managed in `module/createBrowser.js`
|
| 105 |
+
|
| 106 |
+
**Express** (v4.21.0)
|
| 107 |
+
- **Purpose**: HTTP server framework
|
| 108 |
+
- **Integration**: Main application server with middleware stack
|
| 109 |
+
|
| 110 |
+
**body-parser** (v1.20.3)
|
| 111 |
+
- **Purpose**: Parse JSON and URL-encoded request bodies
|
| 112 |
+
- **Integration**: Middleware for POST request handling
|
| 113 |
+
|
| 114 |
+
**cors** (v2.8.5)
|
| 115 |
+
- **Purpose**: Enable Cross-Origin Resource Sharing
|
| 116 |
+
- **Integration**: Middleware to allow API access from web clients
|
| 117 |
+
|
| 118 |
+
## Validation & Schema
|
| 119 |
+
|
| 120 |
+
**ajv** (v8.17.1) + **ajv-formats** (v3.0.1)
|
| 121 |
+
- **Purpose**: JSON Schema validation with format extensions
|
| 122 |
+
- **Integration**: Request parameter validation in `module/reqValidate.js`
|
| 123 |
+
- **Schemas**: Validates URL formats, proxy configs, HTTP methods, cookie structures
|
| 124 |
+
|
| 125 |
+
## Testing
|
| 126 |
+
|
| 127 |
+
**jest** (v29.7.0)
|
| 128 |
+
- **Purpose**: Test framework
|
| 129 |
+
- **Configuration**: Tests located in `tests/` directory with verbose output
|
| 130 |
+
|
| 131 |
+
**supertest** (v7.0.0)
|
| 132 |
+
- **Purpose**: HTTP assertion library for API testing
|
| 133 |
+
- **Integration**: Testing Express endpoints
|
| 134 |
+
|
| 135 |
+
## External Services
|
| 136 |
+
|
| 137 |
+
**Cloudflare Turnstile API**
|
| 138 |
+
- **Integration**: Loaded via CDN script in fake page template
|
| 139 |
+
- **URL**: `https://challenges.cloudflare.com/turnstile/v0/api.js`
|
| 140 |
+
- **Purpose**: CAPTCHA challenge rendering and token generation
|
| 141 |
+
|
| 142 |
+
**httpbin.org**
|
| 143 |
+
- **Purpose**: Header detection service used in `wafSession.js` to extract Accept-Language header
|
| 144 |
+
- **Endpoint**: `https://httpbin.org/get`
|
| 145 |
+
- **Usage**: Validate browser fingerprinting fidelity
|
test-autz-gmail.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const { connect } = require('puppeteer-real-browser');
|
| 2 |
+
|
| 3 |
+
async function createAutzAccountWithGmail() {
|
| 4 |
+
console.log('Launching browser...');
|
| 5 |
+
|
| 6 |
+
const { browser, page } = await connect({
|
| 7 |
+
headless: 'auto',
|
| 8 |
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
| 9 |
+
turnstile: true
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
const testEmail = 'benneu40@gmail.com';
|
| 13 |
+
|
| 14 |
+
try {
|
| 15 |
+
console.log('\n=== STEP 1: Navigate to Autz.org ===');
|
| 16 |
+
await page.goto('https://autz.org/onboarding/qinw2ix?callback_url=https%3A%2F%2Fmy.zone.id%2F', {
|
| 17 |
+
waitUntil: 'networkidle2',
|
| 18 |
+
timeout: 60000
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
console.log('Page loaded, waiting for content...');
|
| 22 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 23 |
+
|
| 24 |
+
await page.screenshot({ path: 'gmail-step1.png', fullPage: true });
|
| 25 |
+
console.log('Screenshot saved: gmail-step1.png');
|
| 26 |
+
|
| 27 |
+
console.log('\n=== STEP 2: Enter Gmail address ===');
|
| 28 |
+
const emailField = await page.$('input[type="email"]');
|
| 29 |
+
if (emailField) {
|
| 30 |
+
console.log(`Entering email: ${testEmail}`);
|
| 31 |
+
await emailField.click();
|
| 32 |
+
await new Promise(resolve => setTimeout(resolve, 500));
|
| 33 |
+
await emailField.evaluate(el => el.value = '');
|
| 34 |
+
await emailField.type(testEmail, { delay: 100 });
|
| 35 |
+
await new Promise(resolve => setTimeout(resolve, 500));
|
| 36 |
+
|
| 37 |
+
const enteredValue = await emailField.evaluate(el => el.value);
|
| 38 |
+
console.log('Email field value:', enteredValue);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
console.log('\n=== STEP 3: Wait for Turnstile captcha ===');
|
| 42 |
+
console.log('Waiting for captcha to auto-solve...');
|
| 43 |
+
|
| 44 |
+
let captchaSolved = false;
|
| 45 |
+
for (let i = 0; i < 30; i++) {
|
| 46 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 47 |
+
const html = await page.content();
|
| 48 |
+
|
| 49 |
+
if (html.includes('Success') || html.includes('success')) {
|
| 50 |
+
console.log('Captcha solved!');
|
| 51 |
+
captchaSolved = true;
|
| 52 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 53 |
+
break;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if (i % 5 === 0) {
|
| 57 |
+
console.log(`Waiting for captcha... ${i + 1}/30`);
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
await page.screenshot({ path: 'gmail-step2.png', fullPage: true });
|
| 62 |
+
console.log('Screenshot saved: gmail-step2.png');
|
| 63 |
+
|
| 64 |
+
console.log('\n=== STEP 4: Click Continue button ===');
|
| 65 |
+
const allBtns = await page.$$('button');
|
| 66 |
+
for (const btn of allBtns) {
|
| 67 |
+
const text = await page.evaluate(el => el.textContent, btn);
|
| 68 |
+
if (text && text.includes('Continue') && !text.includes('Google')) {
|
| 69 |
+
console.log('Clicking Continue button...');
|
| 70 |
+
|
| 71 |
+
await btn.focus();
|
| 72 |
+
await new Promise(resolve => setTimeout(resolve, 300));
|
| 73 |
+
await btn.click();
|
| 74 |
+
console.log('Clicked via .click()');
|
| 75 |
+
|
| 76 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 77 |
+
|
| 78 |
+
await page.keyboard.press('Enter');
|
| 79 |
+
console.log('Pressed Enter key');
|
| 80 |
+
break;
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
console.log('Waiting for page to change...');
|
| 85 |
+
for (let i = 0; i < 15; i++) {
|
| 86 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 87 |
+
const text = await page.evaluate(() => document.body.innerText);
|
| 88 |
+
if (text.includes('Password') || text.includes('password') || text.includes('code') || text.includes('verify')) {
|
| 89 |
+
console.log('Page changed! New content detected.');
|
| 90 |
+
break;
|
| 91 |
+
}
|
| 92 |
+
console.log(`Waiting... ${i + 1}/15`);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
await page.screenshot({ path: 'gmail-step3.png', fullPage: true });
|
| 96 |
+
console.log('Screenshot saved: gmail-step3.png');
|
| 97 |
+
|
| 98 |
+
const pageText = await page.evaluate(() => document.body.innerText);
|
| 99 |
+
console.log('\n--- Current page content ---');
|
| 100 |
+
console.log(pageText.substring(0, 1500));
|
| 101 |
+
|
| 102 |
+
console.log('\nCurrent URL:', page.url());
|
| 103 |
+
|
| 104 |
+
if (pageText.includes('Password') && pageText.includes('Name')) {
|
| 105 |
+
console.log('\n=== SUCCESS: Registration form detected! ===');
|
| 106 |
+
console.log('The Gmail address worked - temp email domains were likely being blocked.');
|
| 107 |
+
} else if (pageText.includes('code') || pageText.includes('verify')) {
|
| 108 |
+
console.log('\n=== Verification step detected ===');
|
| 109 |
+
} else {
|
| 110 |
+
console.log('\n=== Page did not advance ===');
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
} catch (error) {
|
| 114 |
+
console.error('Error:', error.message);
|
| 115 |
+
await page.screenshot({ path: 'gmail-error.png', fullPage: true });
|
| 116 |
+
} finally {
|
| 117 |
+
await browser.close();
|
| 118 |
+
console.log('\nBrowser closed.');
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
createAutzAccountWithGmail();
|
test-autz-signup.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const { connect } = require('puppeteer-real-browser');
|
| 2 |
+
|
| 3 |
+
async function createAutzAccount() {
|
| 4 |
+
console.log('Launching browser...');
|
| 5 |
+
|
| 6 |
+
const { browser, page } = await connect({
|
| 7 |
+
headless: 'auto',
|
| 8 |
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
| 9 |
+
turnstile: true
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
let tempEmail = null;
|
| 13 |
+
let tmailorPage = null;
|
| 14 |
+
|
| 15 |
+
try {
|
| 16 |
+
console.log('\n=== STEP 1: Get Temp Email from tmailor.com ===');
|
| 17 |
+
await page.goto('https://tmailor.com', {
|
| 18 |
+
waitUntil: 'networkidle2',
|
| 19 |
+
timeout: 60000
|
| 20 |
+
});
|
| 21 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 22 |
+
|
| 23 |
+
const buttons = await page.$$('button, a');
|
| 24 |
+
for (const btn of buttons) {
|
| 25 |
+
const text = await page.evaluate(el => el.textContent, btn);
|
| 26 |
+
if (text && text.includes('New Email')) {
|
| 27 |
+
console.log('Clicking New Email button...');
|
| 28 |
+
await btn.click();
|
| 29 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 30 |
+
break;
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
const emailInput = await page.$('input[readonly]');
|
| 35 |
+
if (emailInput) {
|
| 36 |
+
tempEmail = await page.evaluate(el => el.value, emailInput);
|
| 37 |
+
console.log('Got temp email:', tempEmail);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if (!tempEmail || !tempEmail.includes('@')) {
|
| 41 |
+
throw new Error('Could not get temp email from tmailor');
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
tmailorPage = page;
|
| 45 |
+
|
| 46 |
+
console.log('\n=== STEP 2: Navigate to Autz.org ===');
|
| 47 |
+
const autzPage = await browser.newPage();
|
| 48 |
+
await autzPage.goto('https://autz.org/onboarding/qinw2ix?callback_url=https%3A%2F%2Fmy.zone.id%2F', {
|
| 49 |
+
waitUntil: 'networkidle2',
|
| 50 |
+
timeout: 60000
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
console.log('Page loaded, waiting for content...');
|
| 54 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 55 |
+
|
| 56 |
+
await autzPage.screenshot({ path: 'autz-step1.png', fullPage: true });
|
| 57 |
+
console.log('Screenshot saved: autz-step1.png');
|
| 58 |
+
|
| 59 |
+
const pageText = await autzPage.evaluate(() => document.body.innerText);
|
| 60 |
+
console.log('\n--- Page content (first 1000 chars) ---');
|
| 61 |
+
console.log(pageText.substring(0, 1000));
|
| 62 |
+
|
| 63 |
+
console.log('\n=== STEP 3: Look for email input field ===');
|
| 64 |
+
const emailSelectors = [
|
| 65 |
+
'input[type="email"]',
|
| 66 |
+
'input[name="email"]',
|
| 67 |
+
'input[placeholder*="email" i]',
|
| 68 |
+
'input[id*="email" i]',
|
| 69 |
+
'input[type="text"]'
|
| 70 |
+
];
|
| 71 |
+
|
| 72 |
+
let emailField = null;
|
| 73 |
+
for (const selector of emailSelectors) {
|
| 74 |
+
emailField = await autzPage.$(selector);
|
| 75 |
+
if (emailField) {
|
| 76 |
+
console.log(`Found email field with selector: ${selector}`);
|
| 77 |
+
break;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
if (emailField) {
|
| 82 |
+
console.log(`Entering email: ${tempEmail}`);
|
| 83 |
+
await emailField.click();
|
| 84 |
+
await emailField.type(tempEmail, { delay: 50 });
|
| 85 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 86 |
+
} else {
|
| 87 |
+
console.log('No email field found on page');
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
console.log('\n=== STEP 4: Wait for Turnstile captcha ===');
|
| 91 |
+
|
| 92 |
+
console.log('Looking for Turnstile iframe...');
|
| 93 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 94 |
+
|
| 95 |
+
const frames = autzPage.frames();
|
| 96 |
+
console.log(`Found ${frames.length} frames`);
|
| 97 |
+
for (const frame of frames) {
|
| 98 |
+
const url = frame.url();
|
| 99 |
+
console.log('Frame:', url);
|
| 100 |
+
if (url.includes('turnstile') || url.includes('challenges')) {
|
| 101 |
+
console.log('Found Turnstile frame! Waiting for it to solve...');
|
| 102 |
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
console.log('Waiting for captcha to auto-solve...');
|
| 107 |
+
let captchaSolved = false;
|
| 108 |
+
for (let i = 0; i < 30; i++) {
|
| 109 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 110 |
+
|
| 111 |
+
const html = await autzPage.content();
|
| 112 |
+
|
| 113 |
+
if (html.includes('Success') || html.includes('success') || html.includes('cf-turnstile-success')) {
|
| 114 |
+
console.log('Captcha solved!');
|
| 115 |
+
captchaSolved = true;
|
| 116 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 117 |
+
break;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
if (i % 5 === 0) {
|
| 121 |
+
console.log(`Waiting for captcha... ${i + 1}/30`);
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
if (!captchaSolved) {
|
| 126 |
+
console.log('Captcha may not be fully solved, trying anyway...');
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
await autzPage.screenshot({ path: 'autz-step2.png', fullPage: true });
|
| 130 |
+
console.log('Screenshot saved: autz-step2.png');
|
| 131 |
+
|
| 132 |
+
console.log('\n=== STEP 5: Click Continue button ===');
|
| 133 |
+
|
| 134 |
+
const continueBtn = await autzPage.$('button');
|
| 135 |
+
const allContinueBtns = await autzPage.$$('button');
|
| 136 |
+
|
| 137 |
+
for (const btn of allContinueBtns) {
|
| 138 |
+
const text = await autzPage.evaluate(el => el.textContent, btn);
|
| 139 |
+
if (text && text.includes('Continue')) {
|
| 140 |
+
console.log('Found Continue button, clicking with mouse simulation...');
|
| 141 |
+
|
| 142 |
+
const box = await btn.boundingBox();
|
| 143 |
+
if (box) {
|
| 144 |
+
await autzPage.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
| 145 |
+
console.log('Clicked at', box.x + box.width / 2, box.y + box.height / 2);
|
| 146 |
+
} else {
|
| 147 |
+
await btn.click();
|
| 148 |
+
}
|
| 149 |
+
break;
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
console.log('Waiting for page content to change...');
|
| 154 |
+
for (let i = 0; i < 10; i++) {
|
| 155 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 156 |
+
const text = await autzPage.evaluate(() => document.body.innerText);
|
| 157 |
+
if (text.includes('Password') || text.includes('password')) {
|
| 158 |
+
console.log('Registration form detected!');
|
| 159 |
+
break;
|
| 160 |
+
}
|
| 161 |
+
console.log(`Waiting... ${i + 1}/10`);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 165 |
+
await autzPage.screenshot({ path: 'autz-step3.png', fullPage: true });
|
| 166 |
+
console.log('Screenshot saved: autz-step3.png');
|
| 167 |
+
|
| 168 |
+
let pageText2 = await autzPage.evaluate(() => document.body.innerText);
|
| 169 |
+
console.log('\n--- Page after Continue (first 1000 chars) ---');
|
| 170 |
+
console.log(pageText2.substring(0, 1000));
|
| 171 |
+
|
| 172 |
+
const currentUrl = autzPage.url();
|
| 173 |
+
console.log('Current URL:', currentUrl);
|
| 174 |
+
|
| 175 |
+
if (pageText2.toLowerCase().includes('password') && pageText2.toLowerCase().includes('name')) {
|
| 176 |
+
console.log('\n=== STEP 6: Fill out registration form ===');
|
| 177 |
+
|
| 178 |
+
const passwordField = await autzPage.$('input[type="password"], input[name="password"], input[placeholder*="password" i]');
|
| 179 |
+
if (passwordField) {
|
| 180 |
+
const password = 'TestPass123!';
|
| 181 |
+
console.log('Entering password...');
|
| 182 |
+
await passwordField.click();
|
| 183 |
+
await passwordField.type(password, { delay: 30 });
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
const allInputs = await autzPage.$$('input');
|
| 187 |
+
console.log(`Found ${allInputs.length} input fields`);
|
| 188 |
+
|
| 189 |
+
for (const input of allInputs) {
|
| 190 |
+
const inputType = await autzPage.evaluate(el => el.type, input);
|
| 191 |
+
const inputName = await autzPage.evaluate(el => el.name || el.placeholder || '', input);
|
| 192 |
+
const inputValue = await autzPage.evaluate(el => el.value, input);
|
| 193 |
+
console.log(`Input: type=${inputType}, name/placeholder=${inputName}, value=${inputValue?.substring(0, 20)}`);
|
| 194 |
+
|
| 195 |
+
if (inputName.toLowerCase().includes('name') && !inputName.toLowerCase().includes('email') && !inputValue) {
|
| 196 |
+
console.log('Entering name...');
|
| 197 |
+
await input.click();
|
| 198 |
+
await input.type('Test User', { delay: 30 });
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
if ((inputType === 'tel' || inputName.toLowerCase().includes('phone')) && !inputValue) {
|
| 202 |
+
console.log('Entering phone...');
|
| 203 |
+
await input.click();
|
| 204 |
+
await input.type('+12025551234', { delay: 30 });
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
await autzPage.screenshot({ path: 'autz-step4-form.png', fullPage: true });
|
| 209 |
+
console.log('Screenshot saved: autz-step4-form.png');
|
| 210 |
+
|
| 211 |
+
console.log('Looking for Create Account button...');
|
| 212 |
+
const createBtn = await autzPage.$('button');
|
| 213 |
+
const allBtns = await autzPage.$$('button');
|
| 214 |
+
for (const btn of allBtns) {
|
| 215 |
+
const text = await autzPage.evaluate(el => el.textContent, btn);
|
| 216 |
+
if (text && (text.includes('Create Account') || text.includes('Register') || text.includes('Sign Up'))) {
|
| 217 |
+
console.log('Clicking Create Account button...');
|
| 218 |
+
await btn.click();
|
| 219 |
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
| 220 |
+
break;
|
| 221 |
+
}
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
await autzPage.screenshot({ path: 'autz-step5-after-register.png', fullPage: true });
|
| 225 |
+
console.log('Screenshot saved: autz-step5-after-register.png');
|
| 226 |
+
|
| 227 |
+
const finalText = await autzPage.evaluate(() => document.body.innerText);
|
| 228 |
+
console.log('\n--- Page after registration (first 1000 chars) ---');
|
| 229 |
+
console.log(finalText.substring(0, 1000));
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
if (pageText2.toLowerCase().includes('code') || pageText2.toLowerCase().includes('verify') || pageText2.toLowerCase().includes('otp') || pageText2.toLowerCase().includes('sent') || pageText2.toLowerCase().includes('check your email')) {
|
| 233 |
+
console.log('\nVerification code requested! Need to check email...');
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
console.log('\n=== STEP 7: Check tmailor inbox for verification email ===');
|
| 237 |
+
await tmailorPage.bringToFront();
|
| 238 |
+
|
| 239 |
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
| 240 |
+
console.log(`\nChecking inbox, attempt ${attempt}/5...`);
|
| 241 |
+
|
| 242 |
+
const refreshBtns = await tmailorPage.$$('button, a');
|
| 243 |
+
for (const btn of refreshBtns) {
|
| 244 |
+
const text = await tmailorPage.evaluate(el => el.textContent, btn);
|
| 245 |
+
if (text && (text.includes('Refresh') || text.includes('refresh'))) {
|
| 246 |
+
await btn.click();
|
| 247 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 248 |
+
break;
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
const inboxText = await tmailorPage.evaluate(() => document.body.innerText);
|
| 253 |
+
if (inboxText.toLowerCase().includes('autz') ||
|
| 254 |
+
inboxText.toLowerCase().includes('verify') ||
|
| 255 |
+
inboxText.toLowerCase().includes('zone')) {
|
| 256 |
+
console.log('Found verification email!');
|
| 257 |
+
|
| 258 |
+
const emailLinks = await tmailorPage.$$('a, tr, div[class*="mail"], div[class*="message"]');
|
| 259 |
+
for (const link of emailLinks) {
|
| 260 |
+
const linkText = await tmailorPage.evaluate(el => el.textContent, link);
|
| 261 |
+
if (linkText && (linkText.toLowerCase().includes('autz') ||
|
| 262 |
+
linkText.toLowerCase().includes('verify') ||
|
| 263 |
+
linkText.toLowerCase().includes('zone'))) {
|
| 264 |
+
console.log('Clicking on email...');
|
| 265 |
+
await link.click();
|
| 266 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 267 |
+
break;
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
break;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
await tmailorPage.screenshot({ path: 'tmailor-inbox.png', fullPage: true });
|
| 277 |
+
console.log('Screenshot saved: tmailor-inbox.png');
|
| 278 |
+
|
| 279 |
+
console.log('\n=== FINAL STATUS ===');
|
| 280 |
+
console.log('Temp Email Used:', tempEmail);
|
| 281 |
+
console.log('Check the screenshots to see the current state');
|
| 282 |
+
|
| 283 |
+
} catch (error) {
|
| 284 |
+
console.error('Error:', error.message);
|
| 285 |
+
await page.screenshot({ path: 'error-screenshot.png', fullPage: true });
|
| 286 |
+
} finally {
|
| 287 |
+
await browser.close();
|
| 288 |
+
console.log('\nBrowser closed.');
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
createAutzAccount();
|
test-tempmail.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const { connect } = require('puppeteer-real-browser');
|
| 2 |
+
|
| 3 |
+
async function getTempMail() {
|
| 4 |
+
console.log('Launching browser...');
|
| 5 |
+
|
| 6 |
+
const { browser, page } = await connect({
|
| 7 |
+
headless: 'auto',
|
| 8 |
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
| 9 |
+
turnstile: true
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
try {
|
| 13 |
+
console.log('Navigating to tmailor.com...');
|
| 14 |
+
await page.goto('https://tmailor.com', {
|
| 15 |
+
waitUntil: 'networkidle2',
|
| 16 |
+
timeout: 60000
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
console.log('Initial page load complete. Waiting...');
|
| 20 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 21 |
+
|
| 22 |
+
console.log('Looking for Turnstile iframe to solve...');
|
| 23 |
+
|
| 24 |
+
async function solveTurnstile() {
|
| 25 |
+
const frames = page.frames();
|
| 26 |
+
for (const frame of frames) {
|
| 27 |
+
const url = frame.url();
|
| 28 |
+
console.log('Frame URL:', url);
|
| 29 |
+
if (url.includes('turnstile') || url.includes('challenges.cloudflare')) {
|
| 30 |
+
console.log('Found Turnstile challenge frame!');
|
| 31 |
+
try {
|
| 32 |
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
| 33 |
+
const checkbox = await frame.$('input[type="checkbox"]');
|
| 34 |
+
if (checkbox) {
|
| 35 |
+
console.log('Clicking Turnstile checkbox...');
|
| 36 |
+
await checkbox.click();
|
| 37 |
+
return true;
|
| 38 |
+
}
|
| 39 |
+
const verifyButton = await frame.$('[id*="verify"], [class*="verify"], button');
|
| 40 |
+
if (verifyButton) {
|
| 41 |
+
console.log('Clicking verify button in frame...');
|
| 42 |
+
await verifyButton.click();
|
| 43 |
+
return true;
|
| 44 |
+
}
|
| 45 |
+
} catch (e) {
|
| 46 |
+
console.log('Error in Turnstile frame:', e.message);
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
return false;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
let turnstileSolved = await solveTurnstile();
|
| 54 |
+
if (turnstileSolved) {
|
| 55 |
+
console.log('Turnstile interaction attempted, waiting for verification...');
|
| 56 |
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
console.log('Checking for Confirm link on page...');
|
| 60 |
+
const confirmButtons = await page.$$('a[title*="verify"], a[href*="firewall"], button');
|
| 61 |
+
for (const btn of confirmButtons) {
|
| 62 |
+
const text = await page.evaluate(el => el.textContent, btn);
|
| 63 |
+
if (text && text.toLowerCase().includes('confirm')) {
|
| 64 |
+
console.log('Found Confirm button, clicking...');
|
| 65 |
+
await btn.click();
|
| 66 |
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
| 67 |
+
|
| 68 |
+
turnstileSolved = await solveTurnstile();
|
| 69 |
+
if (turnstileSolved) {
|
| 70 |
+
await new Promise(resolve => setTimeout(resolve, 8000));
|
| 71 |
+
}
|
| 72 |
+
break;
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
console.log('Looking for New Email button...');
|
| 77 |
+
const buttons = await page.$$('button, a');
|
| 78 |
+
for (const btn of buttons) {
|
| 79 |
+
const text = await page.evaluate(el => el.textContent, btn);
|
| 80 |
+
if (text && text.includes('New Email')) {
|
| 81 |
+
console.log('Found New Email button, clicking...');
|
| 82 |
+
await btn.click();
|
| 83 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 84 |
+
break;
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
console.log('Waiting for email to appear...');
|
| 89 |
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
| 90 |
+
|
| 91 |
+
let email = null;
|
| 92 |
+
|
| 93 |
+
console.log('Searching for email in page...');
|
| 94 |
+
const allText = await page.evaluate(() => document.body.innerText);
|
| 95 |
+
|
| 96 |
+
const emailPatterns = allText.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g);
|
| 97 |
+
if (emailPatterns) {
|
| 98 |
+
const filtered = emailPatterns.filter(e =>
|
| 99 |
+
!e.includes('gmail.com') &&
|
| 100 |
+
!e.includes('tmailor.com@') &&
|
| 101 |
+
!e.toLowerCase().includes('support')
|
| 102 |
+
);
|
| 103 |
+
if (filtered.length > 0) {
|
| 104 |
+
email = filtered[0];
|
| 105 |
+
console.log('Found email in page text:', email);
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if (!email) {
|
| 110 |
+
const emailSelectors = [
|
| 111 |
+
'#email',
|
| 112 |
+
'#tempmail',
|
| 113 |
+
'.email-address',
|
| 114 |
+
'[data-email]',
|
| 115 |
+
'input[readonly]',
|
| 116 |
+
'input[type="text"]',
|
| 117 |
+
'.copy-text',
|
| 118 |
+
'code',
|
| 119 |
+
'pre'
|
| 120 |
+
];
|
| 121 |
+
|
| 122 |
+
for (const selector of emailSelectors) {
|
| 123 |
+
try {
|
| 124 |
+
const elements = await page.$$(selector);
|
| 125 |
+
for (const element of elements) {
|
| 126 |
+
const value = await page.evaluate(el => {
|
| 127 |
+
return el.value || el.textContent || el.getAttribute('data-email') || el.getAttribute('data-clipboard-text');
|
| 128 |
+
}, element);
|
| 129 |
+
if (value && value.includes('@') && !value.includes('gmail')) {
|
| 130 |
+
email = value.trim();
|
| 131 |
+
console.log(`Found email with selector "${selector}": ${email}`);
|
| 132 |
+
break;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
if (email) break;
|
| 136 |
+
} catch (e) {}
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
console.log('\nTaking screenshot...');
|
| 141 |
+
await page.screenshot({ path: 'tmailor-screenshot.png', fullPage: true });
|
| 142 |
+
console.log('Screenshot saved to tmailor-screenshot.png');
|
| 143 |
+
|
| 144 |
+
console.log('\n=== RESULT ===');
|
| 145 |
+
if (email && email.includes('@')) {
|
| 146 |
+
console.log('SUCCESS! Temp Email:', email);
|
| 147 |
+
} else {
|
| 148 |
+
console.log('Could not find a temp email address');
|
| 149 |
+
console.log('\n--- Page visible text (first 1500 chars) ---');
|
| 150 |
+
console.log(allText.substring(0, 1500));
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
return email;
|
| 154 |
+
|
| 155 |
+
} catch (error) {
|
| 156 |
+
console.error('Error:', error.message);
|
| 157 |
+
} finally {
|
| 158 |
+
await browser.close();
|
| 159 |
+
console.log('Browser closed.');
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
getTempMail();
|