From a6f3613b6a4e41860f4916de508de80e47e2ee98 Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Tue, 11 Apr 2023 21:13:25 +1000 Subject: [PATCH] More concise wording, offer a .min.js for scripts --- haproxy/haproxy.cfg | 6 +- src/js/{argon2.js => argon2.min.js} | 0 src/js/challenge.js | 188 +++++++++++++++++----------- src/js/challenge.min.js | 1 + src/js/worker.js | 2 +- src/js/worker.min.js | 1 + src/lua/scripts/templates.lua | 10 +- 7 files changed, 127 insertions(+), 81 deletions(-) rename src/js/{argon2.js => argon2.min.js} (100%) create mode 100644 src/js/challenge.min.js create mode 100644 src/js/worker.min.js diff --git a/haproxy/haproxy.cfg b/haproxy/haproxy.cfg index 23938f0..7f32697 100644 --- a/haproxy/haproxy.cfg +++ b/haproxy/haproxy.cfg @@ -70,9 +70,9 @@ frontend http-in acl ddos_mode_enabled base,map(/etc/haproxy/map/ddos.map) -m bool # serve challenge page scripts directly from haproxy - http-request return file /etc/haproxy/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 /etc/haproxy/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 /etc/haproxy/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 } + http-request return file /etc/haproxy/js/argon2.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/argon2.min.js } + http-request return file /etc/haproxy/js/challenge.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/challenge.min.js } + http-request return file /etc/haproxy/js/worker.min.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/worker.min.js } # 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/map/maintenance.map) -m found diff --git a/src/js/argon2.js b/src/js/argon2.min.js similarity index 100% rename from src/js/argon2.js rename to src/js/argon2.min.js diff --git a/src/js/challenge.js b/src/js/challenge.js index 9a7cb02..13dd454 100644 --- a/src/js/challenge.js +++ b/src/js/challenge.js @@ -9,110 +9,128 @@ function updateElem(selector, text, color) { } function insertError(str) { - const loader = document.querySelector('#loader'); - const captcha = document.querySelector('#captcha'); + const loader = document.querySelector("#loader"); + const captcha = document.querySelector("#captcha"); console.log(loader, captcha); - (captcha || loader).insertAdjacentHTML('afterend', `

Error: ${str}

`); + (captcha || loader).insertAdjacentHTML( + "afterend", + `

Error: ${str}

