move css to build step, add challenge url

This commit is contained in:
overflowerror 2024-08-16 21:51:09 +02:00
parent 1877b1aa6e
commit 52309a8e21
7 changed files with 142 additions and 103 deletions

View file

@ -5,6 +5,7 @@
"main": "src/main.ts",
"scripts": {
"build": "esbuild src/main.ts --bundle --outfile=public/bundle.js",
"build-mini": "esbuild src/main.ts --minify --bundle --outfile=public/bundle.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {

5
public/challenge.json Normal file
View file

@ -0,0 +1,5 @@
{
"algo": "SHA-256",
"prefixBits": "15",
"input": "challenge:"
}

View file

@ -2,85 +2,9 @@
<html>
<head>
<script src="bundle.js"></script>
<style>
.captcha {
width: 300px;
height: 70px;
padding: 19px;
border: 1px solid grey;
box-sizing: border-box;
font-size: 20px;
font-family: sans-serif;
}
.captcha span {
position: relative;
top: -9px;
}
.captcha .checkbox {
position: relative;
margin: 0;
margin-right: 20px;
height: 30px;
width: 30px;
display: inline-block;
background-color: white;
cursor: pointer;
border: 1px solid black;
border-radius: 3px;
}
.captcha .checkbox:hover {
background-color: lightgrey;
}
.captcha .checkbox.checked {
background-color: #2196F3;
}
.captcha .checkbox.checked::before {
position: absolute;
display: block;
content: "";
width: 7px;
height: 15px;
top: 2px;
left: 9px;
border: white solid;
border-width: 0 5px 5px 0;
transform: rotate(45deg);
}
.captcha .checkbox.loading {
}
.captcha .checkbox.loading::before {
content: "";
display: block;
position: absolute;
top: 2.5px;
left: 2.5px;
width: 17px;
height: 17px;
border: 4px solid;
border-radius: 50%;
border-color: black transparent black transparent;
animation: captchaLoading infinite 1s;
}
@keyframes captchaLoading {
to{
transform: rotate(.5turn)
}
}
</style>
<script>
</script>
<link href="bundle.css" rel="stylesheet">
</head>
<body>
<div class="captcha"></div>
<div class="captcha" data-challenge-url="/challenge.json"></div>
</body>
</html>

10
src/challenge.ts Normal file
View file

@ -0,0 +1,10 @@
export type Challenge = {
algo: string,
prefixBits: number,
input: string,
}
export function validateChallenge(challenge: Challenge): boolean {
return !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input;
}

70
src/default-styles.css Normal file
View file

@ -0,0 +1,70 @@
.captcha {
width: 300px;
height: 70px;
padding: 19px;
border: 1px solid grey;
box-sizing: border-box;
font-size: 20px;
font-family: sans-serif;
}
.captcha span {
position: relative;
top: -9px;
}
.captcha .checkbox {
position: relative;
margin: 0 20px 0 0;
height: 30px;
width: 30px;
display: inline-block;
background-color: white;
cursor: pointer;
border: 1px solid black;
border-radius: 3px;
}
.captcha .checkbox:hover {
background-color: lightgrey;
}
.captcha .checkbox.checked {
background-color: #2196F3;
}
.captcha .checkbox.checked::before {
position: absolute;
display: block;
content: "";
width: 7px;
height: 15px;
top: 2px;
left: 9px;
border: white solid;
border-width: 0 5px 5px 0;
transform: rotate(45deg);
}
.captcha .checkbox.loading {
}
.captcha .checkbox.loading::before {
content: "";
display: block;
position: absolute;
top: 3px;
left: 3px;
width: 17px;
height: 17px;
border: 4px solid;
border-radius: 50%;
border-color: black transparent black transparent;
animation: captchaLoading infinite 1s;
}
@keyframes captchaLoading {
to {
transform: rotate(.5turn)
}
}

View file

@ -29,4 +29,4 @@ Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boo
export async function digest(algo: AlgorithmIdentifier, input: string): Promise<Uint8Array> {
return new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input)));
}
}

View file

@ -1,6 +1,9 @@
import {makeSuffix} from "./content";
import {digest, sha256} from "./hash";
import "./default-styles.css";
import {Challenge, validateChallenge} from "./challenge";
async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string): Promise<string> {
const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(_ => 0));
let iteration = 0;
@ -13,30 +16,56 @@ async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string):
return message;
}
function initCaptchaContentAndGetCheckbox(captcha: Element): Element {
const checkbox = document.createElement("div");
checkbox.classList.add("checkbox");
captcha.append(checkbox);
const text = document.createElement("span");
text.innerText = "I am not a robot";
captcha.append(text);
return checkbox;
}
function prepareChallengeExecution(challenge: Challenge): () => Promise<void> {
return async function() {
console.log("Calculating...");
this.classList.add("loading");
const response = await findHashWithPrefix(challenge.prefixBits, challenge.input);
console.log("Challenge Response: " + response);
this.classList.remove("loading");
this.classList.add("checked");
}
}
async function prepareCaptcha(captcha: Element) {
const challengeUrl = captcha.getAttribute("data-challenge-url");
if (!challengeUrl) {
console.warn("No challenge URL found.");
return;
}
const checkbox = initCaptchaContentAndGetCheckbox(captcha);
checkbox.classList.add("loading");
const challengeResponse = await fetch(challengeUrl);
const challenge = await challengeResponse.json() as Challenge;
if (!validateChallenge(challenge)) {
console.warn("Challenge is invalid.");
return;
}
checkbox.classList.remove("loading");
checkbox.addEventListener("click", prepareChallengeExecution(challenge));
}
window.addEventListener("load", () => {
console.log("load");
[...document.getElementsByClassName("captcha")].forEach(captcha => {
console.dir(captcha);
const checkbox = document.createElement("div");
checkbox.classList.add("checkbox");
captcha.append(checkbox);
const text = document.createElement("span");
text.innerText = "I am not a robot";
captcha.append(text);
checkbox.addEventListener("click", async function() {
console.log("Calculating...");
this.classList.add("loading");
const challenge = makeSuffix(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + ":" + makeSuffix(new Date().valueOf()) + ":";
const response = await findHashWithPrefix(15, challenge);
console.log("Challenge Response: " + response);
this.classList.remove("loading");
this.classList.add("checked");
})
});
[...document.getElementsByClassName("captcha")].forEach(prepareCaptcha);
});