mirror of
https://gitgud.io/fatchan/haproxy-protection.git
synced 2025-05-09 02:05:37 +00:00
Move everything under paths like /.basedflare/ instead of putting stuff in paths where it might conflict
Move templates to own file instead of in main lua script Rename some stuff from "hcatpcha" to more correct "captcha" and "bot-check" because we no longer only have hcaptcha Clean some code and add a few comments
This commit is contained in:
@ -43,15 +43,15 @@ frontend http-in
|
|||||||
acl is_existing_vhost hdr(host),lower,map_str(/etc/haproxy/hosts.map) -m found
|
acl is_existing_vhost hdr(host),lower,map_str(/etc/haproxy/hosts.map) -m found
|
||||||
http-request silent-drop unless is_existing_vhost
|
http-request silent-drop unless is_existing_vhost
|
||||||
|
|
||||||
# debug only, /cdn-cgi/trace
|
# debug information at /.basedflare/cgi/trace
|
||||||
#http-request return status 200 content-type "text/plain; charset=utf-8" lf-file /etc/haproxy/trace.txt if { path /cdn-cgi/trace }
|
http-request return status 200 content-type "text/plain; charset=utf-8" lf-file /etc/haproxy/trace.txt if { path /.basedflare/cgi/trace }
|
||||||
|
|
||||||
# acl for blocked IPs/subnets
|
# acl for blocked IPs/subnets
|
||||||
acl blocked_ip_or_subnet src,map_ip(/etc/haproxy/blocked.map) -m found
|
acl blocked_ip_or_subnet src,map_ip(/etc/haproxy/blocked.map) -m found
|
||||||
http-request deny deny_status 403 if blocked_ip_or_subnet
|
http-request deny deny_status 403 if blocked_ip_or_subnet
|
||||||
|
|
||||||
# ratelimit (and for tor, kill circuit) on POST bot-check. legitimate users shouldn't hit this.
|
# ratelimit (and for tor, kill circuit) on POST bot-check. legitimate users shouldn't hit this.
|
||||||
http-request track-sc0 src table bot_check_post_throttle if { path /bot-check } { method POST }
|
http-request track-sc0 src table bot_check_post_throttle if { path /.basedflare/bot-check } { method POST }
|
||||||
http-request lua.kill-tor-circuit if { sc_http_req_rate(0) gt 1 }
|
http-request lua.kill-tor-circuit if { sc_http_req_rate(0) gt 1 }
|
||||||
http-request tarpit if { sc_http_req_rate(0) gt 1 }
|
http-request tarpit if { sc_http_req_rate(0) gt 1 }
|
||||||
|
|
||||||
@ -65,13 +65,13 @@ frontend http-in
|
|||||||
acl ddos_mode_enabled base,map(/etc/haproxy/ddos.map) -m bool
|
acl ddos_mode_enabled base,map(/etc/haproxy/ddos.map) -m bool
|
||||||
|
|
||||||
# serve challenge page scripts directly from haproxy
|
# serve challenge page scripts directly from haproxy
|
||||||
http-request return file /var/www/js/argon2.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /js/argon2.js }
|
http-request return file /var/www/js/argon2.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/argon2.js }
|
||||||
http-request return file /var/www/js/challenge.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /js/challenge.js }
|
http-request return file /var/www/js/challenge.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/challenge.js }
|
||||||
http-request return file /var/www/js/worker.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /js/worker.js }
|
http-request return file /var/www/js/worker.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/worker.js }
|
||||||
|
|
||||||
# acl for domains in maintenance mode to return maintenance page (after challenge page htp-request return rules, for the footerlogo)
|
# acl for domains in maintenance mode to return maintenance page (after challenge page htp-request return rules, for the footerlogo)
|
||||||
acl maintenance_mode hdr(host),lower,map_str(/etc/haproxy/maintenance.map) -m found
|
acl maintenance_mode hdr(host),lower,map_str(/etc/haproxy/maintenance.map) -m found
|
||||||
http-request return file /var/www/html/maintenance.html status 200 content-type "text/html; charset=utf-8" hdr "cache-control" "private, max-age=30" if maintenance_mode
|
http-request return lf-file /var/www/html/maintenance.html status 200 content-type "text/html; charset=utf-8" hdr "cache-control" "private, max-age=30" if maintenance_mode
|
||||||
|
|
||||||
# create acl for bools updated by lua
|
# create acl for bools updated by lua
|
||||||
acl captcha_passed var(txn.captcha_passed) -m bool
|
acl captcha_passed var(txn.captcha_passed) -m bool
|
||||||
@ -80,14 +80,14 @@ frontend http-in
|
|||||||
acl validate_pow var(txn.validate_pow) -m bool
|
acl validate_pow var(txn.validate_pow) -m bool
|
||||||
|
|
||||||
# check pow/captcha and show page if necessary
|
# check pow/captcha and show page if necessary
|
||||||
acl on_captcha_url path /bot-check
|
acl on_bot_check path /.basedflare/bot-check
|
||||||
http-request use-service lua.hcaptcha-view if on_captcha_url !is_excluded
|
http-request use-service lua.bot-check if on_bot_check !is_excluded
|
||||||
|
|
||||||
# challenge decisions, checking, and redirecting to /bot-check
|
# challenge decisions, checking, and redirecting to /bot-check
|
||||||
http-request lua.decide-checks-necessary if !is_excluded !on_captcha_url ddos_mode_enabled
|
http-request lua.decide-checks-necessary if !is_excluded !on_bot_check ddos_mode_enabled
|
||||||
http-request lua.hcaptcha-check if !is_excluded !on_captcha_url validate_captcha
|
http-request lua.captcha-check if !is_excluded !on_bot_check validate_captcha
|
||||||
http-request lua.pow-check if !is_excluded !on_captcha_url validate_pow OR !is_excluded !on_captcha_url ddos_mode_enabled_override
|
http-request lua.pow-check if !is_excluded !on_bot_check validate_pow OR !is_excluded !on_bot_check ddos_mode_enabled_override
|
||||||
http-request redirect location /bot-check?%[capture.req.uri] code 302 if validate_captcha !captcha_passed !on_captcha_url ddos_mode_enabled !is_excluded OR validate_pow !pow_passed !on_captcha_url ddos_mode_enabled !is_excluded OR !pow_passed ddos_mode_enabled_override !on_captcha_url !is_excluded
|
http-request redirect location /.basedflare/bot-check?%[capture.req.uri] code 302 if validate_captcha !captcha_passed !on_bot_check ddos_mode_enabled !is_excluded OR validate_pow !pow_passed !on_bot_check ddos_mode_enabled !is_excluded OR !pow_passed ddos_mode_enabled_override !on_bot_check !is_excluded
|
||||||
|
|
||||||
# X-Cache-Status header (may be sent in some non-cache responses because NOSRV can happen for other reasons, but should always be present in responses served by cache-use)
|
# X-Cache-Status header (may be sent in some non-cache responses because NOSRV can happen for other reasons, but should always be present in responses served by cache-use)
|
||||||
http-response set-header X-Cache-Status HIT if !{ srv_id -m found }
|
http-response set-header X-Cache-Status HIT if !{ srv_id -m found }
|
||||||
|
@ -25,6 +25,7 @@ const wasmSupported = (() => {
|
|||||||
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})();
|
})();
|
||||||
@ -37,7 +38,7 @@ function postResponse(powResponse, captchaResponse) {
|
|||||||
body['h-captcha-response'] = captchaResponse;
|
body['h-captcha-response'] = captchaResponse;
|
||||||
body['g-recaptcha-response'] = captchaResponse;
|
body['g-recaptcha-response'] = captchaResponse;
|
||||||
}
|
}
|
||||||
fetch('/bot-check', {
|
fetch('/.basedflare/bot-check', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
@ -52,7 +53,7 @@ function postResponse(powResponse, captchaResponse) {
|
|||||||
return insertError('server responded with error.');
|
return insertError('server responded with error.');
|
||||||
}
|
}
|
||||||
finishRedirect();
|
finishRedirect();
|
||||||
}).catch(err => {
|
}).catch(() => {
|
||||||
insertError('failed to send challenge response.');
|
insertError('failed to send challenge response.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -74,7 +75,7 @@ const powFinished = new Promise((resolve, reject) => {
|
|||||||
const eHashes = Math.pow(16, Math.floor(diff/8)) * ((diff%8)*2);
|
const eHashes = Math.pow(16, Math.floor(diff/8)) * ((diff%8)*2);
|
||||||
const diffString = '0'.repeat(Math.floor(diff/8));
|
const diffString = '0'.repeat(Math.floor(diff/8));
|
||||||
const combined = pow;
|
const combined = pow;
|
||||||
const [userkey, challenge, signature] = combined.split("#");
|
const [userkey, challenge] = combined.split("#");
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
if (window.Worker) {
|
if (window.Worker) {
|
||||||
const cpuThreads = window.navigator.hardwareConcurrency;
|
const cpuThreads = window.navigator.hardwareConcurrency;
|
||||||
@ -117,7 +118,7 @@ const powFinished = new Promise((resolve, reject) => {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('No webworker support, running in main/UI thread!');
|
console.warn('No webworker support, running in main/UI thread!');
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let start = Date.now();
|
const start = Date.now();
|
||||||
while(true) {
|
while(true) {
|
||||||
const hash = await argon2.hash({
|
const hash = await argon2.hash({
|
||||||
pass: challenge + i.toString(),
|
pass: challenge + i.toString(),
|
||||||
|
@ -16,7 +16,7 @@ onmessage = async function(e) {
|
|||||||
...argonOpts,
|
...argonOpts,
|
||||||
});
|
});
|
||||||
// This throttle seems to really help some browsers not stop the workers abruptly
|
// This throttle seems to really help some browsers not stop the workers abruptly
|
||||||
i % 10 === 0 && await new Promise(res => setTimeout(res, 10));
|
i % 10 === 0 && await new Promise(res => setTimeout(res, 1));
|
||||||
if (hash.hashHex.startsWith(diffString)
|
if (hash.hashHex.startsWith(diffString)
|
||||||
&& ((parseInt(hash.hashHex[diffString.length],16) &
|
&& ((parseInt(hash.hashHex[diffString.length],16) &
|
||||||
0xff >> (((diffString.length+1)*8)-diff)) === 0)) {
|
0xff >> (((diffString.length+1)*8)-diff)) === 0)) {
|
||||||
|
@ -66,4 +66,3 @@ function _M.send_tor_control_port(circuit_identifier)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return _M
|
return _M
|
||||||
|
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
_M = {}
|
_M = {}
|
||||||
|
|
||||||
|
-- Testing only
|
||||||
|
-- require("socket")
|
||||||
|
-- require("print_r")
|
||||||
|
|
||||||
|
-- main libs
|
||||||
local url = require("url")
|
local url = require("url")
|
||||||
local utils = require("utils")
|
local utils = require("utils")
|
||||||
local cookie = require("cookie")
|
local cookie = require("cookie")
|
||||||
local json = require("json")
|
local json = require("json")
|
||||||
local sha = require("sha")
|
local sha = require("sha")
|
||||||
local randbytes = require("randbytes")
|
local randbytes = require("randbytes")
|
||||||
|
local templates = require("templates")
|
||||||
|
|
||||||
|
-- argon2 POW
|
||||||
local argon2 = require("argon2")
|
local argon2 = require("argon2")
|
||||||
local pow_difficulty = tonumber(os.getenv("POW_DIFFICULTY") or 18)
|
local pow_difficulty = tonumber(os.getenv("POW_DIFFICULTY") or 18)
|
||||||
local pow_kb = tonumber(os.getenv("POW_KB") or 6000)
|
local pow_kb = tonumber(os.getenv("POW_KB") or 6000)
|
||||||
@ -16,10 +24,7 @@ argon2.parallelism(1)
|
|||||||
argon2.hash_len(32)
|
argon2.hash_len(32)
|
||||||
argon2.variant(argon2.variants.argon2_id)
|
argon2.variant(argon2.variants.argon2_id)
|
||||||
|
|
||||||
-- Testing only
|
-- environment variables
|
||||||
-- require("socket")
|
|
||||||
-- require("print_r")
|
|
||||||
|
|
||||||
local captcha_secret = os.getenv("HCAPTCHA_SECRET") or os.getenv("RECAPTCHA_SECRET")
|
local captcha_secret = os.getenv("HCAPTCHA_SECRET") or os.getenv("RECAPTCHA_SECRET")
|
||||||
local captcha_sitekey = os.getenv("HCAPTCHA_SITEKEY") or os.getenv("RECAPTCHA_SITEKEY")
|
local captcha_sitekey = os.getenv("HCAPTCHA_SITEKEY") or os.getenv("RECAPTCHA_SITEKEY")
|
||||||
local captcha_cookie_secret = os.getenv("CAPTCHA_COOKIE_SECRET")
|
local captcha_cookie_secret = os.getenv("CAPTCHA_COOKIE_SECRET")
|
||||||
@ -27,6 +32,7 @@ local pow_cookie_secret = os.getenv("POW_COOKIE_SECRET")
|
|||||||
local hmac_cookie_secret = os.getenv("HMAC_COOKIE_SECRET")
|
local hmac_cookie_secret = os.getenv("HMAC_COOKIE_SECRET")
|
||||||
local ray_id = os.getenv("RAY_ID")
|
local ray_id = os.getenv("RAY_ID")
|
||||||
|
|
||||||
|
-- load captcha map and set hcaptcha/recaptch based off env vars
|
||||||
local captcha_map = Map.new("/etc/haproxy/ddos.map", Map._str);
|
local captcha_map = Map.new("/etc/haproxy/ddos.map", Map._str);
|
||||||
local captcha_provider_domain = ""
|
local captcha_provider_domain = ""
|
||||||
local captcha_classname = ""
|
local captcha_classname = ""
|
||||||
@ -47,6 +53,7 @@ else
|
|||||||
captcha_backend_name = "recaptcha"
|
captcha_backend_name = "recaptcha"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- setup initial server backends based on hosts.map into backends.map
|
||||||
function _M.setup_servers()
|
function _M.setup_servers()
|
||||||
if pow_difficulty < 8 then
|
if pow_difficulty < 8 then
|
||||||
error("POW_DIFFICULTY must be > 8. Around 16-32 is better")
|
error("POW_DIFFICULTY must be > 8. Around 16-32 is better")
|
||||||
@ -75,103 +82,6 @@ function _M.setup_servers()
|
|||||||
handle:close()
|
handle:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- main page template
|
|
||||||
local body_template = [[
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name='viewport' content='width=device-width initial-scale=1'>
|
|
||||||
<title>Hold on...</title>
|
|
||||||
<style>
|
|
||||||
:root{--text-color:#c5c8c6;--bg-color:#1d1f21}
|
|
||||||
@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#EEE}}
|
|
||||||
.h-captcha,.g-recaptcha{min-height:85px;display:block}
|
|
||||||
.red{color:red;font-weight:bold}
|
|
||||||
.powstatus{color:green;font-weight:bold}
|
|
||||||
a,a:visited{color:var(--text-color)}
|
|
||||||
body,html{height:100%%}
|
|
||||||
body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);font-family:Helvetica,Arial,sans-serif;max-width:1200px;margin:0 auto;padding: 0 20px}
|
|
||||||
details{transition: border-left-color 0.5s;max-width:1200px;text-align:left;border-left: 2px solid var(--text-color);padding:10px}
|
|
||||||
code{background-color:#dfdfdf30;border-radius:3px;padding:0 3px;}
|
|
||||||
img,h3,p{margin:0 0 5px 0}
|
|
||||||
footer{font-size:x-small;margin-top:auto;margin-bottom:20px;text-align:center}
|
|
||||||
img{display:inline}
|
|
||||||
.pt{padding-top:15vh;display:flex;align-items:center;word-break:break-all}
|
|
||||||
.pt img{margin-right:10px}
|
|
||||||
details[open]{border-left-color: #1400ff}
|
|
||||||
.lds-ring{display:inline-block;position:relative;width:80px;height:80px}.lds-ring div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:10px;border:5px solid var(--text-color);border-radius:50%%;animation:lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;border-color:var(--text-color) transparent transparent transparent}.lds-ring div:nth-child(1){animation-delay:-0.45s}.lds-ring div:nth-child(2){animation-delay:-0.3s}.lds-ring div:nth-child(3){animation-delay:-0.15s}@keyframes lds-ring{0%%{transform:rotate(0deg)}100%%{transform:rotate(360deg)}}
|
|
||||||
</style>
|
|
||||||
<noscript>
|
|
||||||
<style>.jsonly{display:none}</style>
|
|
||||||
</noscript>
|
|
||||||
<script src="/js/argon2.js"></script>
|
|
||||||
<script src="/js/challenge.js"></script>
|
|
||||||
</head>
|
|
||||||
<body data-pow="%s" data-diff="%s" data-time="%s" data-kb="%s">
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
<noscript>
|
|
||||||
<br>
|
|
||||||
<p class="red">JavaScript is required on this page.</p>
|
|
||||||
%s
|
|
||||||
</noscript>
|
|
||||||
<div class="powstatus"></div>
|
|
||||||
<footer>
|
|
||||||
<p>Security and Performance by <a href="https://gitgud.io/fatchan/haproxy-protection/">haproxy-protection</a></p>
|
|
||||||
<p>Node: <code>%s</code></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
]]
|
|
||||||
|
|
||||||
local noscript_extra_template = [[
|
|
||||||
<details>
|
|
||||||
<summary>No JavaScript?</summary>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<p>Run this in a linux terminal (requires <code>argon2</code> package installed):</p>
|
|
||||||
<code style="word-break: break-all;">
|
|
||||||
echo "Q0g9IiQyIjtCPSQocHJpbnRmICcwJS4wcycgJChzZXEgMSAkNCkpO2VjaG8gIldvcmtpbmcuLi4iO0k9MDt3aGlsZSB0cnVlOyBkbyBIPSQoZWNobyAtbiAkQ0gkSSB8IGFyZ29uMiAkMSAtaWQgLXQgJDUgLWsgJDYgLXAgMSAtbCAzMiAtcik7RT0ke0g6MDokNH07W1sgJEUgPT0gJEIgXV0gJiYgZWNobyAiT3V0cHV0OiIgJiYgZWNobyAkMSMkMiMkMyMkSSAmJiBleGl0IDA7KChJKyspKTtkb25lOwo=" | base64 -d | bash -s %s %s %s %s %s %s
|
|
||||||
</code>
|
|
||||||
<li>Paste the script output into the box and submit:
|
|
||||||
<form method="post">
|
|
||||||
<textarea name="pow_response" placeholder="script output" required></textarea>
|
|
||||||
<div><input type="submit" value="submit" /></div>
|
|
||||||
</form>
|
|
||||||
</ol>
|
|
||||||
</details>
|
|
||||||
]]
|
|
||||||
|
|
||||||
-- title with favicon and hostname
|
|
||||||
local site_name_section_template = [[
|
|
||||||
<h3 class="pt">
|
|
||||||
<img src="/favicon.ico" width="32" height="32" alt="icon">
|
|
||||||
%s
|
|
||||||
</h3>
|
|
||||||
]]
|
|
||||||
|
|
||||||
-- spinner animation for proof of work
|
|
||||||
local pow_section_template = [[
|
|
||||||
<h3>
|
|
||||||
Checking your browser for robots 🤖
|
|
||||||
</h3>
|
|
||||||
<div class="jsonly">
|
|
||||||
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
|
||||||
</div>
|
|
||||||
]]
|
|
||||||
|
|
||||||
-- message, captcha form and submit button
|
|
||||||
local captcha_section_template = [[
|
|
||||||
<h3>
|
|
||||||
Please solve the captcha to continue.
|
|
||||||
</h3>
|
|
||||||
<div id="captcha" class="jsonly">
|
|
||||||
<div class="%s" data-sitekey="%s" data-callback="onCaptchaSubmit"></div>
|
|
||||||
<script src="%s" async defer></script>
|
|
||||||
</div>
|
|
||||||
]]
|
|
||||||
|
|
||||||
-- kill a tor circuit
|
-- kill a tor circuit
|
||||||
function _M.kill_tor_circuit(txn)
|
function _M.kill_tor_circuit(txn)
|
||||||
local ip = txn.sf:src()
|
local ip = txn.sf:src()
|
||||||
@ -214,7 +124,7 @@ function _M.view(applet)
|
|||||||
-- check if captcha is enabled, path+domain priority, then just domain, and 0 otherwise
|
-- check if captcha is enabled, path+domain priority, then just domain, and 0 otherwise
|
||||||
local captcha_enabled = false
|
local captcha_enabled = false
|
||||||
local host = applet.headers['host'][0]
|
local host = applet.headers['host'][0]
|
||||||
local path = applet.qs; --because on /bot-check?/whatever, .qs (query string) holds the "path"
|
local path = applet.qs; --because on /.basedflare/bot-check?/whatever, .qs (query string) holds the "path"
|
||||||
|
|
||||||
local captcha_map_lookup = captcha_map:lookup(host..path) or captcha_map:lookup(host) or 0
|
local captcha_map_lookup = captcha_map:lookup(host..path) or captcha_map:lookup(host) or 0
|
||||||
captcha_map_lookup = tonumber(captcha_map_lookup)
|
captcha_map_lookup = tonumber(captcha_map_lookup)
|
||||||
@ -223,18 +133,18 @@ function _M.view(applet)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- pow at least is always enabled when reaching bot-check page
|
-- pow at least is always enabled when reaching bot-check page
|
||||||
site_name_body = string.format(site_name_section_template, host)
|
site_name_body = string.format(templates.site_name_section, host)
|
||||||
if captcha_enabled then
|
if captcha_enabled then
|
||||||
captcha_body = string.format(captcha_section_template, captcha_classname,
|
captcha_body = string.format(templates.captcha_section, captcha_classname,
|
||||||
captcha_sitekey, captcha_script_src)
|
captcha_sitekey, captcha_script_src)
|
||||||
else
|
else
|
||||||
pow_body = pow_section_template
|
pow_body = templates.pow_section
|
||||||
noscript_extra_body = string.format(noscript_extra_template, user_key, challenge_hash, signature,
|
noscript_extra_body = string.format(templates.noscript_extra, user_key, challenge_hash, signature,
|
||||||
math.ceil(pow_difficulty/8), pow_time, pow_kb)
|
math.ceil(pow_difficulty/8), pow_time, pow_kb)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- sub in the body sections
|
-- sub in the body sections
|
||||||
response_body = string.format(body_template, combined_challenge,
|
response_body = string.format(templates.body, combined_challenge,
|
||||||
pow_difficulty, pow_time, pow_kb,
|
pow_difficulty, pow_time, pow_kb,
|
||||||
site_name_body, pow_body, captcha_body, noscript_extra_body, ray_id)
|
site_name_body, pow_body, captcha_body, noscript_extra_body, ray_id)
|
||||||
response_status_code = 403
|
response_status_code = 403
|
@ -1,10 +1,10 @@
|
|||||||
package.path = package.path .. "./?.lua;/etc/haproxy/scripts/?.lua;/etc/haproxy/libs/?.lua"
|
package.path = package.path .. "./?.lua;/etc/haproxy/scripts/?.lua;/etc/haproxy/libs/?.lua"
|
||||||
|
|
||||||
local hcaptcha = require("hcaptcha")
|
local bot_check = require("bot-check")
|
||||||
|
|
||||||
core.register_service("hcaptcha-view", "http", hcaptcha.view)
|
core.register_service("bot-check", "http", bot_check.view)
|
||||||
core.register_action("hcaptcha-check", { 'http-req', }, hcaptcha.check_captcha_status)
|
core.register_action("captcha-check", { 'http-req', }, bot_check.check_captcha_status)
|
||||||
core.register_action("pow-check", { 'http-req', }, hcaptcha.check_pow_status)
|
core.register_action("pow-check", { 'http-req', }, bot_check.check_pow_status)
|
||||||
core.register_action("decide-checks-necessary", { 'http-req', }, hcaptcha.decide_checks_necessary)
|
core.register_action("decide-checks-necessary", { 'http-req', }, bot_check.decide_checks_necessary)
|
||||||
core.register_action("kill-tor-circuit", { 'http-req', }, hcaptcha.kill_tor_circuit)
|
core.register_action("kill-tor-circuit", { 'http-req', }, bot_check.kill_tor_circuit)
|
||||||
core.register_init(hcaptcha.setup_servers)
|
core.register_init(bot_check.setup_servers)
|
||||||
|
100
src/scripts/templates.lua
Normal file
100
src/scripts/templates.lua
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
local _M = {}
|
||||||
|
|
||||||
|
-- main page template
|
||||||
|
_M.body = [[
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name='viewport' content='width=device-width initial-scale=1'>
|
||||||
|
<title>Hold on...</title>
|
||||||
|
<style>
|
||||||
|
:root{--text-color:#c5c8c6;--bg-color:#1d1f21}
|
||||||
|
@media (prefers-color-scheme:light){:root{--text-color:#333;--bg-color:#EEE}}
|
||||||
|
.h-captcha,.g-recaptcha{min-height:85px;display:block}
|
||||||
|
.red{color:red;font-weight:bold}
|
||||||
|
.powstatus{color:green;font-weight:bold}
|
||||||
|
a,a:visited{color:var(--text-color)}
|
||||||
|
body,html{height:100%%}
|
||||||
|
body{display:flex;flex-direction:column;background-color:var(--bg-color);color:var(--text-color);font-family:Helvetica,Arial,sans-serif;max-width:1200px;margin:0 auto;padding: 0 20px}
|
||||||
|
details{transition: border-left-color 0.5s;max-width:1200px;text-align:left;border-left: 2px solid var(--text-color);padding:10px}
|
||||||
|
code{background-color:#dfdfdf30;border-radius:3px;padding:0 3px;}
|
||||||
|
img,h3,p{margin:0 0 5px 0}
|
||||||
|
footer{font-size:x-small;margin-top:auto;margin-bottom:20px;text-align:center}
|
||||||
|
img{display:inline}
|
||||||
|
.pt{padding-top:15vh;display:flex;align-items:center;word-break:break-all}
|
||||||
|
.pt img{margin-right:10px}
|
||||||
|
details[open]{border-left-color: #1400ff}
|
||||||
|
.lds-ring{display:inline-block;position:relative;width:80px;height:80px}.lds-ring div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:10px;border:5px solid var(--text-color);border-radius:50%%;animation:lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;border-color:var(--text-color) transparent transparent transparent}.lds-ring div:nth-child(1){animation-delay:-0.45s}.lds-ring div:nth-child(2){animation-delay:-0.3s}.lds-ring div:nth-child(3){animation-delay:-0.15s}@keyframes lds-ring{0%%{transform:rotate(0deg)}100%%{transform:rotate(360deg)}}
|
||||||
|
</style>
|
||||||
|
<noscript>
|
||||||
|
<style>.jsonly{display:none}</style>
|
||||||
|
</noscript>
|
||||||
|
<script src="/.basedflare/js/argon2.js"></script>
|
||||||
|
<script src="/.basedflare/js/challenge.js"></script>
|
||||||
|
</head>
|
||||||
|
<body data-pow="%s" data-diff="%s" data-time="%s" data-kb="%s">
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
<noscript>
|
||||||
|
<br>
|
||||||
|
<p class="red">JavaScript is required on this page.</p>
|
||||||
|
%s
|
||||||
|
</noscript>
|
||||||
|
<div class="powstatus"></div>
|
||||||
|
<footer>
|
||||||
|
<p>Security and Performance by <a href="https://gitgud.io/fatchan/haproxy-protection/">haproxy-protection</a></p>
|
||||||
|
<p>Node: <code>%s</code></p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
]]
|
||||||
|
|
||||||
|
_M.noscript_extra = [[
|
||||||
|
<details>
|
||||||
|
<summary>No JavaScript?</summary>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<p>Run this in a linux terminal (requires <code>argon2</code> package installed):</p>
|
||||||
|
<code style="word-break: break-all;">
|
||||||
|
echo "Q0g9IiQyIjtCPSQocHJpbnRmICcwJS4wcycgJChzZXEgMSAkNCkpO2VjaG8gIldvcmtpbmcuLi4iO0k9MDt3aGlsZSB0cnVlOyBkbyBIPSQoZWNobyAtbiAkQ0gkSSB8IGFyZ29uMiAkMSAtaWQgLXQgJDUgLWsgJDYgLXAgMSAtbCAzMiAtcik7RT0ke0g6MDokNH07W1sgJEUgPT0gJEIgXV0gJiYgZWNobyAiT3V0cHV0OiIgJiYgZWNobyAkMSMkMiMkMyMkSSAmJiBleGl0IDA7KChJKyspKTtkb25lOwo=" | base64 -d | bash -s %s %s %s %s %s %s
|
||||||
|
</code>
|
||||||
|
<li>Paste the script output into the box and submit:
|
||||||
|
<form method="post">
|
||||||
|
<textarea name="pow_response" placeholder="script output" required></textarea>
|
||||||
|
<div><input type="submit" value="submit" /></div>
|
||||||
|
</form>
|
||||||
|
</ol>
|
||||||
|
</details>
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- title with favicon and hostname
|
||||||
|
_M.site_name_section = [[
|
||||||
|
<h3 class="pt">
|
||||||
|
<img src="/favicon.ico" width="32" height="32" alt="icon">
|
||||||
|
%s
|
||||||
|
</h3>
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- spinner animation for proof of work
|
||||||
|
_M.pow_section = [[
|
||||||
|
<h3>
|
||||||
|
Checking your browser for robots 🤖
|
||||||
|
</h3>
|
||||||
|
<div class="jsonly">
|
||||||
|
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||||
|
</div>
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- message, captcha form and submit button
|
||||||
|
_M.captcha_section = [[
|
||||||
|
<h3>
|
||||||
|
Please solve the captcha to continue.
|
||||||
|
</h3>
|
||||||
|
<div id="captcha" class="jsonly">
|
||||||
|
<div class="%s" data-sitekey="%s" data-callback="onCaptchaSubmit"></div>
|
||||||
|
<script src="%s" async defer></script>
|
||||||
|
</div>
|
||||||
|
]]
|
||||||
|
|
||||||
|
return _M
|
Reference in New Issue
Block a user