`, + ); loader && loader.remove(); captcha && captcha.remove(); - updateElem('.powstatus', ''); + updateElem(".powstatus", ""); } function finishRedirect() { - window.location=location.search.slice(1)+location.hash || "/"; + window.location = location.search.slice(1) + location.hash || "/"; } function makeLoaderGreen() { - const dots = document.querySelectorAll('.b'); + const dots = document.querySelectorAll(".b"); if (dots && dots.length > 0) { - dots.forEach(dot => dot.classList.add('green')); + dots.forEach((dot) => dot.classList.add("green")); } } const wasmSupported = (() => { - try { - if (typeof WebAssembly === "object" - && typeof WebAssembly.instantiate === "function") { - const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)); - if (module instanceof WebAssembly.Module) - return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; - } - } catch (e) { + try { + if ( + typeof WebAssembly === "object" && + typeof WebAssembly.instantiate === "function" + ) { + const module = new WebAssembly.Module( + Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00), + ); + if (module instanceof WebAssembly.Module) { + return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; + } + } + } catch (e) { console.error(e); - } - return false; + } + return false; })(); function postResponse(powResponse, captchaResponse) { const body = { - 'pow_response': powResponse, + "pow_response": powResponse, }; if (captchaResponse) { - body['h-captcha-response'] = captchaResponse; - body['g-recaptcha-response'] = captchaResponse; + body["h-captcha-response"] = captchaResponse; + body["g-recaptcha-response"] = captchaResponse; } - fetch('/.basedflare/bot-check', { - method: 'POST', + fetch("/.basedflare/bot-check", { + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams(body), - redirect: 'manual', - }).then(res => { + redirect: "manual", + }).then((res) => { const s = res.status; if (s >= 400 && s < 500) { - return insertError('Server rejected your submission.'); + return insertError("Server rejected your submission."); } else if (s >= 500) { - return insertError('Server encountered an error.'); + return insertError("Server encountered an error."); } - window.localStorage.setItem('basedflare-redirect', Math.random()); + window.localStorage.setItem("basedflare-redirect", Math.random()); finishRedirect(); }).catch(() => { - insertError('Failed to send request to server.'); + insertError("Failed to send request to server."); }); } -const powFinished = new Promise(resolve => { - +const powFinished = new Promise((resolve) => { let start = Date.now(); const workers = []; let finished = false; const stopPow = () => { finished = true; - const hasCaptcha = document.getElementById('captcha'); + const hasCaptcha = document.getElementById("captcha"); if (hasCaptcha) { - updateElem('.powstatus', 'Proof-of-work completed.', '#31cc31'); + updateElem(".powstatus", "Waiting for captcha.", "#31cc31"); } else { - updateElem('.powstatus', 'Proof-of-work completed. Submitting...', '#31cc31'); + updateElem(".powstatus", "Submitting...", "#31cc31"); makeLoaderGreen(); } - workers.forEach(w => w.terminate()); + workers.forEach((w) => w.terminate()); }; const submitPow = (answer) => { - window.localStorage.setItem('basedflare-pow-response', answer); + window.localStorage.setItem("basedflare-pow-response", answer); stopPow(); - const dummyTime = 4000 - (Date.now()-start); + const dummyTime = 3500 - (Date.now() - start); window.setTimeout(() => { - resolve({ answer }); + resolve({ + answer + }); }, dummyTime); }; - window.addEventListener('DOMContentLoaded', async () => { - - const { time, kb, pow, diff, mode } = document.querySelector('[data-pow]').dataset; - window.addEventListener('storage', event => { - if (event.key === 'basedflare-pow-response' && !finished) { - console.log('Got answer', event.newValue, 'from storage event'); + window.addEventListener("DOMContentLoaded", async () => { + const { + time, + kb, + pow, + diff, + mode + } = + document.querySelector("[data-pow]").dataset; + window.addEventListener("storage", (event) => { + if (event.key === "basedflare-pow-response" && !finished) { + console.log("Got answer", event.newValue, "from storage event"); stopPow(); - resolve({ answer: event.newValue, localStorage: true }); - } else if (event.key === 'basedflare-redirect') { - console.log('Redirecting, solved in another tab'); + resolve({ + answer: event.newValue, + localStorage: true + }); + } else if (event.key === "basedflare-redirect") { + console.log("Redirecting, solved in another tab"); finishRedirect(); } }); if (!wasmSupported) { - return insertError('Browser does not support WebAssembly.'); + return insertError("Browser does not support WebAssembly."); } const powOpts = { time: time, @@ -122,47 +140,71 @@ const powFinished = new Promise(resolve => { type: argon2 ? argon2.ArgonType.Argon2id : null, mode: mode, }; - console.log('Got pow', pow, 'with difficulty', diff); - const eHashes = Math.pow(16, Math.floor(diff/8)) * (((diff%8)*2)||1); - const diffString = '0'.repeat(Math.floor(diff/8)); + console.log("Got pow", pow, "with difficulty", diff); + const eHashes = Math.pow(16, Math.floor(diff / 8)) * + (((diff % 8) * 2) || 1); + const diffString = "0".repeat(Math.floor(diff / 8)); const [userkey, challenge] = pow.split("#"); if (window.Worker) { const cpuThreads = window.navigator.hardwareConcurrency; - const isTor = location.hostname.endsWith('.onion'); + const isTor = location.hostname.endsWith(".onion"); /* Try to use all threads on tor, because tor limits threads for anti fingerprinting but this makes it awfully slow because workerThreads will always be = 1 */ - const workerThreads = (isTor || cpuThreads === 2) ? cpuThreads : Math.max(Math.ceil(cpuThreads/2),cpuThreads-1); + const workerThreads = (isTor || cpuThreads === 2) ? + cpuThreads : + Math.max(Math.ceil(cpuThreads / 2), cpuThreads - 1); const messageHandler = (e) => { if (e.data.length === 1) { const totalHashes = e.data[0]; //assumes all worker threads are same speed - const elapsedSec = Math.floor((Date.now()-start)/1000); - const hps = Math.floor(totalHashes/elapsedSec); - const requiredSec = Math.floor(eHashes/hps) * 1.5; //estimate 1.5x time - const remainingSec = Math.max(0, Math.floor(requiredSec-elapsedSec)); //dont show negative time - console.log(`Proof-of-work: ${hps}H/s, ≈${remainingSec}s remaining`); - return updateElem('.powstatus', `Working... ≈${remainingSec}s remaining`); + const elapsedSec = Math.floor((Date.now() - start) / 1000); + const hps = Math.floor(totalHashes / elapsedSec); + const requiredSec = Math.floor(eHashes / hps) * 1.5; //estimate 1.5x time + const remainingSec = Math.max( + 0, + Math.floor(requiredSec - elapsedSec), + ); //dont show negative time + console.log(`${hps}H/s, ≈${remainingSec}s remaining`); + return updateElem( + ".powstatus", + `Working, ≈${remainingSec}s remaining`, + ); } - if (finished) { return; } + if (finished) return; const [workerId, answer] = e.data; - console.log('Worker', workerId, 'returned answer', answer, 'in', Date.now()-start+'ms'); + console.log( + "Worker", + workerId, + "returned answer", + answer, + "in", + Date.now() - start + "ms", + ); submitPow(`${pow}#${answer}`); - } + }; for (let i = 0; i < workerThreads; i++) { - const powWorker = new Worker('/.basedflare/js/worker.js'); + const powWorker = new Worker("/.basedflare/js/worker.min.js"); powWorker.onmessage = messageHandler; workers.push(powWorker); } start = Date.now(); for (let i = 0; i < workerThreads; i++) { - await new Promise(res => setTimeout(res, 10)); - workers[i].postMessage([userkey, challenge, diff, diffString, powOpts, i, workerThreads]); + await new Promise((res) => setTimeout(res, 10)); + workers[i].postMessage([ + userkey, + challenge, + diff, + diffString, + powOpts, + i, + workerThreads, + ]); } } else { - return insertError('Browser does not support Web Workers.'); + return insertError("Browser does not support Web Workers."); } }); }).then((powResponse) => { - const hasCaptchaForm = document.getElementById('captcha'); + const hasCaptchaForm = document.getElementById("captcha"); if (!hasCaptchaForm && !powResponse.localStorage) { postResponse(powResponse.answer); } @@ -172,14 +214,16 @@ const powFinished = new Promise(resolve => { }); function onCaptchaSubmit(captchaResponse) { - const captchaElem = document.querySelector('[data-sitekey]'); + const captchaElem = document.querySelector("[data-sitekey]"); // captchaElem.insertAdjacentHTML('afterend', `
`); - captchaElem.insertAdjacentHTML('afterend', `
`); + captchaElem.insertAdjacentHTML( + "afterend", + `
`, + ); captchaElem.remove(); - powFinished.then(powResponse => { - updateElem('.powstatus', 'Submitting...', '#31cc31'); + powFinished.then((powResponse) => { + updateElem(".powstatus", "Submitting...", "#31cc31"); makeLoaderGreen(); postResponse(powResponse, captchaResponse); }); } - diff --git a/src/js/challenge.min.js b/src/js/challenge.min.js new file mode 100644 index 0000000..56e7a9c --- /dev/null +++ b/src/js/challenge.min.js @@ -0,0 +1 @@ +function updateElem(selector,text,color){const updateElem=document.querySelector(selector);if(updateElem){updateElem.innerText=text;if(color){updateElem.style.color=color}}}function insertError(str){const loader=document.querySelector("#loader");const captcha=document.querySelector("#captcha");console.log(loader,captcha);(captcha||loader).insertAdjacentHTML("afterend",`

