diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3d5968f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +demo/** linguist-vendored +standalone/public/tester.html linguist-vendored +standalone/public/login.html linguist-vendored +standalone/public/index.html linguist-vendored +widget/test.html linguist-vendored +wasm/test/browser.html linguist-vendored \ No newline at end of file diff --git a/assets/archive/cli/.gitignore b/assets/archive/cli/.gitignore deleted file mode 100644 index a14702c..0000000 --- a/assets/archive/cli/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# dependencies (bun install) -node_modules - -# output -out -dist -*.tgz - -# code coverage -coverage -*.lcov - -# logs -logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# caches -.eslintcache -.cache -*.tsbuildinfo - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store diff --git a/assets/archive/cli/README.md b/assets/archive/cli/README.md deleted file mode 100644 index 15e6709..0000000 --- a/assets/archive/cli/README.md +++ /dev/null @@ -1 +0,0 @@ -# Please see [github.com/tiagozip/cap](https://github.com/tiagozip/cap) diff --git a/assets/archive/cli/bun.lock b/assets/archive/cli/bun.lock deleted file mode 100644 index 71e1c7b..0000000 --- a/assets/archive/cli/bun.lock +++ /dev/null @@ -1,33 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@cap.js/cli", - "dependencies": { - "@cap.js/solver": "0.0.3", - "kleur": "^4.1.5", - }, - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@cap.js/solver": ["@cap.js/solver@0.0.3", "", {}, "sha512-J/Vso8LllJGBmVuj68mXalSsOg8a+Ry0/lnOYu63srk37UFlbQVWdcm63hARnEdOduR33DWS0Ot/yGHPliJhqw=="], - - "@types/bun": ["@types/bun@1.2.10", "", { "dependencies": { "bun-types": "1.2.10" } }, "sha512-eilv6WFM3M0c9ztJt7/g80BDusK98z/FrFwseZgT4bXCq2vPhXD4z8R3oddmAn+R/Nmz9vBn4kweJKmGTZj+lg=="], - - "@types/node": ["@types/node@22.15.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A=="], - - "bun-types": ["bun-types@1.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ=="], - - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - } -} diff --git a/assets/archive/cli/index.js b/assets/archive/cli/index.js deleted file mode 100644 index 39a285b..0000000 --- a/assets/archive/cli/index.js +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bun - -import solver from "@cap.js/solver"; -import kleur from "kleur"; - -const args = process.argv.slice(2); - -(async () => { - if (args.length === 0) { - console.log(`${kleur.bold("@cap.js/cli")} ${kleur.gray( - "cli solver for cap challenges" - )}${ - !process.versions?.bun - ? `\n\n ${kleur.yellow().bold("warn")}${kleur.dim( - ":" - )} bun is not installed and recommended` - : "" - } - -Usage: - ${kleur.dim("$")} ${kleur.bold().green("bunx @cap.js/cli")} ${kleur.cyan( - "" - )} - -Options: - ${kleur.cyan("")} The challenges to solve in - format \`salt:target\``); - return; - } - - const challenges = args.map((arg) => { - const [salt, target] = arg.split(":"); - - if (!salt || !target) { - console.error( - `${kleur.red("Error:")} ${kleur.yellow(arg)} is not a valid challenge` - ); - process.exit(1); - } - - return [salt, target]; - }); - - console.log( - `${kleur.cyan("@cap.js/cli")} ${kleur.dim( - `${challenges.length} challenges` - )}\n\n\n` - ); - - const start = performance.now(); - - await solver(challenges, { - onProgress: (status) => { - process.stdout.moveCursor(0, -2); - process.stdout.clearScreenDown(); - console.log( - `${status.result.salt}:${status.result.target}:${status.result.nonce}` - ); - console.log(kleur.dim("\nSolving challenges… " + status.progress + "%")); - }, - }); - - const end = performance.now(); - - process.stdout.moveCursor(0, -2); - process.stdout.clearScreenDown(); - console.log( - kleur.dim(`\nSolved challenges in ${kleur.bold(Math.round(end - start))}ms`) - ); -})(); diff --git a/assets/archive/cli/package.json b/assets/archive/cli/package.json deleted file mode 100644 index 5c09f97..0000000 --- a/assets/archive/cli/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "version": "0.0.5", - "name": "@cap.js/cli", - "module": "index.js", - "description": "CLI server-side solver for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.", - "type": "module", - "private": false, - "repository": { - "type": "git", - "url": "git+https://github.com/tiagozip/cap.git" - }, - "scripts": { - "npm:publish": "npm publish --access public" - }, - "author": "Tiago", - "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", - "anti-abuse", - "anti-automation", - "anti-bot", - "anti-ddos", - "anti-dos", - "anti-exploitation", - "anti-spam", - "anti-scraping", - "attack-mitigation", - "protocol", - "client-server", - "computational-puzzle", - "complexity", - "puzzle", - "crypto", - "cryptographic", - "algorithm", - "cybersecurity", - "ddos", - "hashcash", - "hcaptcha", - "captcha-alternative", - "verification", - "invisible", - "pow", - "proof-of-work", - "recaptcha", - "spam", - "bots", - "filtering", - "turing-test" - ], - "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" - }, - "dependencies": { - "@cap.js/solver": "0.0.3", - "kleur": "^4.1.5" - }, - "license": "Apache-2.0", - "bin": { - "cli": "./index.js" - }, - "bugs": { - "url": "https://github.com/tiagozip/cap/issues" - }, - "homepage": "https://github.com/tiagozip/cap#readme" -} diff --git a/assets/archive/standalone/Dockerfile b/assets/archive/standalone/Dockerfile deleted file mode 100644 index f72e975..0000000 --- a/assets/archive/standalone/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM oven/bun:1 AS base -WORKDIR /usr/src/app - -FROM base AS install -RUN mkdir -p /temp/dev -COPY package.json bun.lock /temp/dev/ -RUN cd /temp/dev && bun install - -FROM base AS prod -RUN mkdir -p /temp/prod -COPY package.json bun.lock /temp/prod/ -RUN cd /temp/prod && bun install - -FROM base AS prerelease -COPY --from=install /temp/dev/node_modules node_modules -COPY . . - -FROM base AS release -COPY --from=prod /temp/prod/node_modules node_modules -COPY --from=prerelease /usr/src/app . -WORKDIR /usr/src/app - -RUN mkdir -p /usr/src/app/.data && chown -R bun:bun /usr/src/app/.data - -EXPOSE 3000/tcp - -USER bun - -ENTRYPOINT [ "bun", "run", "./src/index.js" ] \ No newline at end of file diff --git a/assets/archive/standalone/bun.lock b/assets/archive/standalone/bun.lock deleted file mode 100644 index a08968c..0000000 --- a/assets/archive/standalone/bun.lock +++ /dev/null @@ -1,79 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "app", - "dependencies": { - "@cap.js/server": "^1.0.13", - "@elysiajs/cors": "^1.3.3", - "@elysiajs/static": "^1.3.0", - "elysia": "^1.3.1", - "elysia-rate-limit": "^4.3.0", - }, - "devDependencies": { - "bun-types": "latest", - }, - }, - }, - "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@cap.js/server": ["@cap.js/server@1.0.13", "", { "dependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.3" } }, "sha512-JcEd/XdqheDSaoBdW0YK4o2lpxlFiCipcUcvgr8ZHscprYVXOTpk7fpagiXqJAJSr68dwyaYk+h8WBv42R4QTw=="], - - "@elysiajs/cors": ["@elysiajs/cors@1.3.3", "", { "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-mYIU6PyMM6xIJuj7d27Vt0/wuzVKIEnFPjcvlkyd7t/m9xspAG37cwNjFxVOnyvY43oOd2I/oW2DB85utXpA2Q=="], - - "@elysiajs/static": ["@elysiajs/static@1.3.0", "", { "dependencies": { "node-cache": "^5.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-7mWlj2U/AZvH27IfRKqpUjDP1W9ZRldF9NmdnatFEtx0AOy7YYgyk0rt5hXrH6wPcR//2gO2Qy+k5rwswpEhJA=="], - - "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], - - "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/node": ["@types/node@22.15.2", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A=="], - - "bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="], - - "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], - - "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], - - "debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], - - "elysia": ["elysia@1.3.1", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-En41P6cDHcHtQ0nvfsn9ayB+8ahQJqG1nzvPX8FVZjOriFK/RtZPQBtXMfZDq/AsVIk7JFZGFEtAVEmztNJVhQ=="], - - "elysia-rate-limit": ["elysia-rate-limit@4.3.0", "", { "dependencies": { "@alloc/quick-lru": "5.2.0", "debug": "4.3.4" }, "peerDependencies": { "elysia": ">= 1.0.0" } }, "sha512-TCquBhMqUK+Wce+2RoaIk6byhJ6KMtO316PYp4Mc1GKkBH7qKPQH7Y5qVLauBkzx69wBYgFwMrtuz8yToG3JtQ=="], - - "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], - - "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], - - "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], - - "file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], - - "node-cache": ["node-cache@5.1.2", "", { "dependencies": { "clone": "2.x" } }, "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg=="], - - "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - - "peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="], - - "strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="], - - "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "@tokenizer/inflate/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - - "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - } -} diff --git a/assets/archive/standalone/package.json b/assets/archive/standalone/package.json deleted file mode 100644 index 2588bb1..0000000 --- a/assets/archive/standalone/package.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "name": "cap-standalone", - "version": "1.0.17", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch src/index.js", - "docker:publish": "docker buildx build --platform linux/amd64,linux/arm64 -t tiago2/cap:latest . --push" - }, - "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", - "anti-abuse", - "anti-automation", - "anti-bot", - "anti-ddos", - "anti-dos", - "anti-exploitation", - "anti-spam", - "anti-scraping", - "attack-mitigation", - "protocol", - "client-server", - "computational-puzzle", - "complexity", - "puzzle", - "crypto", - "cryptographic", - "algorithm", - "cybersecurity", - "ddos", - "hashcash", - "hcaptcha", - "captcha-alternative", - "verification", - "invisible", - "pow", - "proof-of-work", - "recaptcha", - "spam", - "bots", - "filtering", - "turing-test" - ], - "dependencies": { - "@cap.js/server": "^1.0.13", - "@elysiajs/cors": "^1.3.3", - "@elysiajs/static": "^1.3.0", - "elysia": "^1.3.1", - "elysia-rate-limit": "^4.3.0" - }, - "devDependencies": { - "bun-types": "latest" - }, - "module": "src/index.js" -} diff --git a/assets/archive/standalone/public/index.html b/assets/archive/standalone/public/index.html deleted file mode 100644 index 223cd4f..0000000 --- a/assets/archive/standalone/public/index.html +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - Cap - - - - - - -
-

Your keys

- -
- -
-

Loading keys...

-
- - - diff --git a/assets/archive/standalone/public/index.js b/assets/archive/standalone/public/index.js deleted file mode 100644 index 793ba26..0000000 --- a/assets/archive/standalone/public/index.js +++ /dev/null @@ -1,407 +0,0 @@ -const createKeyBtn = document.querySelector(".new-keys"); - -createKeyBtn.addEventListener("click", async () => { - const modal = document.createElement("div"); - modal.classList.add("dialog-wrapper"); - modal.innerHTML = ` -
- - -

Create a new key

-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
- -
-
`; - - document.body.appendChild(modal); - - const inputs = [ - "keyName", - "challengesCount", - "challengeSize", - "challengeDifficulty", - "expiresMs", - ]; - - const createKeyBtn = modal.querySelector(".create-key"); - - modal.addEventListener("click", (e) => { - if (e.target === modal) { - modal.remove(); - } - }); - - const closeButton = modal.querySelector(".close-button"); - closeButton.addEventListener("click", () => { - modal.remove(); - }); - - modal.querySelector("#keyName").focus(); - - inputs.forEach((input) => { - input = modal.querySelector(`#${input}`); - input.addEventListener("input", () => { - createKeyBtn.disabled = !inputs.every((i) => { - const inputEl = modal.querySelector(`#${i}`); - return inputEl.value.trim() !== ""; - }); - }); - }); - - createKeyBtn.addEventListener("click", async () => { - createKeyBtn.disabled = true; - let props = {}; - - inputs.forEach((i) => { - i.disabled = true; - props[i] = modal.querySelector(`#${i}`).value; - }); - createKeyBtn.innerText = "Creating key..."; - const { message, publicKey, privateKey } = await ( - await fetch("/internal/createKey", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(props), - }) - ).json(); - - createKeyBtn.disabled = false; - inputs.forEach((i) => { - i.disabled = false; - }); - createKeyBtn.innerText = "Create key"; - - if (message) return alert(message); - - modal.remove(); - reloadKeysList(); - - const newModal = document.createElement("div"); - newModal.classList.add("dialog-wrapper"); - newModal.innerHTML = ` -
- -

Key details

-

Make sure to copy your secret key — it's required to validate tokens and you won't be able to see it again.

- -
- - -
- -
- - -
-
`; - - document.body.appendChild(newModal); - - const newCloseButton = newModal.querySelector(".close-button"); - newCloseButton.addEventListener("click", () => { - newModal.remove(); - }); - }); -}); - -const reloadKeysList = async () => { - const { keys } = await (await fetch("/internal/listKeys")).json(); - - const keysEl = document.querySelector(".keys"); - if (!keys.length) { - keysEl.innerHTML = "

You don't have any keys.

"; - return; - } - - keysEl.innerHTML = ""; - - keys.forEach((key) => { - const keyEl = document.createElement("div"); - keyEl.innerHTML = ` `; - keyEl.querySelector("span.key-name").innerText = key.name; - keyEl.title = key.publicKey; - keyEl.classList.add("key"); - - keyEl.querySelector(".copy-key").addEventListener("click", () => { - navigator.clipboard.writeText(key.publicKey); - keyEl.querySelector( - ".copy-key" - ).innerHTML = ` Copied`; - setTimeout(() => { - keyEl.querySelector( - ".copy-key" - ).innerHTML = ` Copy key`; - }, 1000); - }); - - keyEl.querySelector(".edit-key").addEventListener("click", () => { - const modal = document.createElement("div"); - modal.classList.add("dialog-wrapper"); - modal.innerHTML = ` -
- - -

Edit key

-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - - -
-
`; - - document.body.appendChild(modal); - - modal.querySelector(".delete-key").addEventListener("click", async () => { - modal.remove(); - - const newModal = document.createElement("div"); - newModal.classList.add("dialog-wrapper"); - newModal.innerHTML = ` -
- - -

Delete key?

-

This is permanent. Once your key is deleted, you'll no longer be able to interact with it through the API

-
- -
-
`; - - document.body.appendChild(newModal); - - const confirmDeleteBtn = newModal.querySelector(".confirm-delete"); - - newModal.addEventListener("click", (e) => { - if (e.target === newModal) { - newModal.remove(); - } - }); - - const closeButton = newModal.querySelector(".close-button"); - closeButton.addEventListener("click", () => { - newModal.remove(); - }); - - confirmDeleteBtn.addEventListener("click", async () => { - confirmDeleteBtn.disabled = true; - await fetch("/internal/deleteKey", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - publicKey: key.publicKey, - }), - }); - await reloadKeysList(); - newModal.remove(); - }); - - confirmDeleteBtn.focus(); - }); - - modal.querySelector(".rotate-key").addEventListener("click", async () => { - modal.remove(); - - const newModal = document.createElement("div"); - newModal.classList.add("dialog-wrapper"); - newModal.innerHTML = ` -
- - -

Rotate secret key

-

This will rotate your secret key while keeping your key ID, invalidating the old one.

-
- -
-
`; - - document.body.appendChild(newModal); - - const confirmRotateBtn = newModal.querySelector(".confirm-rotate"); - - newModal.addEventListener("click", (e) => { - if (e.target === newModal) { - newModal.remove(); - } - }); - - const closeButton = newModal.querySelector(".close-button"); - closeButton.addEventListener("click", () => { - newModal.remove(); - }); - - confirmRotateBtn.addEventListener("click", async () => { - confirmRotateBtn.disabled = true; - - const { privateKey } = await (await fetch("/internal/rotateKey", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - publicKey: key.publicKey, - }), - })).json(); - await reloadKeysList(); - newModal.remove(); - - const successModal = document.createElement("div"); - successModal.classList.add("dialog-wrapper"); - successModal.innerHTML = ` -
- -

Secret key rotated

- -

Make sure to copy your new secret key — it's required to validate tokens and you won't be able to see it again.

- -
- - -
- -
- - -
-
`; - - document.body.appendChild(successModal); - - const newCloseButton = successModal.querySelector(".close-button"); - newCloseButton.addEventListener("click", () => { - successModal.remove(); - }); - }); - - confirmRotateBtn.focus(); - }); - - const inputs = [ - "keyName", - "challengesCount", - "challengeSize", - "challengeDifficulty", - "expiresMs", - ]; - - const createKeyBtn = modal.querySelector(".create-key"); - - modal.addEventListener("click", (e) => { - if (e.target === modal) { - modal.remove(); - } - }); - - const closeButton = modal.querySelector(".close-button"); - closeButton.addEventListener("click", () => { - modal.remove(); - }); - - modal.querySelector("#keyName").focus(); - - inputs.forEach((input) => { - modal.querySelector(`#${input}`).value = - key[input === "keyName" ? "name" : input]; - input = modal.querySelector(`#${input}`); - input.addEventListener("input", () => { - createKeyBtn.disabled = !inputs.every((i) => { - const inputEl = modal.querySelector(`#${i}`); - return inputEl.value.trim() !== ""; - }); - }); - }); - - createKeyBtn.addEventListener("click", async () => { - createKeyBtn.disabled = true; - let props = {}; - - inputs.forEach((i) => { - i.disabled = true; - props[i] = modal.querySelector(`#${i}`).value; - }); - createKeyBtn.innerText = "Saving changes..."; - const { message } = await ( - await fetch("/internal/editKey", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - publicKey: key.publicKey, - ...props, - }), - }) - ).json(); - - createKeyBtn.disabled = false; - inputs.forEach((i) => { - i.disabled = false; - }); - createKeyBtn.innerText = "Save changes"; - - if (message) return alert(message); - - modal.remove(); - reloadKeysList(); - }); - }); - - keysEl.appendChild(keyEl); - }); -}; - -reloadKeysList(); diff --git a/assets/archive/standalone/public/lock.html b/assets/archive/standalone/public/lock.html deleted file mode 100644 index 0d86ce4..0000000 --- a/assets/archive/standalone/public/lock.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - Login to Cap - - - - -
- - -
- - -
- - -

-

- Powered by Cap -

-
- - - diff --git a/assets/archive/standalone/public/logo.png b/assets/archive/standalone/public/logo.png deleted file mode 100644 index f6e67dc..0000000 Binary files a/assets/archive/standalone/public/logo.png and /dev/null differ diff --git a/assets/archive/standalone/public/test.html b/assets/archive/standalone/public/test.html deleted file mode 100644 index 6dabaff..0000000 --- a/assets/archive/standalone/public/test.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - Challenge tester - - - - -
- - - - - - - -
- -
No logs
- -
- -
- - - - - - diff --git a/assets/archive/standalone/src/index.js b/assets/archive/standalone/src/index.js deleted file mode 100644 index 448cfc0..0000000 --- a/assets/archive/standalone/src/index.js +++ /dev/null @@ -1,451 +0,0 @@ -import { timingSafeEqual } from "node:crypto"; -import fs from "node:fs/promises"; -import Cap from "@cap.js/server"; -import { cors } from "@elysiajs/cors"; -import { staticPlugin } from "@elysiajs/static"; -import crypto from "crypto"; -import { Elysia, file, NotFoundError } from "elysia"; -import { rateLimit } from "elysia-rate-limit"; -import path from "path"; - -const ADMIN_KEY = process.env.ADMIN_KEY?.trim(); -const dataDir = "./.data"; -const keysStorePath = path.join(dataDir, "keys.json"); - -function generateAdminKeyHint() { - return `\nWe've generated this one for you to use: \n\n${crypto - .randomBytes(30) - .toString("hex")}\n\nThen, restart this process.\n\n`; -} - -if (!ADMIN_KEY) { - console.error( - `\nNo admin key has been set. Make sure to set it using the \n\`ADMIN_KEY\` environment variable.${generateAdminKeyHint()}`, - ); - process.exit(1); -} - -if (ADMIN_KEY === "your_secret_key") { - console.error( - `\nDon't leave the admin key as default! Make sure to set it \nusing the \`ADMIN_KEY\` environment variable.${generateAdminKeyHint()}`, - ); - process.exit(1); -} - -if (ADMIN_KEY.length < 20) { - console.warn( - `\n${"*".repeat( - 60, - )}\n\nThe admin key you're using is quite short. We recommend \nusing a longer one.${generateAdminKeyHint()}${"*".repeat( - 60, - )}\n`, - ); -} - -let keys = []; -const capInstances = new Map(); - -const updateCache = async () => { - let cacheConfig = {}; - - try { - cacheConfig = JSON.parse( - await fs.readFile(path.join(dataDir, "assets-cache.json"), "utf-8"), - ); - } catch {} - - const lastUpdate = cacheConfig["lastUpdate"] || 0; - const currentTime = Date.now(); - const updateInterval = 1000 * 60 * 60 * 24; // 1 day - - if (!(currentTime - lastUpdate > updateInterval)) return; - - const CACHE_HOST = process.env.CACHE_HOST || "https://cdn.jsdelivr.net"; - if (CACHE_HOST === "disable") return; - - const WIDGET_VERSION = process.env.WIDGET_VERSION || "latest"; - const WASM_VERSION = process.env.WASM_VERSION || "latest"; - - try { - const [widgetSource, floatingSource, wasmSource, wasmLoaderSource] = - await Promise.all([ - fetch(`${CACHE_HOST}/npm/@cap.js/widget@${WIDGET_VERSION}`).then((r) => - r.text(), - ), - fetch( - `${CACHE_HOST}/npm/@cap.js/widget@${WIDGET_VERSION}/cap-floating.min.js`, - ).then((r) => r.text()), - fetch( - `${CACHE_HOST}/npm/@cap.js/wasm@${WASM_VERSION}/browser/cap_wasm_bg.wasm`, - ).then((r) => r.arrayBuffer()), - fetch( - `${CACHE_HOST}/npm/@cap.js/wasm@${WASM_VERSION}/browser/cap_wasm.min.js`, - ).then((r) => r.text()), - ]); - - cacheConfig["lastUpdate"] = currentTime; - await fs.writeFile( - path.join(dataDir, "assets-cache.json"), - JSON.stringify(cacheConfig), - ); - - await fs.writeFile(path.join(dataDir, "assets-widget.js"), widgetSource); - await fs.writeFile( - path.join(dataDir, "assets-floating.js"), - floatingSource, - ); - await fs.writeFile( - path.join(dataDir, "assets-cap_wasm_bg.wasm"), - Buffer.from(wasmSource), - ); - await fs.writeFile( - path.join(dataDir, "assets-cap_wasm.js"), - wasmLoaderSource, - ); - - console.log("[asset server] updated assets cache"); - } catch (e) { - console.error( - "[asset server] error updating assets cache, trying to load them might fail:", - e, - ); - } -}; - -const init = async () => { - try { - await fs.mkdir(dataDir, { recursive: true }); - - const data = await fs.readFile(keysStorePath, "utf-8"); - keys = JSON.parse(data); - capInstances.clear(); - - keys.forEach((key) => { - capInstances.set( - key.publicKey, - new Cap({ - tokens_store_path: path.join(dataDir, `tokens-${key.publicKey}.json`), - }), - ); - }); - } catch { - await fs.writeFile(keysStorePath, "[]"); - keys = []; - capInstances.clear(); - } - - await updateCache(); -}; - -const saveKeys = async () => { - await fs.writeFile(keysStorePath, JSON.stringify(keys)); -}; - -const getCapInstance = (publicKey) => { - if (!capInstances.has(publicKey) || !capInstances.get(publicKey)) { - const keyData = keys.find((k) => k.publicKey === publicKey); - if (keyData) { - capInstances.set( - publicKey, - new Cap({ - tokens_store_path: path.join(dataDir, `tokens-${publicKey}.json`), - }), - ); - } - } - return capInstances.get(publicKey); -}; - -const auth = new Elysia({ - prefix: "/internal/auth", -}) - .use( - rateLimit({ - scoping: "scoped", - count: 10, - duration: 15000, - }), - ) - .post("/", async ({ body, cookie, set }) => { - try { - if ( - !body?.password || - !timingSafeEqual(Buffer.from(body.password), Buffer.from(ADMIN_KEY)) - ) { - set.status = 401; - return { success: false }; - } - } catch { - set.status = 500; - return { success: false }; - } - - cookie["cap-admin-key"].set({ - value: await Bun.password.hash(body.password), - httpOnly: true, - secure: true, - sameSite: "lax", - path: "/", - maxAge: 86400 * 7, - }); - - return { success: true }; - }) - .get("/logout", ({ cookie, redirect }) => { - cookie["cap-admin-key"].remove(); - return redirect("/"); - }); - -const internal = new Elysia({ prefix: "/internal" }) - .onBeforeHandle(async ({ cookie, set }) => { - const authCookie = cookie["cap-admin-key"]?.value; - if (!authCookie || !(await Bun.password.verify(ADMIN_KEY, authCookie))) { - set.status = 401; - return { - success: false, - message: "Unauthorized", - }; - } - }) - .use( - rateLimit({ - scoping: "scoped", - number: 60, - duration: 10000, - }), - ) - .post( - "/createKey", - async ({ - body: { - keyName, - challengesCount = 18, - challengeSize = 32, - challengeDifficulty = 4, - expiresMs = 600000, - }, - set, - }) => { - if (!keyName?.trim()) { - set.status = 400; - return { success: false, message: "Key name is required" }; - } - - const publicKey = crypto.randomBytes(6).toString("hex"); - const privateKey = crypto.randomBytes(25).toString("hex"); - const privateKeyHash = await Bun.password.hash(privateKey); - - const newKey = { - name: keyName, - publicKey, - privateKey: privateKeyHash, - challengesCount: Number(challengesCount), - challengeSize: Number(challengeSize), - challengeDifficulty: Number(challengeDifficulty), - expiresMs: Number(expiresMs), - }; - - keys.push(newKey); - await saveKeys(); - - getCapInstance(publicKey); - - set.status = 201; - return { - success: true, - publicKey, - privateKey, - }; - }, - ) - .post( - "/editKey", - async ({ - body: { - publicKey, - keyName, - challengesCount, - challengeSize, - challengeDifficulty, - expiresMs, - }, - set, - }) => { - const keyIndex = keys.findIndex((key) => key.publicKey === publicKey); - - if (keyIndex === -1) { - set.status = 404; - return { success: false, message: "Key not found" }; - } - - if (!keyName.trim()) { - set.status = 400; - return { success: false, message: "Key name is required" }; - } - - const existingKey = keys[keyIndex]; - keys[keyIndex] = { - ...existingKey, - name: keyName, - challengesCount: Number(challengesCount), - challengeSize: Number(challengeSize), - challengeDifficulty: Number(challengeDifficulty), - expiresMs: Number(expiresMs), - }; - - await saveKeys(); - - return { success: true }; - }, - ) - .get("/listKeys", async () => { - return { - keys: keys.map(({ privateKey, ...rest }) => rest), - }; - }) - .post("/deleteKey", async ({ body: { publicKey }, set }) => { - const initialLength = keys.length; - keys = keys.filter((key) => key.publicKey !== publicKey); - - if (keys.length === initialLength) { - set.status = 404; - return { success: false, message: "Key not found" }; - } - - await saveKeys(); - - const tokenFilePath = path.join(dataDir, `tokens-${publicKey}.json`); - try { - await fs.unlink(tokenFilePath); - } catch (error) { - if (error.code !== "ENOENT") { - console.warn( - `Could not delete token file ${tokenFilePath}:`, - error.message, - ); - } - } - - capInstances.delete(publicKey); - - set.status = 200; - return { success: true }; - }) - .post("/rotateKey", async ({ body: { publicKey }, set }) => { - const keyIndex = keys.findIndex((key) => key.publicKey === publicKey); - - if (keyIndex === -1) { - set.status = 404; - return { success: false, message: "Key not found" }; - } - - const newPrivateKey = crypto.randomBytes(25).toString("hex"); - const newPrivateKeyHash = await Bun.password.hash(newPrivateKey); - - keys[keyIndex].privateKey = newPrivateKeyHash; - - await saveKeys(); - - return { success: true, privateKey: newPrivateKey }; - }); - -const api = new Elysia({ prefix: "/:key" }) - .use( - cors({ - origin: process.env.CORS_ORIGIN?.split(",") || true, - }), - ) - .use( - rateLimit({ - scoping: "scoped", - number: 80, - duration: 1000, - }), - ) - .derive(({ params }) => { - const keyData = keys.find((k) => k.publicKey === params.key); - if (!keyData) { - throw new NotFoundError("Key not found"); - } - return { keyData, capInstance: getCapInstance(params.key) }; - }) - .post("/api/challenge", async ({ keyData, capInstance }) => { - return await capInstance.createChallenge({ - challengeSize: keyData.challengeSize, - challengeDifficulty: keyData.challengeDifficulty, - challengeCount: keyData.challengesCount, - expiresMs: keyData.expiresMs, - }); - }) - .post("/api/redeem", async ({ body, set, capInstance }) => { - const { token, solutions } = body; - - if (!token || !solutions) { - set.status = 400; - return { success: false, message: "Missing solutions and/or token" }; - } - - return await capInstance.redeemChallenge({ token, solutions }); - }) - .post("/siteverify", async ({ body, set, keyData, capInstance }) => { - const { secret, response } = body; - - if (!secret || !response) { - set.status = 400; - return { success: false, message: "Missing secret or/and response" }; - } - - if (!(await Bun.password.verify(secret, keyData.privateKey))) { - set.status = 400; - return { success: false, message: "Invalid secret" }; - } - - return await capInstance.validateToken(response, { - keepToken: false, - }); - }); - -const assetsServer = new Elysia({ prefix: "/assets" }) - .use( - cors({ - origin: process.env.CORS_ORIGIN?.split(",") || true, - methods: ["GET"], - }), - ) - .onBeforeHandle(({ set }) => { - set.headers["Cache-Control"] = "max-age=31536000, immutable"; - }) - .get("/widget.js", ({ set }) => { - set.headers["Content-Type"] = "text/javascript"; - return file(path.join(dataDir, "assets-widget.js")); - }) - .get("/floating.js", ({ set }) => { - set.headers["Content-Type"] = "text/javascript"; - return file(path.join(dataDir, "assets-floating.js")); - }) - .get("/cap_wasm_bg.wasm", ({ set }) => { - set.headers["Content-Type"] = "application/wasm"; - return file(path.join(dataDir, "assets-cap_wasm_bg.wasm")); - }) - .get("/cap_wasm.js", ({ set }) => { - set.headers["Content-Type"] = "text/javascript"; - return file(path.join(dataDir, "assets-cap_wasm.js")); - }); - -new Elysia() - .use(staticPlugin()) - .use(auth) - .use(internal) - .use(api) - .use(assetsServer) - .get("/", async ({ cookie }) => { - const authCookie = cookie["cap-admin-key"]?.value; - const isAuthed = - authCookie && (await Bun.password.verify(ADMIN_KEY, authCookie)); - - return file(isAuthed ? "./public/index.html" : "./public/lock.html"); - }) - .listen(3000); - -console.log(`Cap standalone running on http://localhost:3000`); -init(); diff --git a/checkpoints/elysia/bun.lock b/checkpoints/elysia/bun.lock deleted file mode 100644 index b98c427..0000000 --- a/checkpoints/elysia/bun.lock +++ /dev/null @@ -1,55 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@cap.js/middleware-elysia", - "dependencies": { - "@cap.js/server": "^1.0.15", - "elysia": "^1.3.3", - }, - }, - }, - "packages": { - "@cap.js/server": ["@cap.js/server@1.0.15", "", { "dependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.3" } }, "sha512-mcDbHAL4ar5O4gnDE+62ZPeXhLXxd1KKHzJCsZfn81HY8Fgo5ousxYCnzhsLfki5sH6sO6jpNtS4MZXlKDq+fA=="], - - "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], - - "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/node": ["@types/node@22.15.24", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng=="], - - "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], - - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - - "elysia": ["elysia@1.3.3", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-x6a89d4h0xX9TB0CSGkUuKNqIK776HBJw0WHtK1B3ViQqZijsLnOor6be/7TwVl8MG6m59NHGHbmbBS6lnCXSw=="], - - "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], - - "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], - - "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], - - "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], - - "peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="], - - "strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="], - - "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - } -} diff --git a/checkpoints/elysia/index.d.ts b/checkpoints/elysia/index.d.ts deleted file mode 100644 index 968e634..0000000 --- a/checkpoints/elysia/index.d.ts +++ /dev/null @@ -1,59 +0,0 @@ -import Cap from "@cap.js/server"; -import type { Elysia } from "elysia"; - -export interface CapMiddlewareOptions { - /** - * Duration in hours for which the authentication token remains valid - * @default 32 - */ - token_validity_hours?: number; - - /** - * File path where authentication tokens are stored - * @default ".data/middlewareTokens.json" - */ - tokens_store_path?: string; - - /** - * Size of the generated token in bytes - * @default 16 - */ - token_size?: number; - - /** - * Path to the HTML template used for verification - * @default "./index.html" (relative to middleware file) - */ - verification_template_path?: string; - - /** - * Determines how the middleware is applied - * @default "global" - */ - scoping?: "global" | "scoped"; -} - -export interface TokenValidationResult { - success: boolean; -} - -export interface ChallengeRedeemBody { - token: string; - solutions: unknown[]; -} - -export interface ChallengeRedeemResponse { - success: boolean; - token?: string; - expires?: number; -} - -/** - * Cap middleware for Elysia that provides challenge-based bot protection - * - * @param userOptions Configuration options for the middleware - * @returns An Elysia plugin instance configured with Cap protection - */ -export declare function capMiddleware( - userOptions?: CapMiddlewareOptions -): Elysia; diff --git a/checkpoints/elysia/index.html b/checkpoints/elysia/index.html deleted file mode 100644 index f83b42f..0000000 --- a/checkpoints/elysia/index.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - Verifying you are a human… - - - - - - - - - - - - - -

