feat: seeded challenges and many other improvements!
BREAKING for server->widget. if you're using an old version of the widget with the new server, it will not work. old server versions with the new widget will work fine.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
import fs from "fs/promises";
|
||||
import { Elysia, file } from "elysia";
|
||||
import Cap from "@cap.js/server";
|
||||
import Cap from "../../server/index.js";
|
||||
|
||||
const cap = new Cap({
|
||||
tokens_store_path: ".data/tokensList.json",
|
||||
|
||||
Vendored
+15
-11
@@ -16,10 +16,14 @@ declare class Cap extends EventEmitter<[never]> {
|
||||
/**
|
||||
* Generates a new challenge
|
||||
* @param {ChallengeConfig} [conf] - Challenge configuration
|
||||
* @returns {{ challenge: Array<ChallengeTuple>, token?: string, expires: number }} Challenge data
|
||||
* @returns {{ challenge: {c: number, s: number, d: number}, token?: string, expires: number }} Challenge data
|
||||
*/
|
||||
createChallenge(conf?: ChallengeConfig): {
|
||||
challenge: Array<ChallengeTuple>;
|
||||
challenge: {
|
||||
c: number;
|
||||
s: number;
|
||||
d: number;
|
||||
};
|
||||
token?: string;
|
||||
expires: number;
|
||||
};
|
||||
@@ -77,17 +81,17 @@ type PathLike = import("fs").PathLike;
|
||||
type ChallengeTuple = [string, string];
|
||||
type ChallengeData = {
|
||||
/**
|
||||
* - Array of [salt, target] tuples
|
||||
* - Challenge configuration object
|
||||
*/
|
||||
challenge: Array<ChallengeTuple>;
|
||||
challenge: {
|
||||
c: number;
|
||||
s: number;
|
||||
d: number;
|
||||
};
|
||||
/**
|
||||
* - Expiration timestamp
|
||||
*/
|
||||
expires: number;
|
||||
/**
|
||||
* - Challenge token
|
||||
*/
|
||||
token: string;
|
||||
};
|
||||
type ChallengeState = {
|
||||
/**
|
||||
@@ -129,13 +133,13 @@ type TokenConfig = {
|
||||
};
|
||||
type Solution = {
|
||||
/**
|
||||
* - The challenge token
|
||||
* - Challenge token
|
||||
*/
|
||||
token: string;
|
||||
/**
|
||||
* - Array of [salt, target, solution] tuples
|
||||
* - Array of challenge solutions
|
||||
*/
|
||||
solutions: Array<[string, string, number]>;
|
||||
solutions: number[];
|
||||
};
|
||||
type CapConfig = {
|
||||
/**
|
||||
|
||||
+79
-35
@@ -14,9 +14,11 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChallengeData
|
||||
* @property {Array<ChallengeTuple>} challenge - Array of [salt, target] tuples
|
||||
* @property {Object} challenge - Challenge configuration object
|
||||
* @property {number} challenge.c - Number of challenges
|
||||
* @property {number} challenge.s - Size of each challenge
|
||||
* @property {number} challenge.d - Difficulty level
|
||||
* @property {number} expires - Expiration timestamp
|
||||
* @property {string} token - Challenge token
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -41,8 +43,8 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} Solution
|
||||
* @property {string} token - The challenge token
|
||||
* @property {Array<[string, string, number]>} solutions - Array of [salt, target, solution] tuples
|
||||
* @property {string} token - Challenge token
|
||||
* @property {number[]} solutions - Array of challenge solutions
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -60,6 +62,45 @@ const { EventEmitter } = require("events");
|
||||
|
||||
const DEFAULT_TOKENS_STORE = ".data/tokensList.json";
|
||||
|
||||
/**
|
||||
* Generates a deterministic hex string of given length from a string seed
|
||||
*
|
||||
* @param {string} seed - Initial seed value
|
||||
* @param {number} length - Output hex string length
|
||||
* @returns {string} Deterministic hex string generated from the seed
|
||||
*/
|
||||
function prng(seed, length) {
|
||||
/**
|
||||
* @param {string} str
|
||||
*/
|
||||
function fnv1a(str) {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash ^= str.charCodeAt(i);
|
||||
hash +=
|
||||
(hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
||||
}
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
let state = fnv1a(seed);
|
||||
let result = "";
|
||||
|
||||
function next() {
|
||||
state ^= state << 13;
|
||||
state ^= state >>> 17;
|
||||
state ^= state << 5;
|
||||
return state >>> 0;
|
||||
}
|
||||
|
||||
while (result.length < length) {
|
||||
const rnd = next();
|
||||
result += rnd.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
return result.substring(0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main Cap class
|
||||
* @extends EventEmitter
|
||||
@@ -107,43 +148,28 @@ class Cap extends EventEmitter {
|
||||
/**
|
||||
* Generates a new challenge
|
||||
* @param {ChallengeConfig} [conf] - Challenge configuration
|
||||
* @returns {{ challenge: Array<ChallengeTuple>, token?: string, expires: number }} Challenge data
|
||||
* @returns {{ challenge: {c: number, s: number, d: number}, token?: string, expires: number }} Challenge data
|
||||
*/
|
||||
createChallenge(conf) {
|
||||
this._cleanExpiredTokens();
|
||||
|
||||
/** @type {Array<ChallengeTuple>} */
|
||||
const challenges = Array.from(
|
||||
{ length: (conf && conf.challengeCount) || 50 },
|
||||
() =>
|
||||
/** @type {ChallengeTuple} */ ([
|
||||
crypto
|
||||
.randomBytes(Math.ceil(((conf && conf.challengeSize) || 32) / 2))
|
||||
.toString("hex")
|
||||
.slice(0, (conf && conf.challengeSize) || 32),
|
||||
crypto
|
||||
.randomBytes(
|
||||
Math.ceil(((conf && conf.challengeDifficulty) || 4) / 2)
|
||||
)
|
||||
.toString("hex")
|
||||
.slice(0, (conf && conf.challengeDifficulty) || 4),
|
||||
])
|
||||
);
|
||||
/** @type {{c: number, s: number, d: number}} */
|
||||
const challenge = {
|
||||
c: (conf && conf.challengeCount) || 50,
|
||||
s: (conf && conf.challengeSize) || 32,
|
||||
d: (conf && conf.challengeDifficulty) || 4,
|
||||
};
|
||||
|
||||
const token = crypto.randomBytes(25).toString("hex");
|
||||
const expires = Date.now() + ((conf && conf.expiresMs) || 600000);
|
||||
|
||||
if (conf && conf.store === false) {
|
||||
return { challenge: challenges, expires };
|
||||
return { challenge, expires };
|
||||
}
|
||||
|
||||
this.config.state.challengesList[token] = {
|
||||
challenge: challenges,
|
||||
expires,
|
||||
token,
|
||||
};
|
||||
this.config.state.challengesList[token] = { expires, challenge };
|
||||
|
||||
return { challenge: challenges, token, expires };
|
||||
return { challenge, token, expires };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +178,12 @@ class Cap extends EventEmitter {
|
||||
* @returns {Promise<{success: boolean, message?: string, token?: string, expires?: number}>}
|
||||
*/
|
||||
async redeemChallenge({ token, solutions }) {
|
||||
if (!token || !solutions) {
|
||||
if (
|
||||
!token ||
|
||||
!solutions ||
|
||||
!Array.isArray(solutions) ||
|
||||
solutions.some((s) => typeof s !== "number")
|
||||
) {
|
||||
return { success: false, message: "Invalid body" };
|
||||
}
|
||||
|
||||
@@ -166,13 +197,26 @@ class Cap extends EventEmitter {
|
||||
|
||||
delete this.config.state.challengesList[token];
|
||||
|
||||
const isValid = challengeData.challenge.every(([salt, target]) => {
|
||||
const solution = solutions.find(([s, t]) => s === salt && t === target);
|
||||
let i = 0;
|
||||
|
||||
const challenges = Array.from(
|
||||
{ length: challengeData.challenge.c },
|
||||
() => {
|
||||
i = i + 1;
|
||||
|
||||
return [
|
||||
prng(`${token}${i}`, challengeData.challenge.s),
|
||||
prng(`${token}${i}d`, challengeData.challenge.d),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
const isValid = challenges.every(([salt, target], i) => {
|
||||
return (
|
||||
solution &&
|
||||
solutions[i] &&
|
||||
crypto
|
||||
.createHash("sha256")
|
||||
.update(salt + solution[2])
|
||||
.update(salt + solutions[i])
|
||||
.digest("hex")
|
||||
.startsWith(target)
|
||||
);
|
||||
@@ -218,7 +262,7 @@ class Cap extends EventEmitter {
|
||||
if (!(conf && conf.keepToken)) {
|
||||
delete this.config.state.tokensList[key];
|
||||
}
|
||||
|
||||
|
||||
if (!this.config.noFSState) {
|
||||
await fs.writeFile(
|
||||
this.config.tokens_store_path,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cap.js/server",
|
||||
"version": "1.0.16",
|
||||
"version": "2.0.0",
|
||||
"author": "Tiago",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
+20
-18
@@ -2,14 +2,14 @@
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "app",
|
||||
"name": "cap-standalone",
|
||||
"dependencies": {
|
||||
"@cap.js/server": "^1.0.13",
|
||||
"@cap.js/server": "^1.0.16",
|
||||
"@elysiajs/cors": "^1.3.3",
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@elysiajs/swagger": "^1.3.0",
|
||||
"elysia": "^1.3.1",
|
||||
"elysia-rate-limit": "^4.3.0",
|
||||
"@elysiajs/swagger": "^1.3.1",
|
||||
"elysia": "^1.3.5",
|
||||
"elysia-rate-limit": "^4.4.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest",
|
||||
@@ -19,13 +19,13 @@
|
||||
"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=="],
|
||||
"@cap.js/server": ["@cap.js/server@1.0.16", "", { "dependencies": { "@types/node": "^22.14.1", "typescript": "^5.8.3" } }, "sha512-sUutpIiSdFwVkyHJWMQqdfz7DDD3vE/uT7pA80fV2yOWA/wQRlNkIGhQNsWBazwhkPhA2bFsLLIhPvp8MUv10w=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@elysiajs/swagger": ["@elysiajs/swagger@1.3.0", "", { "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-0fo3FWkDRPNYpowJvLz3jBHe9bFe6gruZUyf+feKvUEEMG9ZHptO1jolSoPE0ffFw1BgN1/wMsP19p4GRXKdfg=="],
|
||||
"@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=="],
|
||||
|
||||
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="],
|
||||
|
||||
@@ -33,17 +33,17 @@
|
||||
|
||||
"@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="],
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.37", "", {}, "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw=="],
|
||||
|
||||
"@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=="],
|
||||
"@types/node": ["@types/node@24.0.10", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA=="],
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
|
||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
|
||||
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
|
||||
"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": ["elysia@1.3.5", "", { "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-XVIKXlKFwUT7Sta8GY+wO5reD9I0rqAEtaz1Z71UgJb61csYt8Q3W9al8rtL5RgumuRR8e3DNdzlUN9GkC4KDw=="],
|
||||
|
||||
"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=="],
|
||||
"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=="],
|
||||
|
||||
"exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="],
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
"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=="],
|
||||
"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=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
@@ -77,11 +77,9 @@
|
||||
|
||||
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||
|
||||
"peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="],
|
||||
"strtok3": ["strtok3@10.3.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw=="],
|
||||
|
||||
"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=="],
|
||||
"token-types": ["token-types@6.0.3", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
@@ -89,16 +87,20 @@
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
|
||||
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
|
||||
|
||||
"@cap.js/server/@types/node": ["@types/node@22.16.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ=="],
|
||||
|
||||
"@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
|
||||
|
||||
"@tokenizer/inflate/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cap-standalone",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.3",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "bun run --watch src/index.js",
|
||||
@@ -45,12 +45,12 @@
|
||||
"turing-test"
|
||||
],
|
||||
"dependencies": {
|
||||
"@cap.js/server": "^1.0.13",
|
||||
"@cap.js/server": "^1.0.16",
|
||||
"@elysiajs/cors": "^1.3.3",
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@elysiajs/swagger": "^1.3.0",
|
||||
"elysia": "^1.3.1",
|
||||
"elysia-rate-limit": "^4.3.0"
|
||||
"@elysiajs/swagger": "^1.3.1",
|
||||
"elysia": "^1.3.5",
|
||||
"elysia-rate-limit": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
margin-top: 10%;
|
||||
margin-top: 14vh;
|
||||
background-color: #181818;
|
||||
color: #e8e8ea;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
(()=>{const t=(e,o,n,i)=>{o.onclick=null;const s=parseInt(o.getAttribute("data-cap-floating-offset"))||8,a=o.getAttribute("data-cap-floating-position")||"top",l=o.getBoundingClientRect();Object.assign(n.style,{display:"block",position:"absolute",zIndex:"99999"});const c=l.left+(l.width-n.offsetWidth)/2,r=Math.min(c,window.innerWidth-n.offsetWidth);n.style.top="top"===a?`${Math.max(window.scrollY,l.top-n.offsetHeight-s+window.scrollY)}px`:`${Math.min(l.bottom+s+window.scrollY,window.innerHeight-n.offsetHeight+window.scrollY)}px`,n.style.left=`${Math.max(r,2)}px`,n.solve(),n.addEventListener("solve",({detail:s})=>{o.setAttribute("data-cap-token",s.token),o.setAttribute("data-cap-progress","done"),setTimeout(()=>{i.forEach(t=>{o.addEventListener("click",t),t.call(o,e)}),setTimeout(()=>{o.onclick=null,i.forEach(t=>o.removeEventListener("click",t)),o.onclick=e=>t(e,o,n,i)},50)},500),setTimeout(()=>{n.style.display="none"},700)})},e=e=>{const o=e.getAttribute("data-cap-floating");if(!o)return;const n=document.querySelector(o);if(!document.contains(n)&&!n.solve)throw new Error(`[cap floating] "${o}" doesn't exist or isn't a Cap widget`);n.style.display="none";const i=[e.onclick].filter(Boolean);"function"==typeof getEventListeners&&i.push(...(getEventListeners(e).click||[]).map(t=>t.listener)),i.length&&(e.onclick=null,i.forEach(t=>e.removeEventListener("click",t))),e.addEventListener("click",o=>{o.stopImmediatePropagation(),o.preventDefault(),t(o,e,n,i)})},o=t=>{e(t),t.querySelectorAll("[data-cap-floating]").forEach(e)};o(document.body),new MutationObserver(t=>t.forEach(t=>t.addedNodes.forEach(t=>{t.nodeType===Node.ELEMENT_NODE&&o(t)}))).observe(document.body,{childList:!0,subtree:!0})})();
|
||||
(()=>{const t=(e,o,n,i)=>{o.onclick=null;const s=parseInt(o.getAttribute("data-cap-floating-offset"))||8,a=o.getAttribute("data-cap-floating-position")||"top",l=o.getBoundingClientRect();Object.assign(n.style,{display:"block",position:"absolute",zIndex:"99999",opacity:"0",transform:"scale(0.98)",marginTop:"-4px",boxShadow:"rgba(0, 0, 0, 0.05) 0px 6px 24px 0px",borderRadius:"14px",transition:"opacity 0.15s, margin-top 0.2s, transform 0.2s"}),setTimeout(()=>{n.style.transform="scale(1)",n.style.opacity="1",n.style.marginTop="0"},5);const r=l.left+(l.width-n.offsetWidth)/2,c=Math.min(r,window.innerWidth-n.offsetWidth);n.style.top="top"===a?`${Math.max(window.scrollY,l.top-n.offsetHeight-s+window.scrollY)}px`:`${Math.min(l.bottom+s+window.scrollY,window.innerHeight-n.offsetHeight+window.scrollY)}px`,n.style.left=`${Math.max(c,2)}px`,n.solve(),n.addEventListener("solve",({detail:s})=>{o.setAttribute("data-cap-token",s.token),o.setAttribute("data-cap-progress","done"),setTimeout(()=>{i.forEach(t=>{o.addEventListener("click",t),t.call(o,e)}),setTimeout(()=>{o.onclick=null,i.forEach(t=>o.removeEventListener("click",t)),o.onclick=e=>t(e,o,n,i)},50)},500),setTimeout(()=>{n.style.transform="scale(0.98)",n.style.opacity="0",n.style.marginTop="-4px"},500),setTimeout(()=>{n.style.display="none"},700)})},e=e=>{const o=e.getAttribute("data-cap-floating");if(!o)return;const n=document.querySelector(o);if(!document.contains(n)&&!n.solve)throw new Error(`[cap floating] "${o}" doesn't exist or isn't a Cap widget`);n.style.display="none";const i=[e.onclick].filter(Boolean);"function"==typeof getEventListeners&&i.push(...(getEventListeners(e).click||[]).map(t=>t.listener)),i.length&&(e.onclick=null,i.forEach(t=>e.removeEventListener("click",t))),e.addEventListener("click",o=>{o.stopImmediatePropagation(),o.preventDefault(),t(o,e,n,i)})},o=t=>{e(t),t.querySelectorAll("[data-cap-floating]").forEach(e)};o(document.body),new MutationObserver(t=>t.forEach(t=>t.addedNodes.forEach(t=>{t.nodeType===Node.ELEMENT_NODE&&o(t)}))).observe(document.body,{childList:!0,subtree:!0})})();
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cap.js/widget",
|
||||
"version": "0.1.24",
|
||||
"version": "0.1.25",
|
||||
"description": "Client-side widget for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.",
|
||||
"keywords": [
|
||||
"security",
|
||||
|
||||
+68
-6
@@ -143,7 +143,10 @@
|
||||
|
||||
this.#div.setAttribute(
|
||||
"aria-label",
|
||||
this.getI18nText("verifying-aria-label", "Verifying you're a human, please wait")
|
||||
this.getI18nText(
|
||||
"verifying-aria-label",
|
||||
"Verifying you're a human, please wait"
|
||||
)
|
||||
);
|
||||
|
||||
this.dispatchEvent("progress", { progress: 0 });
|
||||
@@ -157,7 +160,56 @@
|
||||
method: "POST",
|
||||
})
|
||||
).json();
|
||||
const solutions = await this.solveChallenges(challenge);
|
||||
|
||||
let challenges = challenge;
|
||||
|
||||
if (!Array.isArray(challenges)) {
|
||||
function prng(seed, length) {
|
||||
function fnv1a(str) {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash ^= str.charCodeAt(i);
|
||||
hash +=
|
||||
(hash << 1) +
|
||||
(hash << 4) +
|
||||
(hash << 7) +
|
||||
(hash << 8) +
|
||||
(hash << 24);
|
||||
}
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
let state = fnv1a(seed);
|
||||
let result = "";
|
||||
|
||||
function next() {
|
||||
state ^= state << 13;
|
||||
state ^= state >>> 17;
|
||||
state ^= state << 5;
|
||||
return state >>> 0;
|
||||
}
|
||||
|
||||
while (result.length < length) {
|
||||
const rnd = next();
|
||||
result += rnd.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
return result.substring(0, length);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
|
||||
challenges = Array.from({ length: challenge.c }, () => {
|
||||
i = i + 1;
|
||||
|
||||
return [
|
||||
prng(`${token}${i}`, challenge.s),
|
||||
prng(`${token}${i}d`, challenge.d),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
const solutions = await this.solveChallenges(challenges);
|
||||
|
||||
const resp = await (
|
||||
await capFetch(`${apiEndpoint}redeem`, {
|
||||
@@ -189,14 +241,20 @@
|
||||
|
||||
this.#div.setAttribute(
|
||||
"aria-label",
|
||||
this.getI18nText("verified-aria-label", "We have verified you're a human, you may now continue")
|
||||
this.getI18nText(
|
||||
"verified-aria-label",
|
||||
"We have verified you're a human, you may now continue"
|
||||
)
|
||||
);
|
||||
|
||||
return { success: true, token: this.token };
|
||||
} catch (err) {
|
||||
this.#div.setAttribute(
|
||||
"aria-label",
|
||||
this.getI18nText("error-aria-label", "An error occurred, please try again")
|
||||
this.getI18nText(
|
||||
"error-aria-label",
|
||||
"An error occurred, please try again"
|
||||
)
|
||||
);
|
||||
this.error(err.message);
|
||||
throw err;
|
||||
@@ -249,7 +307,8 @@
|
||||
this.dispatchEvent("progress", {
|
||||
progress: Math.round((completed / total) * 100),
|
||||
});
|
||||
resolve([salt, target, data.nonce]);
|
||||
|
||||
resolve(data.nonce);
|
||||
};
|
||||
|
||||
worker.onerror = (err) => {
|
||||
@@ -309,7 +368,10 @@
|
||||
this.#div.classList.add("captcha");
|
||||
this.#div.setAttribute("role", "button");
|
||||
this.#div.setAttribute("tabindex", "0");
|
||||
this.#div.setAttribute("aria-label", this.getI18nText("verify-aria-label", "Click to verify you're a human"));
|
||||
this.#div.setAttribute(
|
||||
"aria-label",
|
||||
this.getI18nText("verify-aria-label", "Click to verify you're a human")
|
||||
);
|
||||
this.#div.setAttribute("aria-live", "polite");
|
||||
this.#div.setAttribute("disabled", "true");
|
||||
this.#div.innerHTML = `<div class="checkbox" part="checkbox"></div><p part="label">${this.getI18nText(
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
typeof WebAssembly !== "object" ||
|
||||
typeof WebAssembly?.instantiate !== "function"
|
||||
) {
|
||||
self.onmessage = async ({ data: { salt, target } }) => {
|
||||
// Fallback solver in case WASM is not available
|
||||
// fallback worker for environments without wasm
|
||||
// this is much slower than the wasm version
|
||||
|
||||
self.onmessage = async ({ data: { salt, target } }) => {
|
||||
let nonce = 0;
|
||||
const batchSize = 50000;
|
||||
let processed = 0;
|
||||
@@ -48,7 +49,7 @@
|
||||
|
||||
processed += batchSize;
|
||||
} catch (error) {
|
||||
console.error("[cap worker] fallback worker error", error);
|
||||
console.error("[cap worker]", error);
|
||||
self.postMessage({
|
||||
found: false,
|
||||
error: error.message,
|
||||
|
||||
Reference in New Issue
Block a user