Error: ${ str }

`,);loader&&loader.remove();captcha&&captcha.remove();updateElem(".powstatus","")}function finishRedirect(){window.location=location.search.slice(1)+location.hash||"/"}function makeLoaderGreen(){const dots=document.querySelectorAll(".b");if(dots&&dots.length>0){dots.forEach((dot)=>dot.classList.add("green"))}}const wasmSupported=(()=>{try{if(typeof WebAssembly==="object"&&typeof WebAssembly.instantiate==="function"){const module=new WebAssembly.Module(Uint8Array.of(0x0,0x61,0x73,0x6d,0x01,0x00,0x00,0x00),);if(module instanceof WebAssembly.Module){return new WebAssembly.Instance(module)instanceof WebAssembly.Instance}}}catch(e){console.error(e)}return false})();function postResponse(powResponse,captchaResponse){const body={"pow_response":powResponse};if(captchaResponse){body["h-captcha-response"]=captchaResponse;body["g-recaptcha-response"]=captchaResponse}fetch("/.basedflare/bot-check",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(body),redirect:"manual"}).then((res)=>{const s=res.status;if(s>=400&&s<500){return insertError("Server rejected your submission.")}else if(s>=500){return insertError("Server encountered an error.")}window.localStorage.setItem("basedflare-redirect",Math.random());finishRedirect()}).catch(()=>{insertError("Failed to send request to server.")})}const powFinished=new Promise((resolve)=>{let start=Date.now();const workers=[];let finished=false;const stopPow=()=>{finished=true;const hasCaptcha=document.getElementById("captcha");if(hasCaptcha){updateElem(".powstatus","Waiting for captcha.","#31cc31")}else{updateElem(".powstatus","Submitting...","#31cc31");makeLoaderGreen()}workers.forEach((w)=>w.terminate())};const submitPow=(answer)=>{window.localStorage.setItem("basedflare-pow-response",answer);stopPow();const dummyTime=3500-(Date.now()-start);window.setTimeout(()=>{resolve({answer})},dummyTime)};window.addEventListener("DOMContentLoaded",async()=>{const{time,kb,pow,diff,mode}=document.querySelector("[data-pow]").dataset;window.addEventListener("storage",(event)=>{if(event.key==="basedflare-pow-response"&&!finished){console.log("Got answer",event.newValue,"from storage event");stopPow();resolve({answer:event.newValue,localStorage:true})}else if(event.key==="basedflare-redirect"){console.log("Redirecting, solved in another tab");finishRedirect()}});if(!wasmSupported){return insertError("Browser does not support WebAssembly.")}const powOpts={time:time,mem:kb,hashLen:32,parallelism:1,type:argon2?argon2.ArgonType.Argon2id:null,mode:mode};console.log("Got pow",pow,"with difficulty",diff);const eHashes=Math.pow(16,Math.floor(diff/8))*(((diff%8)*2)||1);const diffString="0".repeat(Math.floor(diff/8));const[userkey,challenge]=pow.split("#");if(window.Worker){const cpuThreads=window.navigator.hardwareConcurrency;const isTor=location.hostname.endsWith(".onion");const workerThreads=(isTor||cpuThreads===2)?cpuThreads:Math.max(Math.ceil(cpuThreads/2),cpuThreads-1);const messageHandler=(e)=>{if(e.data.length===1){const totalHashes=e.data[0];const elapsedSec=Math.floor((Date.now()-start)/1000);const hps=Math.floor(totalHashes/elapsedSec);const requiredSec=Math.floor(eHashes/hps)*1.5;const remainingSec=Math.max(0,Math.floor(requiredSec-elapsedSec),);console.log(`${ hps }H/s, ≈${ remainingSec }s remaining`);return updateElem(".powstatus",`Working, ≈${ remainingSec }s remaining`,)}if(finished){return}const[workerId,answer]=e.data;console.log("Worker",workerId,"returned answer",answer,"in",Date.now()-start+"ms",);submitPow(`${ pow }#${ answer }`)};for(let i=0;isetTimeout(res,10));workers[i].postMessage([userkey,challenge,diff,diffString,powOpts,i,workerThreads])}}else{return insertError("Browser does not support Web Workers.")}})}).then((powResponse)=>{const hasCaptchaForm=document.getElementById("captcha");if(!hasCaptchaForm&&!powResponse.localStorage){postResponse(powResponse.answer)}return powResponse.answer}).catch((e)=>{console.error(e)});function onCaptchaSubmit(captchaResponse){const captchaElem=document.querySelector("[data-sitekey]");captchaElem.insertAdjacentHTML("afterend",`
`,);captchaElem.remove();powFinished.then((powResponse)=>{updateElem(".powstatus","Submitting...","#31cc31");makeLoaderGreen();postResponse(powResponse,captchaResponse)})} diff --git a/src/js/worker.js b/src/js/worker.js index 9a120f0..e35b899 100644 --- a/src/js/worker.js +++ b/src/js/worker.js @@ -8,7 +8,7 @@ async function nativeHash(data, method) { onmessage = async function(e) { const [userkey, challenge, diff, diffString, powOpts, id, threads] = e.data; if (powOpts.mode === "argon2") { - importScripts('/.basedflare/js/argon2.js'); + importScripts('/.basedflare/js/argon2.min.js'); } console.log('Worker thread', id, 'started'); let i = id; diff --git a/src/js/worker.min.js b/src/js/worker.min.js new file mode 100644 index 0000000..0c7992c --- /dev/null +++ b/src/js/worker.min.js @@ -0,0 +1 @@ +async function nativeHash(data,method){const buffer=new TextEncoder('utf-8').encode(data);const hashBuffer=await crypto.subtle.digest(method,buffer);const hashArray=Array.from(new Uint8Array(hashBuffer));return hashArray.map(b=>b.toString(16).padStart(2,'0')).join('')}onmessage=async function(e){const[userkey,challenge,diff,diffString,powOpts,id,threads]=e.data;if(powOpts.mode==="argon2"){importScripts('/.basedflare/js/argon2.min.js')}console.log('Worker thread',id,'started');let i=id;if(id===0){setInterval(()=>{postMessage([i])},500)}while(true){let hash;if(powOpts.mode==="argon2"){const argonHash=await argon2.hash({pass:challenge+i.toString(),salt:userkey,...powOpts});hash=argonHash.hashHex}else{hash=await nativeHash(userkey+challenge+i.toString(),'sha-256')}i%10===0&&await new Promise(res=>setTimeout(res,10));if(hash.toString().startsWith(diffString)&&((parseInt(hash[diffString.length],16)&0xff>>(((diffString.length+1)*8)-diff))===0)){console.log('Worker',id,'found solution');postMessage([id,i]);break}i+=threads}} diff --git a/src/lua/scripts/templates.lua b/src/lua/scripts/templates.lua index 46d7dcd..1ce3ce8 100644 --- a/src/lua/scripts/templates.lua +++ b/src/lua/scripts/templates.lua @@ -32,13 +32,13 @@ details[open]{border-left-color: #1400ff} .b:nth-of-type(1){animation:p 3s infinite} .b:nth-of-type(2){animation:p 3s .5s infinite} .b:nth-of-type(3){animation:p 3s 1s infinite} -@keyframes p{0%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow1)}70%%{transform:scale(1);box-shadow:0 0 0 10px var(--shadow2)}100%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow3)}} +@keyframes p{0%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow1)}70%%{transform:scale(1);box-shadow:0 0 0 8px var(--shadow2)}100%%{transform:scale(.95);box-shadow:0 0 0 0 var(--shadow3)}} - - + + %s @@ -98,14 +98,14 @@ _M.noscript_extra_sha256 = [[ _M.site_name_section = [[

 - Checking your browser before accessing %s + Verifying your connection to %s...

]] -- animation while waiting _M.pow_section = [[

- This process is automatic. Your browser will redirect to your requested content shortly. + This process is automatic, please wait a moment.