Checking your browser...

- -

Verifying you are a human before proceeding...

- - - - - -
-
- -

Why am I seeing this page?

-

- To keep our site secure, we need to confirm you're a human and not a - robot. This quick check helps stop spam and abuse. -

- -

What should I do?

-

- No action is required on your end. Once verified, you'll continue to - your destination. If you're stuck, try refreshing the page or checking - your connection. -

- -
-
- - - - - - diff --git a/checkpoints/elysia/index.js b/checkpoints/elysia/index.js deleted file mode 100644 index 0aabe2a..0000000 --- a/checkpoints/elysia/index.js +++ /dev/null @@ -1,165 +0,0 @@ -import crypto from "node:crypto"; -import fs from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import Cap from "@cap.js/server"; -import Elysia from "elysia"; - -export const capMiddleware = (userOptions) => { - const options = { - token_validity_hours: 32, - tokens_store_path: ".data/middlewareTokens.json", - token_size: 16, // token size in bytes - verification_template_path: join( - dirname(fileURLToPath(import.meta.url)), - "./index.html", - ), - scoping: "global", // 'global' | 'scoped' - - ...userOptions, - }; - - const cap = new Cap({ - noFSState: true, - }); - - let tokensCache = null; - let cacheLastModified = 0; - - fs.mkdir(dirname(options.tokens_store_path), { recursive: true }); - - async function loadCustomTokens() { - try { - const stats = await fs.stat(options.tokens_store_path); - const fileModified = stats.mtime.getTime(); - - if (tokensCache && fileModified <= cacheLastModified) { - return tokensCache; - } - - const fileContent = await fs.readFile(options.tokens_store_path, "utf-8"); - tokensCache = JSON.parse(fileContent); - cacheLastModified = Date.now(); - return tokensCache; - } catch { - tokensCache = {}; - cacheLastModified = Date.now(); - return tokensCache; - } - } - - async function saveCustomTokens(tokens) { - await fs.writeFile(options.tokens_store_path, JSON.stringify(tokens)); - tokensCache = tokens; - cacheLastModified = Date.now(); - } - - async function storeCustomToken(token) { - const tokens = await loadCustomTokens(); - - tokens[token] = Date.now() + options.token_validity_hours * 60 * 60 * 1000; - await saveCustomTokens(tokens); - return token; - } - - async function validateCustomToken(token) { - const tokens = await loadCustomTokens(); - const tokenData = tokens[token]; - - if (!tokenData) return { success: false }; - - if (Date.now() > tokenData) { - delete tokens[token]; - await saveCustomTokens(tokens); - return { success: false }; - } - - return { success: true }; - } - - async function cleanupExpiredTokens() { - const tokens = await loadCustomTokens(); - const now = Date.now(); - let hasChanges = false; - - for (const [token, data] of Object.entries(tokens)) { - if (now > data) { - delete tokens[token]; - hasChanges = true; - } - } - - if (hasChanges) { - await saveCustomTokens(tokens); - } - } - - const plugin = new Elysia({ - name: "@cap.js/middleware-elysia", - seed: options, - }); - - plugin.onBeforeHandle({ as: options.scoping }, async ({ path, cookie }) => { - if (path === "/__cap_clearance/redeem") { - return; - } - - const customToken = cookie["__cap_clearance"]?.value; - if (customToken) { - const validation = await validateCustomToken(customToken); - if (validation.success) { - return; - } - } - - const challenge = cap.createChallenge(); - - return new Response( - (await fs.readFile(options.verification_template_path, "utf-8")) - .replace("window.CAP_CHALLENGE", JSON.stringify(challenge)) - .replace("window.TOKEN_VALIDITY_HOURS", options.token_validity_hours) - .replace( - "{{TIME}}", - new Date() - .toISOString() - .replace("T", " ") - .replace(/\.\d+Z$/, "Z"), - ), - { - headers: { "Content-Type": "text/html" }, - }, - ); - }); - - plugin.post("/__cap_clearance/redeem", async ({ body, set }) => { - const { token, solutions } = body; - - if (!token || !solutions) { - set.status = 400; - return { success: false }; - } - - const challengeResult = await cap.redeemChallenge({ token, solutions }); - - cap.validateToken(challengeResult.token); - - if (challengeResult.success) { - const customToken = crypto - .randomBytes(options.token_size) - .toString("hex"); - await storeCustomToken(customToken); - - await cleanupExpiredTokens(); - - return { - success: true, - token: customToken, - expires: challengeResult.expires, - }; - } - - return challengeResult; - }); - - return plugin; -}; diff --git a/checkpoints/elysia/package.json b/checkpoints/elysia/package.json deleted file mode 100644 index e426618..0000000 --- a/checkpoints/elysia/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@cap.js/middleware-elysia", - "version": "0.0.3", - "description": "Elysia Cloudflare-like middleware for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tiagozip/cap.git" - }, - "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", - "anti-abuse", - "anti-automation", - "anti-bot", - "anti-ddos", - "anti-dos", - "anti-exploitation", - "anti-spam", - "anti-scraping", - "attack-mitigation", - "protocol", - "client-server", - "computational-puzzle", - "complexity", - "puzzle", - "crypto", - "cryptographic", - "algorithm", - "cybersecurity", - "ddos", - "hashcash", - "hcaptcha", - "captcha-alternative", - "verification", - "invisible", - "pow", - "proof-of-work", - "recaptcha", - "spam", - "bots", - "filtering", - "turing-test" - ], - "author": "Tiago", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/tiagozip/cap/issues" - }, - "homepage": "https://github.com/tiagozip/cap#readme", - "dependencies": { - "@cap.js/server": "^1.0.15", - "elysia": "^1.3.3" - } -} diff --git a/checkpoints/express/bun.lock b/checkpoints/express/bun.lock deleted file mode 100644 index 0907392..0000000 --- a/checkpoints/express/bun.lock +++ /dev/null @@ -1,20 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@cap.js/checkpoint-hono", - "dependencies": { - "@cap.js/server": "^1.0.15", - }, - }, - }, - "packages": { - "@cap.js/server": ["@cap.js/server@1.0.15", "", { "dependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.3" } }, "sha512-mcDbHAL4ar5O4gnDE+62ZPeXhLXxd1KKHzJCsZfn81HY8Fgo5ousxYCnzhsLfki5sH6sO6jpNtS4MZXlKDq+fA=="], - - "@types/node": ["@types/node@22.15.28", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-I0okKVDmyKR281I0UIFV7EWAWRnR0gkuSKob5wVcByyyhr7Px/slhkQapcYX4u00ekzNWaS1gznKZnuzxwo4pw=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - } -} diff --git a/checkpoints/express/index.html b/checkpoints/express/index.html deleted file mode 100644 index f83b42f..0000000 --- a/checkpoints/express/index.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - Verifying you are a human… - - - - - - - - - - - - - -

Checking your browser...

- -

Verifying you are a human before proceeding...

- - - - - -
-
- -

Why am I seeing this page?

-

- To keep our site secure, we need to confirm you're a human and not a - robot. This quick check helps stop spam and abuse. -

- -

What should I do?

-

- No action is required on your end. Once verified, you'll continue to - your destination. If you're stuck, try refreshing the page or checking - your connection. -

- -
-
- - - - - - diff --git a/checkpoints/express/index.js b/checkpoints/express/index.js deleted file mode 100644 index 9589b6c..0000000 --- a/checkpoints/express/index.js +++ /dev/null @@ -1,159 +0,0 @@ -import crypto from "node:crypto"; -import fs from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import Cap from "@cap.js/server"; - -export const capCheckpoint = (userOptions) => { - const options = { - token_validity_hours: 32, - tokens_store_path: ".data/middlewareTokens.json", - token_size: 16, - verification_template_path: join( - dirname(fileURLToPath(import.meta.url)), - "./index.html", - ), - ...userOptions, - }; - - const cap = new Cap({ - noFSState: true, - }); - - let tokensCache = null; - let cacheLastModified = 0; - - fs.mkdir(dirname(options.tokens_store_path), { recursive: true }); - - async function loadCustomTokens() { - try { - const stats = await fs.stat(options.tokens_store_path); - - if (tokensCache && stats.mtime.getTime() <= cacheLastModified) { - return tokensCache; - } - - tokensCache = JSON.parse( - await fs.readFile(options.tokens_store_path, "utf-8"), - ); - cacheLastModified = Date.now(); - - return tokensCache; - } catch { - tokensCache = {}; - cacheLastModified = Date.now(); - - return tokensCache; - } - } - - async function saveCustomTokens(tokens) { - await fs.writeFile(options.tokens_store_path, JSON.stringify(tokens)); - tokensCache = tokens; - cacheLastModified = Date.now(); - } - - async function storeCustomToken(token) { - const tokens = await loadCustomTokens(); - - tokens[token] = Date.now() + options.token_validity_hours * 60 * 60 * 1000; - await saveCustomTokens(tokens); - return token; - } - - async function validateCustomToken(token) { - const tokens = await loadCustomTokens(); - const tokenData = tokens[token]; - - if (!tokenData) return { success: false }; - - if (Date.now() > tokenData) { - delete tokens[token]; - await saveCustomTokens(tokens); - return { success: false }; - } - - return { success: true }; - } - - async function cleanupExpiredTokens() { - const tokens = await loadCustomTokens(); - const now = Date.now(); - let hasChanges = false; - - for (const [token, data] of Object.entries(tokens)) { - if (now > data) { - delete tokens[token]; - hasChanges = true; - } - } - - if (hasChanges) { - await saveCustomTokens(tokens); - } - } - - return async (req, res, next) => { - const { path, method } = req; - - if (method === "POST" && path === "/__cap_clearance/redeem") { - const { token, solutions } = req.body; - - if (!token || !solutions) { - return res.status(400).json({ success: false }); - } - - const challengeResult = await cap.redeemChallenge({ token, solutions }); - - cap.validateToken(challengeResult.token); - - if (challengeResult.success) { - const customToken = crypto - .randomBytes(options.token_size) - .toString("hex"); - await storeCustomToken(customToken); - - await cleanupExpiredTokens(); - - res.cookie("__cap_clearance", customToken, { - maxAge: options.token_validity_hours * 60 * 60 * 1000, - sameSite: "Strict", - }); - - return res.json({ - success: true, - token: customToken, - expires: challengeResult.expires, - }); - } - - return res.json(challengeResult); - } - - const customToken = req.cookies?.__cap_clearance; - if (customToken) { - const validation = await validateCustomToken(customToken); - if (validation.success) { - return next(); - } - } - - const challenge = cap.createChallenge(); - - const html = ( - await fs.readFile(options.verification_template_path, "utf-8") - ) - .replace("window.CAP_CHALLENGE", JSON.stringify(challenge)) - .replace("window.TOKEN_VALIDITY_HOURS", options.token_validity_hours) - .replace( - "{{TIME}}", - new Date() - .toISOString() - .replace("T", " ") - .replace(/\.\d+Z$/, "Z"), - ); - - res.setHeader("Content-Type", "text/html"); - res.send(html); - }; -}; diff --git a/checkpoints/express/package.json b/checkpoints/express/package.json deleted file mode 100644 index 06185c6..0000000 --- a/checkpoints/express/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "@cap.js/checkpoint-express", - "version": "0.0.2", - "description": "Express Cloudflare-like checkpoint for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tiagozip/cap.git" - }, - "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", - "anti-abuse", - "anti-automation", - "anti-bot", - "anti-ddos", - "anti-dos", - "anti-exploitation", - "anti-spam", - "anti-scraping", - "attack-mitigation", - "protocol", - "client-server", - "computational-puzzle", - "complexity", - "puzzle", - "crypto", - "cryptographic", - "algorithm", - "cybersecurity", - "ddos", - "hashcash", - "hcaptcha", - "captcha-alternative", - "verification", - "invisible", - "pow", - "proof-of-work", - "recaptcha", - "spam", - "bots", - "filtering", - "turing-test" - ], - "author": "Tiago", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/tiagozip/cap/issues" - }, - "homepage": "https://github.com/tiagozip/cap#readme", - "dependencies": { - "@cap.js/server": "^1.0.15" - } -} diff --git a/checkpoints/hono/bun.lock b/checkpoints/hono/bun.lock deleted file mode 100644 index a90379a..0000000 --- a/checkpoints/hono/bun.lock +++ /dev/null @@ -1,23 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@cap.js/checkpoint-hono", - "dependencies": { - "@cap.js/server": "^1.0.15", - "hono": "^4.7.10", - }, - }, - }, - "packages": { - "@cap.js/server": ["@cap.js/server@1.0.15", "", { "dependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.3" } }, "sha512-mcDbHAL4ar5O4gnDE+62ZPeXhLXxd1KKHzJCsZfn81HY8Fgo5ousxYCnzhsLfki5sH6sO6jpNtS4MZXlKDq+fA=="], - - "@types/node": ["@types/node@22.15.28", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-I0okKVDmyKR281I0UIFV7EWAWRnR0gkuSKob5wVcByyyhr7Px/slhkQapcYX4u00ekzNWaS1gznKZnuzxwo4pw=="], - - "hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - } -} diff --git a/checkpoints/hono/index.html b/checkpoints/hono/index.html deleted file mode 100644 index f83b42f..0000000 --- a/checkpoints/hono/index.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - Verifying you are a human… - - - - - - - - - - - - - -

