mirror of
https://github.com/sequentialread/pow-captcha.git
synced 2025-11-23 22:15:47 +00:00
first attempt at fixing the docs being wrong and re-adding the
cross-origin webworker support
This commit is contained in:
parent
6ef839d2bf
commit
48abd8be27
@ -1,5 +1,5 @@
|
||||
|
||||
FROM golang:1.16-alpine as build
|
||||
FROM golang:1.22-alpine as build
|
||||
ARG GOARCH=
|
||||
ARG GO_BUILD_ARGS=
|
||||
|
||||
|
||||
23
README.md
23
README.md
@ -187,13 +187,20 @@ Revokes an existing API token.
|
||||
In order to set up 💥PoW! Bot Deterrent on your page, you just need to load/include `pow-bot-deterrent.js` and one or more html elements
|
||||
with all 3 of the following properties:
|
||||
|
||||
#### `data-pow-bot-deterrent-url`
|
||||
#### `data-pow-bot-deterrent-static-assets-cross-origin-url`
|
||||
|
||||
This is the base url from which `pow-bot-deterrent.js` will attempt to load additional resources `pow-bot-deterrent.css` and `proofOfWorker.js`.
|
||||
*OPTIONAL* This is the base url from which `pow-bot-deterrent.js` will attempt to load additional resources `pow-bot-deterrent.css` and `proofOfWorker_CrossOrigin.js`.
|
||||
|
||||
> 💬 *INFO* In our examples, we passed the Bot Deterrent server URL down to the HTML page and used it as the value for this property.
|
||||
However, that's not required. The HTML page doesn't need to talk to the Bot Deterrent server at all, it just needs to know where it can
|
||||
download the `pow-bot-deterrent.css` and `proofOfWorker.js` files. There is nothing stopping you from simply hosting those files on your own server or CDN and placing the corresponding URL into the `data-pow-bot-deterrent-url` property.
|
||||
Example value: `https://bot-deterrent.example.com/static/`
|
||||
|
||||
> 💬 *INFO* The HTML page doesn't need to talk to the Bot Deterrent server at all, it just needs to know where it can
|
||||
download the `pow-bot-deterrent.css` and `proofOfWorker_CrossOrigin.js` files. Doing this cross-origin is simpler and easier, but it can cause issues with website's [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP). So if you care about that, try using `data-pow-bot-deterrent-static-assets-path` instead.
|
||||
|
||||
#### `data-pow-bot-deterrent-static-assets-path`
|
||||
|
||||
A path (on the same origin as your site) where the `pow-bot-deterrent.css`, `proofOfWorker.js`, and `scrypt.wasm` files can be found.
|
||||
|
||||
*OPTIONAL*, default value is `/pow-bot-deterrent-static/`
|
||||
|
||||
#### `data-pow-bot-deterrent-challenge`
|
||||
|
||||
@ -470,7 +477,7 @@ There are two main important parts, the form and the javascript at the bottom:
|
||||
<input type="hidden" name="nonce" />
|
||||
<input type="submit" disabled="true" value="Add" />
|
||||
<div class="bot-deterrent-container"
|
||||
data-pow-bot-deterrent-url="{{ .PowAPIURL }}"
|
||||
data-pow-bot-deterrent-static-assets-cross-origin-url="{{ .CrossOriginStaticAssetsUrl }}"
|
||||
data-pow-bot-deterrent-challenge="{{ .Challenge }}"
|
||||
data-pow-bot-deterrent-callback="myPowCallback">
|
||||
</div>
|
||||
@ -484,7 +491,7 @@ There are two main important parts, the form and the javascript at the bottom:
|
||||
document.querySelector("form input[type='submit']").disabled = false;
|
||||
};
|
||||
</script>
|
||||
<script src="{{ .PowAPIURL }}/pow-bot-deterrent-static/pow-bot-deterrent.js"></script>
|
||||
<script src="{{ .CrossOriginStaticAssetsUrl }}/pow-bot-deterrent.js"></script>
|
||||
```
|
||||
|
||||
⚠️ **NOTE** that the element with the `pow-bot-deterrent` data properties is placed **inside a form element**. This is required because the bot deterrent needs to know which input elements it should trigger on. We only want it to trigger when the user actually intends to submit the form; otherwise we are wasting a lot of their CPU cycles for no reason!
|
||||
@ -492,7 +499,7 @@ There are two main important parts, the form and the javascript at the bottom:
|
||||
> 💬 *INFO* The double curly brace elements like `{{ .Challenge }}` are Golang string template interpolations. They are specific to the example app & how it renders the page.
|
||||
|
||||
When the page loads, the `pow-bot-deterrent.js` script will execute, querying the page for all elements with the `data-pow-bot-deterrent-challenge`
|
||||
property. It will then validate each element to make sure it also has the `data-pow-bot-deterrent-url` and `data-pow-bot-deterrent-callback` properties. For each element it found, it will locate the `<form>` parent/grandparent enclosing the element. If none are found, it will throw an error. Otherwise, it will set up an event listener on every input element inside that form, so that as soon as the user starts filling out the form, the bot deterrent display will pop up and the Proof of Work will begin.
|
||||
property. It will then validate each element to make sure it also has the `data-pow-bot-deterrent-static-assets-cross-origin-url` and `data-pow-bot-deterrent-callback` properties. For each element it found, it will locate the `<form>` parent/grandparent enclosing the element. If none are found, it will throw an error. Otherwise, it will set up an event listener on every input element inside that form, so that as soon as the user starts filling out the form, the bot deterrent display will pop up and the Proof of Work will begin.
|
||||
|
||||
When the Proof of Work finishes, `pow-bot-deterrent.js` will call the function specified by `data-pow-bot-deterrent-callback`, passing the winning nonce as the first argument, or throw an error if that function is not defined.
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
<input type="hidden" name="nonce" />
|
||||
<input type="submit" disabled="true" value="Add" />
|
||||
<div class="bot-deterrent-container"
|
||||
data-pow-bot-deterrent-url="http://localhost:8080/"
|
||||
data-pow-bot-deterrent-static-assets-cross-origin-url="{{ .CrossOriginStaticAssetsUrl }}"
|
||||
data-pow-bot-deterrent-challenge="{{ .Challenge }}"
|
||||
data-pow-bot-deterrent-callback="myPowCallback">
|
||||
</div>
|
||||
@ -47,6 +47,6 @@
|
||||
document.querySelector("form input[type='submit']").disabled = false;
|
||||
};
|
||||
</script>
|
||||
<script src="/pow-bot-deterrent-static/pow-bot-deterrent.js"></script>
|
||||
<script src="{{ .CrossOriginStaticAssetsUrl }}/pow-bot-deterrent.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -142,11 +142,11 @@ func renderPageTemplate(challenge string) ([]byte, error) {
|
||||
pageData := struct {
|
||||
Challenge string
|
||||
Items []string
|
||||
PowAPIURL string
|
||||
CrossOriginStaticAssetsUrl string
|
||||
}{
|
||||
Challenge: challenge,
|
||||
Items: items,
|
||||
PowAPIURL: powAPIURL.String(),
|
||||
CrossOriginStaticAssetsUrl: fmt.Sprintf("%s/pow-bot-deterrent-static", powAPIURL.String()),
|
||||
}
|
||||
var outputBuffer bytes.Buffer
|
||||
err = pageTemplate.Execute(&outputBuffer, pageData)
|
||||
|
||||
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.sequentialread.com/forest/pow-bot-deterrent
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04 // indirect
|
||||
git.sequentialread.com/forest/pkg-errors v0.9.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@ -1,5 +1,9 @@
|
||||
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04 h1:FmvQmRJzAgbCc/4qfECAluzd+oVBzXNJMjyLQTJ4Wq0=
|
||||
git.sequentialread.com/forest/config-lite v0.0.0-20220225195944-164dc71bce04/go.mod h1:jaNfZ5BXx8OsKVZ6FuN0Lr/gIeEwbTNNHSO4RpFz6qo=
|
||||
git.sequentialread.com/forest/pkg-errors v0.9.2 h1:j6pwbL6E+TmE7TD0tqRtGwuoCbCfO6ZR26Nv5nest9g=
|
||||
git.sequentialread.com/forest/pkg-errors v0.9.2/go.mod h1:8TkJ/f8xLWFIAid20aoqgDZcCj9QQt+FU+rk415XO1w=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
|
||||
@ -17,12 +17,14 @@
|
||||
|
||||
const challenges = Array.from(document.querySelectorAll("[data-pow-bot-deterrent-challenge]"));
|
||||
const challengesMap = {};
|
||||
let staticAssetsCrossOriginURL = "";
|
||||
let staticAssetsPath = trimSlashes("/pow-bot-deterrent-static/")
|
||||
let proofOfWorker = { postMessage: () => console.error("error: proofOfWorker was never loaded. ") };
|
||||
|
||||
challenges.forEach(element => {
|
||||
|
||||
if(element.dataset.powBotDeterrentStaticAssetsPath) {
|
||||
if(element.dataset.powBotDeterrentStaticAssetsCrossOriginUrl) {
|
||||
staticAssetsCrossOriginURL = trimSlashes(element.dataset.powBotDeterrentStaticAssetsCrossOriginUrl);
|
||||
} else if(element.dataset.powBotDeterrentStaticAssetsPath) {
|
||||
staticAssetsPath = trimSlashes(element.dataset.powBotDeterrentStaticAssetsPath);
|
||||
}
|
||||
|
||||
@ -58,7 +60,13 @@
|
||||
//todo
|
||||
}
|
||||
|
||||
let cssIsAlreadyLoaded = document.querySelector(`link[href='/${staticAssetsPath}/pow-bot-deterrent.css']`);
|
||||
|
||||
let cssIsAlreadyLoaded;
|
||||
if(staticAssetsCrossOriginURL) {
|
||||
cssIsAlreadyLoaded = document.querySelector(`link[href='/${staticAssetsPath}/pow-bot-deterrent.css']`);
|
||||
} else {
|
||||
cssIsAlreadyLoaded = document.querySelector(`link[href='${staticAssetsCrossOriginURL}/pow-bot-deterrent.css']`);
|
||||
}
|
||||
|
||||
cssIsAlreadyLoaded = cssIsAlreadyLoaded || Array.from(document.styleSheets).some(x => {
|
||||
try {
|
||||
@ -74,7 +82,7 @@
|
||||
"charset": "utf8",
|
||||
});
|
||||
stylesheet.onload = () => renderProgressInfo(element);
|
||||
stylesheet.setAttribute("href", `${staticAssetsPath}/pow-bot-deterrent.css`);
|
||||
stylesheet.setAttribute("href", `${staticAssetsCrossOriginURL || staticAssetsPath}/pow-bot-deterrent.css`);
|
||||
} else {
|
||||
renderProgressInfo(element);
|
||||
}
|
||||
@ -134,9 +142,27 @@
|
||||
//todo
|
||||
}
|
||||
|
||||
let webWorkerPointerDataURL = null;
|
||||
if(staticAssetsCrossOriginURL != "") {
|
||||
// https://stackoverflow.com/questions/21913673/execute-web-worker-from-different-origin/62914052#62914052
|
||||
const webWorkerCrossOriginURL = `${staticAssetsCrossOriginURL}/proofOfWorker_CrossOrigin.js`;
|
||||
|
||||
webWorkerPointerDataURL = URL.createObjectURL(
|
||||
new Blob(
|
||||
[ `importScripts( "${ webWorkerCrossOriginURL }" );` ],
|
||||
{ type: "text/javascript" }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let webWorkers;
|
||||
webWorkers = [...Array(numberOfWebWorkersToCreate)].map((_, i) => {
|
||||
const webWorker = new Worker(`/${staticAssetsPath}/proofOfWorker.js?v=2`);
|
||||
let webWorker;
|
||||
if(staticAssetsCrossOriginURL != "") {
|
||||
webWorker = new Worker(webWorkerPointerDataURL);
|
||||
} else {
|
||||
webWorker = new Worker(`/${staticAssetsPath}/proofOfWorker.js?v=2`);
|
||||
}
|
||||
webWorker.onmessage = function(e) {
|
||||
const challengeState = challengesMap[e.data.challenge]
|
||||
if(!challengeState) {
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
|
||||
// THIS FILE IS GENERATED AUTOMATICALLY
|
||||
// Dont edit this file by hand.
|
||||
// Either edit proofOfWorkerStub.js or edit the build located in the wasm_build folder.
|
||||
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
|
||||
|
||||
|
||||
|
||||
let scrypt;
|
||||
let scryptPromise;
|
||||
let wasm = undefined;
|
||||
let wasm;
|
||||
|
||||
let working = false;
|
||||
const batchSize = 4;
|
||||
const batchSize = 8;
|
||||
|
||||
onmessage = function(e) {
|
||||
if(e.data.stop) {
|
||||
@ -94,6 +94,8 @@ onmessage = function(e) {
|
||||
challenge.keyLength
|
||||
);
|
||||
|
||||
//console.log(i.toString(16), hashHex);
|
||||
|
||||
const endOfHash = hashHex.substring(hashHex.length-challenge.difficulty.length);
|
||||
if(endOfHash < smallestHash) {
|
||||
smallestHash = endOfHash
|
||||
@ -124,7 +126,7 @@ onmessage = function(e) {
|
||||
}
|
||||
};
|
||||
|
||||
if(scrypt) {
|
||||
if(wasm && scrypt) {
|
||||
doWork();
|
||||
} else {
|
||||
scryptPromise.then(() => {
|
||||
@ -365,11 +367,11 @@ let wasm_bindgen;
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
/pow-bot-deterrent-static/_bindgen = Object.assign(__wbg_init, { initSync }, __exports);
|
||||
wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports);
|
||||
|
||||
})();
|
||||
|
||||
scrypt = wasm_bindgen.scrypt;
|
||||
scryptPromise = wasm_bindgen({module_or_path: "/pow-bot-deterrent-static/scrypt.wasm"});
|
||||
scryptPromise = wasm_bindgen({module_or_path: "/static/scrypt.wasm"});
|
||||
|
||||
|
||||
|
||||
288
static/proofOfWorker_CrossOrigin.js
Normal file
288
static/proofOfWorker_CrossOrigin.js
Normal file
File diff suppressed because one or more lines are too long
@ -36,12 +36,12 @@ fi
|
||||
|
||||
cd ../
|
||||
|
||||
cp scrypt-wasm/pkg/scrypt_wasm_bg.wasm ../static/
|
||||
cp scrypt-wasm/pkg/scrypt_wasm_bg.wasm ../static/scrypt.wasm
|
||||
|
||||
echo '
|
||||
// THIS FILE IS GENERATED AUTOMATICALLY
|
||||
// Dont edit this file by hand.
|
||||
// Either edit proofOfWorkerStub.js or edit the build located in the wasm_build folder.
|
||||
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
|
||||
' > ../static/proofOfWorker.js
|
||||
|
||||
cat ../proofOfWorkerStub.js | tail -n +6 >> ../static/proofOfWorker.js
|
||||
@ -56,4 +56,64 @@ scryptPromise = wasm_bindgen({module_or_path: "/static/scrypt.wasm"});
|
||||
|
||||
' >> ../static/proofOfWorker.js
|
||||
|
||||
|
||||
|
||||
|
||||
## -----------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
## The proofOfWorker_CrossOrigin.js version embeds the WebAssembly binary into the WebWorker script,
|
||||
## This is neccesary when the pow-bot-deterrent static assets can't be hosted on the same origin
|
||||
## However, it also means that the site can't use a content-security-policy which restricts external javascript
|
||||
|
||||
echo '
|
||||
// THIS FILE IS GENERATED AUTOMATICALLY
|
||||
// Dont edit this file by hand.
|
||||
// Either edit proofOfWorkerStub.js or edit the build script located in the wasm_build folder.
|
||||
' > ../static/proofOfWorker_CrossOrigin.js
|
||||
|
||||
cat ../proofOfWorkerStub.js | tail -n +6 >> ../static/proofOfWorker_CrossOrigin.js
|
||||
|
||||
echo '
|
||||
|
||||
// https://caniuse.com/mdn-javascript_builtins_uint8array_frombase64 its at 60% in oct 2025
|
||||
if (!Uint8Array.fromBase64) {
|
||||
Uint8Array.fromBase64 = function(base64String) {
|
||||
const binaryString = atob(base64String);
|
||||
const toReturn = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
toReturn[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return toReturn;
|
||||
};
|
||||
}
|
||||
|
||||
const base64WASM = "'"$(cat ../static/scrypt.wasm | base64 -w 0)"'";
|
||||
|
||||
const wasmBinary = Uint8Array.fromBase64(base64WASM);
|
||||
|
||||
scryptPromise = WebAssembly.instantiate(wasmBinary, {}).then(instantiatedModule => {
|
||||
wasm = instantiatedModule.instance.exports;
|
||||
|
||||
' >> ../static/proofOfWorker_CrossOrigin.js
|
||||
|
||||
# wasm was defined at the top of proofOfWorker.js, so don't define it again.
|
||||
# tail -n +5 skips the first 4 lines.
|
||||
# we are trying to skip all of:
|
||||
#
|
||||
# let wasm;
|
||||
# export function __wbg_set_wasm(val) {
|
||||
# wasm = val;
|
||||
# }
|
||||
#
|
||||
cat scrypt-wasm/pkg/scrypt_wasm_bg.js | tail -n +5 \
|
||||
| sed 's/export function scrypt/scrypt = function/' \
|
||||
| sed 's/^export //' \
|
||||
| sed -E 's/^/ /' >> ../static/proofOfWorker_CrossOrigin.js
|
||||
|
||||
echo '
|
||||
});
|
||||
' >> ../static/proofOfWorker_CrossOrigin.js
|
||||
|
||||
echo "Build successful!"
|
||||
Loading…
x
Reference in New Issue
Block a user