Checking your browser...

- -

Verifying you are a human before proceeding...

- - - - - -
-
- -

Why am I seeing this page?

-

- To keep our site secure, we need to confirm you're a human and not a - robot. This quick check helps stop spam and abuse. -

- -

What should I do?

-

- No action is required on your end. Once verified, you'll continue to - your destination. If you're stuck, try refreshing the page or checking - your connection. -

- -
-
- - - - - - diff --git a/checkpoints/hono/index.js b/checkpoints/hono/index.js deleted file mode 100644 index 063816f..0000000 --- a/checkpoints/hono/index.js +++ /dev/null @@ -1,164 +0,0 @@ -import crypto from "node:crypto"; -import fs from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import Cap from "@cap.js/server"; -import { getCookie, setCookie } from "hono/cookie"; -import { createMiddleware } from "hono/factory"; - -export const capCheckpoint = (userOptions) => { - const options = { - token_validity_hours: 32, - tokens_store_path: ".data/middlewareTokens.json", - token_size: 16, // token size in bytes - verification_template_path: join( - dirname(fileURLToPath(import.meta.url)), - "./index.html", - ), - - ...userOptions, - }; - - const cap = new Cap({ - noFSState: true, - }); - - let tokensCache = null; - let cacheLastModified = 0; - - fs.mkdir(dirname(options.tokens_store_path), { recursive: true }); - - async function loadCustomTokens() { - try { - const stats = await fs.stat(options.tokens_store_path); - - if (tokensCache && stats.mtime.getTime() <= cacheLastModified) { - return tokensCache; - } - - tokensCache = JSON.parse( - await fs.readFile(options.tokens_store_path, "utf-8"), - ); - cacheLastModified = Date.now(); - - return tokensCache; - } catch { - tokensCache = {}; - cacheLastModified = Date.now(); - - return tokensCache; - } - } - - async function saveCustomTokens(tokens) { - await fs.writeFile(options.tokens_store_path, JSON.stringify(tokens)); - tokensCache = tokens; - cacheLastModified = Date.now(); - } - - async function storeCustomToken(token) { - const tokens = await loadCustomTokens(); - - tokens[token] = Date.now() + options.token_validity_hours * 60 * 60 * 1000; - await saveCustomTokens(tokens); - return token; - } - - async function validateCustomToken(token) { - const tokens = await loadCustomTokens(); - const tokenData = tokens[token]; - - if (!tokenData) return { success: false }; - - if (Date.now() > tokenData) { - delete tokens[token]; - await saveCustomTokens(tokens); - return { success: false }; - } - - return { success: true }; - } - - async function cleanupExpiredTokens() { - const tokens = await loadCustomTokens(); - const now = Date.now(); - let hasChanges = false; - - for (const [token, data] of Object.entries(tokens)) { - if (now > data) { - delete tokens[token]; - hasChanges = true; - } - } - - if (hasChanges) { - await saveCustomTokens(tokens); - } - } - - const middleware = createMiddleware(async (c, next) => { - const { path, method } = c.req; - - if (method === "POST" && path === "/__cap_clearance/redeem") { - const body = await c.req.json(); - const { token, solutions } = body; - - if (!token || !solutions) { - return c.json({ success: false }, 400); - } - - const challengeResult = await cap.redeemChallenge({ token, solutions }); - - cap.validateToken(challengeResult.token); - - if (challengeResult.success) { - const customToken = crypto - .randomBytes(options.token_size) - .toString("hex"); - await storeCustomToken(customToken); - - await cleanupExpiredTokens(); - - setCookie(c, "__cap_clearance", customToken, { - maxAge: options.token_validity_hours * 60 * 60, - sameSite: "Strict", - }); - - return c.json({ - success: true, - token: customToken, - expires: challengeResult.expires, - }); - } - - return c.json(challengeResult); - } - - const customToken = getCookie(c, "__cap_clearance"); - if (customToken) { - const validation = await validateCustomToken(customToken); - if (validation.success) { - return await next(); - } - } - - const challenge = cap.createChallenge(); - - const html = ( - await fs.readFile(options.verification_template_path, "utf-8") - ) - .replace("window.CAP_CHALLENGE", JSON.stringify(challenge)) - .replace("window.TOKEN_VALIDITY_HOURS", options.token_validity_hours) - .replace( - "{{TIME}}", - new Date() - .toISOString() - .replace("T", " ") - .replace(/\.\d+Z$/, "Z"), - ); - - return c.html(html); - }); - - return middleware; -}; diff --git a/checkpoints/hono/package.json b/checkpoints/hono/package.json deleted file mode 100644 index 664010b..0000000 --- a/checkpoints/hono/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "@cap.js/checkpoint-hono", - "version": "0.0.2", - "description": "Hono Cloudflare-like checkpoint for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tiagozip/cap.git" - }, - "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", - "anti-abuse", - "anti-automation", - "anti-bot", - "anti-ddos", - "anti-dos", - "anti-exploitation", - "anti-spam", - "anti-scraping", - "attack-mitigation", - "protocol", - "client-server", - "computational-puzzle", - "complexity", - "puzzle", - "crypto", - "cryptographic", - "algorithm", - "cybersecurity", - "ddos", - "hashcash", - "hcaptcha", - "captcha-alternative", - "verification", - "invisible", - "pow", - "proof-of-work", - "recaptcha", - "spam", - "bots", - "filtering", - "turing-test" - ], - "author": "Tiago", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/tiagozip/cap/issues" - }, - "homepage": "https://github.com/tiagozip/cap#readme", - "dependencies": { - "@cap.js/server": "^1.0.15", - "hono": "^4.7.10" - } -} diff --git a/checkpoints/move_template.js b/checkpoints/move_template.js deleted file mode 100644 index d73f7c7..0000000 --- a/checkpoints/move_template.js +++ /dev/null @@ -1,20 +0,0 @@ -import fs from "node:fs/promises"; - -const template = await fs.readFile("./template.html", "utf-8"); - -const folders = (await fs.readdir("./", { withFileTypes: true })) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name); - -console.log( - `build command:\n${folders - .map((folder) => `cd ${folder} && bun publish --access public && cd ..`) - .join("\n")}\n`, -); - -folders.forEach(async (folder) => { - const path = `./${folder}/index.html`; - await fs.writeFile(path, template); - - console.log(`wrote ${path}`); -}); diff --git a/checkpoints/template.html b/checkpoints/template.html deleted file mode 100644 index f83b42f..0000000 --- a/checkpoints/template.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - Verifying you are a human… - - - - - - - - - - - - - -

Checking your browser...

- -

Verifying you are a human before proceeding...

- - - - - -
-
- -

Why am I seeing this page?

-

- To keep our site secure, we need to confirm you're a human and not a - robot. This quick check helps stop spam and abuse. -

- -

What should I do?

-

- No action is required on your end. Once verified, you'll continue to - your destination. If you're stuck, try refreshing the page or checking - your connection. -

- -
-
- - - - - - diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index e195dae..dc064c6 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -51,10 +51,7 @@ export default withMermaid({ }, ], ["meta", { property: "og:url", content: "https://capjs.js.org" }], - [ - "meta", - { property: "og:image", content: "https://capjs.js.org/logo.png" }, - ], + ["meta", { property: "og:image", content: "https://capjs.js.org/logo.png" }], ["meta", { name: "twitter:card", content: "summary_large_image" }], [ "meta", @@ -71,10 +68,7 @@ export default withMermaid({ "Lightweight, privacy-first, and designed to put you first. Switch from reCAPTCHA in minutes.", }, ], - [ - "meta", - { name: "twitter:image", content: "https://capjs.js.org/logo.png" }, - ], + ["meta", { name: "twitter:image", content: "https://capjs.js.org/logo.png" }], [ "meta", { @@ -206,8 +200,7 @@ export default withMermaid({ footer: { message: "Built in Europe 🇪🇺
Released under the Apache 2.0 License.", - copyright: - "Copyright © 2025-present Tiago", + copyright: "Copyright © 2025-present Tiago", }, }, markdown: { diff --git a/docs/guide/community.md b/docs/guide/community.md index 728b36a..c193b3a 100644 --- a/docs/guide/community.md +++ b/docs/guide/community.md @@ -2,6 +2,8 @@ Want to use Cap without the standalone server and with a different language? Here are some community-maintained libraries that might help. If you want to add a library, feel free to open a pull request! +**Note:** These libraries frequently do **not** support new features such as seeded challenges or instrumentation challenges. + ## Widgets These are wrappers around Cap's widget. They're usually not required as the default widget should work everywhere but can result in better development experience. diff --git a/docs/guide/index.md b/docs/guide/index.md index b9a6db8..addcc56 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -4,7 +4,7 @@ outline: deep # Quickstart -Cap is a modern, lightweight, and self-hosted CAPTCHA alternative using SHA-256 proof-of-work. +Cap is a modern, lightweight, and self-hosted CAPTCHA alternative using SHA-256 proof-of-work and instrumentation challenges. Unlike traditional CAPTCHAs, Cap's fast, unobtrusive, has no telemetry or tracking, and uses accessible proof-of-work instead of annoying visual puzzles. @@ -32,7 +32,6 @@ Using `cdn.jsdelivr.net` is optional. If preferred, you can self-host the script ::: - Next, you can either add the widget component directly to your code: ```html @@ -59,12 +58,12 @@ You can also use get a token programmatically without displaying the widget by u ```js const cap = new Cap({ - apiEndpoint: "/api/" + apiEndpoint: "/api/", }); const solution = await cap.solve(); // you can attach event listeners to track progress -cap.addEventListener("progress", (event) => { +cap.addEventListener("progress", (event) => { console.log(`Solving... ${event.detail.progress}% done`); }); @@ -75,6 +74,6 @@ console.log(solution.token); Cap is fully self-hosted, so you'll need to start a server exposing an API for Cap's protocol. -For most use cases, we recommend using the Docker **[Standalone server](./standalone/index.md)**, as it exposes a simple HTTP API and provides a simple web dashboard with analytics. +For most use cases, **we recommend using the Docker [Standalone server](./standalone/index.md)**, as it exposes a simple HTTP API and provides a simple web dashboard with analytics. It also supports our JavaScript instrumentation challenges, which force bots to use real browsers, along with optional headless browser detection. -However, if you can't use Docker or need a more lightweight solution, you can use the [server library](./server.md) on Node and Bun or a [community library](./community.md) in other languages. \ No newline at end of file +However, if you can't use Docker or need a more lightweight solution, you can use the [server library](./server.md) on Node and Bun or a [community library](./community.md) in other languages. diff --git a/docs/guide/solver.md b/docs/guide/solver.md index 2cd9f2f..239e458 100644 --- a/docs/guide/solver.md +++ b/docs/guide/solver.md @@ -1,8 +1,8 @@ # M2M -`@cap.js/solver` is a standalone library that can be used to solve Cap challenges from the server. It's extremely simple (no dependencies, one single file) yet as fast and efficient as the widget. Note that it can **only be used with Bun**. Node.js support is not planned. +`@cap.js/solver` is a standalone library that can be used to solve Cap challenges from the server. It's extremely simple (no dependencies, one single file) yet as fast and efficient as the widget. Note that it can **only be used with Bun**. -This package does not bypass any actual proof-of-work. +This package does not bypass any actual proof-of-work. **It does not support instrumentation challenges.** ## Installation @@ -22,7 +22,7 @@ console.log( c: 50, // challenge count s: 32, // salt size d: 4, // difficulty - }) + }), ); ``` @@ -55,4 +55,4 @@ The 2nd argument is optional but can always be provided. It's always an object. - For **all challenge types**, `onProgress` can also be used to provide a callback for progress updates. -- For **seeded challenges only**, it is used to specify the number of solutions to generate, the size of the challenges, and the difficulty \ No newline at end of file +- For **seeded challenges only**, it is used to specify the number of solutions to generate, the size of the challenges, and the difficulty diff --git a/docs/guide/standalone/index.md b/docs/guide/standalone/index.md index 61783bc..f8ab441 100644 --- a/docs/guide/standalone/index.md +++ b/docs/guide/standalone/index.md @@ -1,6 +1,6 @@ # Cap Standalone -Cap Standalone is the default way of self-hosting Cap's backend. It provides a simple HTTP API for the widget to use and a siteverify endpoint compatible with hCaptcha or reCAPTCHA's, along with the ability to use multiple site keys. +Cap Standalone is the default way of self-hosting Cap's backend along with instrumentation challenges. It provides an API for the widget and a siteverify endpoint compatible with hCaptcha or reCAPTCHA's, along with the ability to use multiple site keys. ## Installation diff --git a/docs/guide/standalone/options.md b/docs/guide/standalone/options.md index 33a9315..43b5106 100644 --- a/docs/guide/standalone/options.md +++ b/docs/guide/standalone/options.md @@ -62,4 +62,10 @@ We recommend keeping this to the default value, which is `sqlite://./.data/db.sq ## Error messages -Error messages are redacted by default and instead logged to the console. To disable error logging, set `DISABLE_ERROR_LOGGING=true`. To disable error message redaction, set `SHOW_ERRORS=true`. \ No newline at end of file +Error messages are redacted by default and instead logged to the console. To disable error logging, set `DISABLE_ERROR_LOGGING=true`. To disable error message redaction, set `SHOW_ERRORS=true`. + +## Instrumentation challenges + +Cap standalone supports our JavaScript instrumentation challenges to defeat proof-of-work solvers, along with options to try stop headless browsers from solving them. + +You can turn on instrumentation challenges by toggling them on in your site key config. To block headless browsers, turn on "Attempt to block headless browsers" \ No newline at end of file diff --git a/docs/guide/troubleshooting/instrumentation.md b/docs/guide/troubleshooting/instrumentation.md new file mode 100644 index 0000000..a4e7f55 --- /dev/null +++ b/docs/guide/troubleshooting/instrumentation.md @@ -0,0 +1,67 @@ +--- +sidebar: false +editLink: false +prev: false +next: false +footer: false +--- + +# Troubleshooting + +This verification can fail for various reasons, not solely due to bot activity. If you're having trouble completing verification, follow these steps to resolve the issue. + +## 1. Try incognito or private mode + +Open your browser's incognito or private mode to rule out issues caused by extensions or cached data. + +- **Chrome / Edge:** Press `Ctrl+Shift+N` (Windows) or `Cmd+Shift+N` (Mac) +- **Firefox:** Press `Ctrl+Shift+P` (Windows) or `Cmd+Shift+P` (Mac) +- **Safari:** Go to **File → New Private Window** + +## 2. Disable your browser extensions + +Some extensions may interfere with the verification process. Try disabling them temporarily: + +1. Open your browser's extensions or add-ons settings +2. Temporarily disable **all** extensions +3. Reload the page and try again + +If this fixes the issue, re-enable extensions one by one to find the culprit. + +## 3. Try a different browser or device + +The issue may be specific to your current browser. Switch to another browser or device to test. + +- Try **Chrome**, **Firefox**, **Edge**, or **Safari** +- Note: **Internet Explorer is not supported** — use a modern browser instead +- If possible, try on a completely different device (e.g., your phone) + +## 4. Update your browser + +An outdated browser can cause verification to fail. + +1. Open your browser's menu +2. Go to **Help → About** (or similar) +3. Install any available updates and restart your browser + +## 5. Switch to a different network + +Your current network may have restrictions that interfere with verification. + +- Connect to a **different Wi-Fi network** +- Try using a **mobile hotspot** from your phone +- If you're on a corporate or school network, those often have strict filtering that can block verification + +## 6. Close any automated browser sessions + +If you're using a browser controlled by automation software (such as Selenium, Puppeteer, or Playwright), verification will be blocked. + +1. **Fully close** the automated browser session +2. Open the page in a **regular, manually-operated browser** +3. Complete the verification there + +AI agent browsers are also blocked — make sure you're using a standard browser. + +--- + +If you've tried all of the above and still can't get through, consider reaching out to the site owner for further assistance. You may also alternatively [file an issue](https://github.com/tiagozip/cap/issues). \ No newline at end of file diff --git a/docs/guide/widget.md b/docs/guide/widget.md index 1997566..0f6592d 100644 --- a/docs/guide/widget.md +++ b/docs/guide/widget.md @@ -34,6 +34,7 @@ import Cap from '@cap.js/widget'; ``` + ::: You can now use the `` component in your HTML. @@ -48,6 +49,7 @@ The following attributes are supported: - `data-cap-api-endpoint`: API endpoint (required if not using custom fetch) - `data-cap-worker-count`: Number of workers to use (defaults to `navigator.hardwareConcurrency || 8`) +- `data-cap-troubleshooting-url`: Custom URL for the "Troubleshooting" link shown when verification is blocked (defaults to `https://capjs.js.org/guide/troubleshooting.html`). See [Troubleshooting](/guide/troubleshooting.md) - `onsolve=""`: Event listener for the `solve` event - [i18n attributes](#i18n) @@ -66,13 +68,14 @@ widget.addEventListener("solve", function (e) { Alternatively, you can use `onsolve=""` directly within the widget or wrap the widget in a `
` (where Cap will automatically submit the token alongside other form data. for this, it'll create a hidden field with name set to its `data-cap-hidden-field-name` attribute or `cap-token`). ## Invisible mode + You can use `new Cap({ ... })` in your client-side JavaScript to create a new Cap instance and use the `solve()` method to solve the challenge. This is helpful for situations where you don't want the Cap widget to be visible but still want security, e.g. on a social media app when posting something. ```js -import Cap from '@cap.js/widget'; +import Cap from "@cap.js/widget"; const cap = new Cap({ - apiEndpoint: '/api/cap/' + apiEndpoint: "/api/cap/", }); const token = await cap.solve(); @@ -99,11 +102,12 @@ You can change the text on each label of the widget by setting the `data-cap-i18 data-cap-i18n-initial-state="I'm a human" data-cap-i18n-solved-label="I'm a human" data-cap-i18n-error-label="Error" + data-cap-i18n-troubleshooting-label="Troubleshooting" data-cap-i18n-wasm-disabled="Enable WASM for significantly faster solving" >
``` -`verify-aria-label`, `verifying-aria-label`, `verified-aria-label`, `error-aria-label` are also supported for screen readers. +`verify-aria-label`, `verifying-aria-label`, `verified-aria-label`, `error-aria-label` are also supported for screen readers. `troubleshooting-label` controls the text of the "Troubleshooting" link shown when a user is blocked by instrumentation (defaults to "Troubleshooting"). ## Customizing the widget @@ -142,8 +146,9 @@ cap-widget { --cap-checkbox-border-radius: 6px; --cap-checkbox-background: #fafafa91; --cap-checkbox-margin: 2px; - --cap-font: system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", - "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif; + --cap-font: + system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", + "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif; --cap-spinner-color: #000; --cap-spinner-background-color: #eee; --cap-spinner-thickness: 5px; @@ -156,4 +161,4 @@ cap-widget { ## Types -Cap's widget is fully typed. You can find the type definitions in the `cap.d.ts` file. \ No newline at end of file +Cap's widget is fully typed. You can find the type definitions in the `cap.d.ts` file. diff --git a/standalone/bun.lock b/standalone/bun.lock index e7baa78..a13c000 100644 --- a/standalone/bun.lock +++ b/standalone/bun.lock @@ -11,6 +11,7 @@ "@elysiajs/swagger": "^1.3.1", "elysia": "^1.4.12", "elysia-rate-limit": "4.4.0", + "javascript-obfuscator": "^4.1.1", }, "devDependencies": { "bun-types": "latest", @@ -28,6 +29,16 @@ "@elysiajs/swagger": ["@elysiajs/swagger@1.3.1", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-LcbLHa0zE6FJKWPWKsIC/f+62wbDv3aXydqcNPVPyqNcaUgwvCajIi+5kHEU6GO3oXUCpzKaMsb3gsjt8sLzFQ=="], + "@inversifyjs/common": ["@inversifyjs/common@1.3.3", "", {}, "sha512-ZH0wrgaJwIo3s9gMCDM2wZoxqrJ6gB97jWXncROfYdqZJv8f3EkqT57faZqN5OTeHWgtziQ6F6g3L8rCvGceCw=="], + + "@inversifyjs/core": ["@inversifyjs/core@1.3.4", "", { "dependencies": { "@inversifyjs/common": "1.3.3", "@inversifyjs/reflect-metadata-utils": "0.2.3" } }, "sha512-gCCmA4BdbHEFwvVZ2elWgHuXZWk6AOu/1frxsS+2fWhjEk2c/IhtypLo5ytSUie1BCiT6i9qnEo4bruBomQsAA=="], + + "@inversifyjs/reflect-metadata-utils": ["@inversifyjs/reflect-metadata-utils@0.2.3", "", { "peerDependencies": { "reflect-metadata": "0.2.2" } }, "sha512-d3D0o9TeSlvaGM2I24wcNw/Aj3rc4OYvHXOKDC09YEph5fMMiKd6fq1VTQd9tOkDNWvVbw+cnt45Wy9P/t5Lvw=="], + + "@javascript-obfuscator/escodegen": ["@javascript-obfuscator/escodegen@2.3.1", "", { "dependencies": { "@javascript-obfuscator/estraverse": "^5.3.0", "esprima": "^4.0.1", "esutils": "^2.0.2", "optionator": "^0.8.1" }, "optionalDependencies": { "source-map": "~0.6.1" } }, "sha512-Z0HEAVwwafOume+6LFXirAVZeuEMKWuPzpFbQhCEU9++BMz0IwEa9bmedJ+rMn/IlXRBID9j3gQ0XYAa6jM10g=="], + + "@javascript-obfuscator/estraverse": ["@javascript-obfuscator/estraverse@5.4.0", "", {}, "sha512-CZFX7UZVN9VopGbjTx4UXaXsi9ewoM1buL0kY7j1ftYdSs7p2spv9opxFjHlQ/QGTgh4UqufYqJJ0WKLml7b6w=="], + "@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="], "@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="], @@ -40,48 +51,252 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@types/minimatch": ["@types/minimatch@3.0.5", "", {}, "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="], + "@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="], "@types/react": ["@types/react@19.1.11", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ=="], + "@types/validator": ["@types/validator@13.15.10", "", {}, "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA=="], + "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "array-differ": ["array-differ@3.0.0", "", {}, "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "arrify": ["arrify@2.0.1", "", {}, "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="], + + "assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="], + + "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="], + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chance": ["chance@1.1.13", "", {}, "sha512-V6lQCljcLznE7tUYUM9EOAnnKXbctE6j/rdQkYOHIWbfGQbrzTsAXNW9CdU5XCo4ArXQCj/rb6HgxPlmGJcaUg=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + + "charenc": ["charenc@0.0.2", "", {}, "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="], + + "class-validator": ["class-validator@0.14.3", "", { "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", "validator": "^13.15.20" } }, "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "conf": ["conf@15.0.2", "", { "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "atomically": "^2.0.3", "debounce-fn": "^6.0.0", "dot-prop": "^10.0.0", "env-paths": "^3.0.0", "json-schema-typed": "^8.0.1", "semver": "^7.7.2", "uint8array-extras": "^1.5.0" } }, "sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw=="], + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + "crypt": ["crypt@0.0.2", "", {}, "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "debounce-fn": ["debounce-fn@6.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ=="], + "debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "dot-prop": ["dot-prop@10.1.0", "", { "dependencies": { "type-fest": "^5.0.0" } }, "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "elysia": ["elysia@1.4.12", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.2", "fast-decode-uri-component": "^1.0.1" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-wbd0BkrobsjWSloIfYeF3f7G7rR0UWMa6tuLUhf6ZvwjiCEX3FVfhDsM+KaqqRRxkZpPDw42q4yIZlBLyE32ww=="], "elysia-rate-limit": ["elysia-rate-limit@4.4.0", "", { "dependencies": { "@alloc/quick-lru": "5.2.0", "debug": "4.3.4" }, "peerDependencies": { "elysia": ">= 1.0.0" } }, "sha512-pyQdFEdjgf5ELx5CAEfOZ2IWhPaYv8WIQMrXimzHzslsJ9awDHoK6rcF9K7k/yAOh4qB1UhiasNeMMBGtxAwYQ=="], + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "exact-mirror": ["exact-mirror@0.2.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-CrGe+4QzHZlnrXZVlo/WbUZ4qQZq8C0uATQVGVgXIrNXgHDBBNFD1VRfssRA2C9t3RYvh3MadZSdg2Wy7HBoQA=="], "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], "file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="], + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "inversify": ["inversify@6.1.4", "", { "dependencies": { "@inversifyjs/common": "1.3.3", "@inversifyjs/core": "1.3.4" } }, "sha512-PbxrZH/gTa1fpPEEGAjJQzK8tKMIp5gRg6EFNJlCtzUcycuNdmhv3uk5P8Itm/RIjgHJO16oQRLo9IHzQN51bA=="], + + "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "javascript-obfuscator": ["javascript-obfuscator@4.2.2", "", { "dependencies": { "@javascript-obfuscator/escodegen": "2.3.1", "@javascript-obfuscator/estraverse": "5.4.0", "acorn": "8.15.0", "assert": "2.1.0", "chalk": "4.1.2", "chance": "1.1.13", "class-validator": "0.14.3", "commander": "12.1.0", "conf": "15.0.2", "eslint-scope": "8.4.0", "eslint-visitor-keys": "4.2.1", "fast-deep-equal": "3.1.3", "inversify": "6.1.4", "js-string-escape": "1.0.1", "md5": "2.3.0", "mkdirp": "3.0.1", "multimatch": "5.0.0", "process": "0.11.10", "reflect-metadata": "0.2.2", "source-map-support": "0.5.21", "string-template": "1.0.0", "stringz": "2.1.0", "tslib": "2.8.1" }, "bin": { "javascript-obfuscator": "bin/javascript-obfuscator" } }, "sha512-+7oXAUnFCA6vS0omIGHcWpSr67dUBIF7FKGYSXyzxShSLqM6LBgdugWKFl0XrYtGWyJMGfQR5F4LL85iCefkRA=="], + + "js-string-escape": ["js-string-escape@1.0.1", "", {}, "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + + "levn": ["levn@0.3.0", "", { "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA=="], + + "libphonenumber-js": ["libphonenumber-js@1.12.38", "", {}, "sha512-vwzxmasAy9hZigxtqTbFEwp8ZdZ975TiqVDwj5bKx5sR+zi5ucUQy9mbVTkKM9GzqdLdxux/hTw2nmN5J7POMA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "md5": ["md5@2.3.0", "", { "dependencies": { "charenc": "0.0.2", "crypt": "0.0.2", "is-buffer": "~1.1.6" } }, "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "multimatch": ["multimatch@5.0.0", "", { "dependencies": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", "array-union": "^2.1.0", "arrify": "^2.0.1", "minimatch": "^3.0.4" } }, "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA=="], + "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], + "object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "optionator": ["optionator@0.8.3", "", { "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "word-wrap": "~1.2.3" } }, "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA=="], + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "prelude-ls": ["prelude-ls@1.1.2", "", {}, "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "string-template": ["string-template@1.0.0", "", {}, "sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg=="], + + "stringz": ["stringz@2.1.0", "", { "dependencies": { "char-regex": "^1.0.2" } }, "sha512-KlywLT+MZ+v0IRepfMxRtnSvDCMc3nR1qqCs3m/qIbSOWkNZYT8XHQA31rS3TnKp0c5xjZu3M4GY/2aRKSi/6A=="], + "strtok3": ["strtok3@10.3.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw=="], + "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], + + "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + "token-types": ["token-types@6.0.3", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.3.2", "", { "dependencies": { "prelude-ls": "~1.1.2" } }, "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], @@ -90,6 +305,16 @@ "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], + + "validator": ["validator@13.15.26", "", {}, "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA=="], + + "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], + + "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], "zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="], @@ -100,6 +325,10 @@ "@tokenizer/inflate/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "conf/uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], + + "dot-prop/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="], + "@cap.js/server/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], diff --git a/standalone/package.json b/standalone/package.json index 648e1fd..dec97a9 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -1,59 +1,59 @@ { "name": "cap-standalone", - "version": "2.1.4", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch src/index.js", - "docker:publish": "docker buildx build --platform linux/amd64,linux/arm64 -t tiago2/cap:latest . --push" - }, + "version": "2.1.5", "keywords": [ - "security", - "detection", - "prevention", - "defense", - "protection", + "algorithm", "anti-abuse", "anti-automation", "anti-bot", "anti-ddos", "anti-dos", "anti-exploitation", - "anti-spam", "anti-scraping", + "anti-spam", "attack-mitigation", - "protocol", + "bots", + "captcha-alternative", "client-server", - "computational-puzzle", "complexity", - "puzzle", + "computational-puzzle", "crypto", "cryptographic", - "algorithm", "cybersecurity", "ddos", + "defense", + "detection", + "filtering", "hashcash", "hcaptcha", - "captcha-alternative", - "verification", "invisible", "pow", + "prevention", "proof-of-work", + "protection", + "protocol", "recaptcha", + "security", "spam", - "bots", - "filtering", - "turing-test" + "turing-test", + "verification" ], + "module": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "bun run --watch src/index.js", + "docker:publish": "docker buildx build --platform linux/amd64,linux/arm64 -t tiago2/cap:latest . --push" + }, "dependencies": { "@cap.js/server": "^4.0.3", "@elysiajs/cors": "^1.4.0", "@elysiajs/static": "^1.4.4", "@elysiajs/swagger": "^1.3.1", "elysia": "^1.4.12", - "elysia-rate-limit": "4.4.0" + "elysia-rate-limit": "4.4.0", + "javascript-obfuscator": "^4.1.1" }, "devDependencies": { "bun-types": "latest" - }, - "module": "src/index.js" + } } diff --git a/standalone/public/index.html b/standalone/public/index.html index dbac40e..87a59db 100644 --- a/standalone/public/index.html +++ b/standalone/public/index.html @@ -520,7 +520,7 @@ } .config-actions { - margin-top: 16px; + margin-top: 20px; display: flex; gap: 12px; } @@ -545,6 +545,7 @@ .save-btn:disabled { opacity: 0.5; cursor: not-allowed; + filter: grayscale(.3); } .danger-section { @@ -916,7 +917,7 @@ color: var(--ctp-text); font-weight: 500; } - + .about-logo { width: 80px; height: 80px; @@ -997,6 +998,75 @@ color: black; } + .switch-field { + display: flex; + gap: 12px; + grid-column: 1 / -1; + padding-top: 4px; + align-items: center; + } + + .switch { + position: relative; + width: 40px; + height: 22px; + flex-shrink: 0; + } + + .switch input { + opacity: 0; + width: 0; + height: 0; + position: absolute; + } + + .switch-track { + position: absolute; + inset: 0; + background: var(--ctp-surface1); + border-radius: 11px; + cursor: pointer; + transition: background 0.2s; + } + + .switch-track::after { + content: ""; + position: absolute; + top: 3px; + left: 3px; + width: 16px; + height: 16px; + background: var(--ctp-overlay0); + border-radius: 50%; + transition: + transform 0.2s, + background 0.2s; + } + + .switch input:checked + .switch-track { + background: var(--ctp-blue); + } + + .switch input:checked + .switch-track::after { + transform: translateX(18px); + background: var(--ctp-crust); + } + + .switch input:focus-visible + .switch-track { + box-shadow: 0 0 0 2px rgba(137, 180, 250, 0.3); + } + + .switch-label { + font-size: 13px; + font-weight: 500; + color: var(--ctp-subtext1); + cursor: pointer; + user-select: none; + display: flex; + gap: 4px; + flex-direction: column; + } + @keyframes fadeIn { from { opacity: 0; @@ -1045,12 +1115,7 @@