mirror of
https://github.com/altcha-org/altcha.git
synced 2026-01-25 04:16:41 +00:00
spamfilter
This commit is contained in:
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
42
README.md
42
README.md
@@ -59,14 +59,17 @@ Required options (at least one is required):
|
||||
|
||||
Additional options:
|
||||
|
||||
- __auto__ - Automatically verify without user interaction (possible values: `onload`, `onsubmit`).
|
||||
- __auto__ - Automatically verify without user interaction (possible values: `onfocus`, `onload`, `onsubmit`).
|
||||
- __blockspam__ - Only used in conjunction with the `spamfilter` option. If enabled, it will block form submission and fail verification if the Spam Filter returns a negative classification. This effectively prevents submission of the form.
|
||||
- __expire__ - The challenge expiration (duration in milliseconds).
|
||||
- __hidefooter__ - Hide the footer (ALTCHA link).
|
||||
- __hidelogo__ - Hide the ALTCHA logo.
|
||||
- __maxnumber__ - The max. number to iterate to (defaults to 1,000,000).
|
||||
- __name__ - The name of the hidden field containing the payload (defaults to "altcha").
|
||||
- __spamfilter__ - Enable [Spam Filter](#spam-filter).
|
||||
- __strings__ - JSON-encoded translation strings. Refer to [customization](/docs/widget-customization).
|
||||
- __refetchonexpire__ - Automatically re-fetch and re-validate when the challenge expires (defaults to true).
|
||||
- __verifyurl__ - Enable server-side verification by configuring the URL to use for verification requests. This option can be used in conjunction with `spamfilter` to enable server-side verification.
|
||||
- __workers__ - The number of workers to utilize for PoW (defaults to `navigator.hardwareConcurrency || 8`).
|
||||
|
||||
Development / testing options:
|
||||
@@ -112,6 +115,7 @@ export interface Configure {
|
||||
mockerror?: boolean;
|
||||
name?: string;
|
||||
refetchonexpire?: boolean;
|
||||
spamfilter: boolean | SpamFilter;
|
||||
strings?: {
|
||||
error?: string;
|
||||
footer?: string;
|
||||
@@ -121,12 +125,14 @@ export interface Configure {
|
||||
waitAlert?: string;
|
||||
};
|
||||
test?: boolean;
|
||||
verifyurl?: string;
|
||||
workers?: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
- __serververification__ - Triggers upon a server verification (only in conjunction with `spamfilter`).
|
||||
- __statechange__ - Triggers whenever an internal `state` changes.
|
||||
- __verified__ - Triggers when the challenge is verified.
|
||||
|
||||
@@ -152,6 +158,40 @@ document.querySelector('#altcha').addEventListener('statechange', (ev) => {
|
||||
> [!IMPORTANT]
|
||||
> Both programmatic configuration and event listeners have to called/attached after the ALTCHA script loads, such as within window.addEventListener('load', ...).
|
||||
|
||||
## Spam Filter
|
||||
|
||||
The widget integrates with ALTCHA's [Spam Filter API](https://altcha.org/docs/api/spam-filter-api) to allow checking submitted form data for potential spam.
|
||||
|
||||
The Spam Filter API analyzes various signals in the submitted data to determine if it exhibits characteristics of spam. This non-invasive filtering helps reduce spam submissions without frustrating legitimate users.
|
||||
|
||||
### Spam Filter Configuration
|
||||
|
||||
The Spam Filter can be enabled with default configuration by setting the `spamfilter` option to `true`, or it can be customized using the following configuration schema:
|
||||
|
||||
```ts
|
||||
interface SpamFilter {
|
||||
email?: string | false;
|
||||
expectedLanguages?: string[];
|
||||
expectedCountries?: string[];
|
||||
fields?: string[] | false;
|
||||
ipAddress?: string | false;
|
||||
timeZone?: string | false;
|
||||
}
|
||||
```
|
||||
|
||||
SpamFilter configuration options:
|
||||
|
||||
- __email__ - The name of the input field for the user's email. Disable email checking with `false`.
|
||||
- __expectedLanguages__ - An array of expected languages as 2-letter codes (ISO 639 alpha-2).
|
||||
- __expectedCountries__ - An array of expected countries as 2-letter codes (ISO 3166-1 alpha-2).
|
||||
- __fields__ - An array of input names to send to the spam filter.
|
||||
- __ipAddress__ - The user's IP is detected automatically but can be overridden or disabled with `false`.
|
||||
- __timeZone__ - The user's timezone is detected automatically but can be overridden or disabled with `false`.
|
||||
|
||||
### Exclude Inputs from Spam Checking
|
||||
|
||||
By default, all text inputs and textareas within the parent form are spam-checked. To exclude a specific input, add the `data-no-spamfilter` attribute. Alternatively, explicitly list the checked fields using the `fields` config option.
|
||||
|
||||
## Contributing
|
||||
See [Contributing Guide](https://github.com/altcha-org/altcha/blob/main/CONTRIBUTING.md) and please follow our [Code of Conduct](https://github.com/altcha-org/altcha/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
|
||||
3
dist/altcha.iife.js
vendored
3
dist/altcha.iife.js
vendored
File diff suppressed because one or more lines are too long
1512
dist/altcha.js
vendored
1512
dist/altcha.js
vendored
File diff suppressed because it is too large
Load Diff
3
dist/altcha.umd.cjs
vendored
3
dist/altcha.umd.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Svelte + TS</title>
|
||||
<title>ALTCHA</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
454
package-lock.json
generated
454
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "altcha",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "altcha",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.3",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.41.2",
|
||||
@@ -17,6 +17,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"husky": "^9.0.11",
|
||||
"prettier": "3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"sass": "^1.71.1",
|
||||
"svelte": "^4.2.11",
|
||||
"svelte-check": "^3.4.6",
|
||||
@@ -40,9 +42,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -56,9 +58,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -72,9 +74,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -88,9 +90,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -104,9 +106,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -120,9 +122,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -136,9 +138,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -152,9 +154,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -168,9 +170,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -184,9 +186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -200,9 +202,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -216,9 +218,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
|
||||
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -232,9 +234,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
|
||||
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -248,9 +250,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -264,9 +266,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
|
||||
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -280,9 +282,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
|
||||
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -296,9 +298,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -312,9 +314,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -328,9 +330,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -344,9 +346,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -360,9 +362,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -376,9 +378,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -392,9 +394,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -518,9 +520,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
|
||||
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
|
||||
"integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -531,9 +533,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
|
||||
"integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -544,9 +546,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
|
||||
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
|
||||
"integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -557,9 +559,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
|
||||
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
|
||||
"integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -570,9 +572,22 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
|
||||
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
|
||||
"integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
|
||||
"integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -583,9 +598,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
|
||||
"integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -596,9 +611,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
|
||||
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
|
||||
"integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -608,10 +623,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
|
||||
"integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
|
||||
"integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -621,10 +649,23 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
|
||||
"integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
|
||||
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
|
||||
"integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -635,9 +676,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
|
||||
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
|
||||
"integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -648,9 +689,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
|
||||
"integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -661,9 +702,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
|
||||
"integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -674,9 +715,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
|
||||
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
|
||||
"integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1159,9 +1200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -1343,9 +1384,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
||||
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -1355,29 +1396,29 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.19.12",
|
||||
"@esbuild/android-arm": "0.19.12",
|
||||
"@esbuild/android-arm64": "0.19.12",
|
||||
"@esbuild/android-x64": "0.19.12",
|
||||
"@esbuild/darwin-arm64": "0.19.12",
|
||||
"@esbuild/darwin-x64": "0.19.12",
|
||||
"@esbuild/freebsd-arm64": "0.19.12",
|
||||
"@esbuild/freebsd-x64": "0.19.12",
|
||||
"@esbuild/linux-arm": "0.19.12",
|
||||
"@esbuild/linux-arm64": "0.19.12",
|
||||
"@esbuild/linux-ia32": "0.19.12",
|
||||
"@esbuild/linux-loong64": "0.19.12",
|
||||
"@esbuild/linux-mips64el": "0.19.12",
|
||||
"@esbuild/linux-ppc64": "0.19.12",
|
||||
"@esbuild/linux-riscv64": "0.19.12",
|
||||
"@esbuild/linux-s390x": "0.19.12",
|
||||
"@esbuild/linux-x64": "0.19.12",
|
||||
"@esbuild/netbsd-x64": "0.19.12",
|
||||
"@esbuild/openbsd-x64": "0.19.12",
|
||||
"@esbuild/sunos-x64": "0.19.12",
|
||||
"@esbuild/win32-arm64": "0.19.12",
|
||||
"@esbuild/win32-ia32": "0.19.12",
|
||||
"@esbuild/win32-x64": "0.19.12"
|
||||
"@esbuild/aix-ppc64": "0.20.2",
|
||||
"@esbuild/android-arm": "0.20.2",
|
||||
"@esbuild/android-arm64": "0.20.2",
|
||||
"@esbuild/android-x64": "0.20.2",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@esbuild/freebsd-arm64": "0.20.2",
|
||||
"@esbuild/freebsd-x64": "0.20.2",
|
||||
"@esbuild/linux-arm": "0.20.2",
|
||||
"@esbuild/linux-arm64": "0.20.2",
|
||||
"@esbuild/linux-ia32": "0.20.2",
|
||||
"@esbuild/linux-loong64": "0.20.2",
|
||||
"@esbuild/linux-mips64el": "0.20.2",
|
||||
"@esbuild/linux-ppc64": "0.20.2",
|
||||
"@esbuild/linux-riscv64": "0.20.2",
|
||||
"@esbuild/linux-s390x": "0.20.2",
|
||||
"@esbuild/linux-x64": "0.20.2",
|
||||
"@esbuild/netbsd-x64": "0.20.2",
|
||||
"@esbuild/openbsd-x64": "0.20.2",
|
||||
"@esbuild/sunos-x64": "0.20.2",
|
||||
"@esbuild/win32-arm64": "0.20.2",
|
||||
"@esbuild/win32-ia32": "0.20.2",
|
||||
"@esbuild/win32-x64": "0.20.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
@@ -1405,17 +1446,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"version": "4.19.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"body-parser": "1.20.2",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie": "0.6.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
@@ -1446,30 +1487,6 @@
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -1485,21 +1502,6 @@
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/express/node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -2318,9 +2320,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2339,12 +2341,37 @@
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-svelte": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.3.tgz",
|
||||
"integrity": "sha512-wJq8RunyFlWco6U0WJV5wNCM7zpBFakS76UBSbmzMGpncpK98NZABaE+s7n8/APDCEVNHXC5Mpq+MLebQtsRlg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"prettier": "^3.0.0",
|
||||
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
@@ -2481,9 +2508,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
|
||||
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
|
||||
"version": "4.14.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
|
||||
"integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
@@ -2496,19 +2523,22 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.12.0",
|
||||
"@rollup/rollup-android-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.12.0",
|
||||
"@rollup/rollup-darwin-x64": "4.12.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.12.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.12.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.12.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.12.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.14.3",
|
||||
"@rollup/rollup-android-arm64": "4.14.3",
|
||||
"@rollup/rollup-darwin-arm64": "4.14.3",
|
||||
"@rollup/rollup-darwin-x64": "4.14.3",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.14.3",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.14.3",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.14.3",
|
||||
"@rollup/rollup-linux-x64-musl": "4.14.3",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.14.3",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.14.3",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.14.3",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -2725,9 +2755,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -3026,14 +3056,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
|
||||
"integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
|
||||
"version": "5.2.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz",
|
||||
"integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.3",
|
||||
"postcss": "^8.4.35",
|
||||
"rollup": "^4.2.0"
|
||||
"esbuild": "^0.20.1",
|
||||
"postcss": "^8.4.38",
|
||||
"rollup": "^4.13.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"build": "rimraf dist && vite build && echo \"declare module 'altcha';\" > dist/altcha.d.ts",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"format": "prettier --write ./src/**/*",
|
||||
"test": "vitest run tests/helpers.test.ts",
|
||||
"test:e2e": "playwright test",
|
||||
"prepare": "husky"
|
||||
@@ -53,6 +54,8 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"husky": "^9.0.11",
|
||||
"prettier": "3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.3",
|
||||
"sass": "^1.71.1",
|
||||
"svelte": "^4.2.11",
|
||||
"svelte-check": "^3.4.6",
|
||||
|
||||
65
server/server-deno.ts
Normal file
65
server/server-deno.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* An example server implementation for ALTCHA using Deno.
|
||||
*
|
||||
* Run: deno run --allow-net server-deno.ts
|
||||
*/
|
||||
|
||||
import { Hono } from 'https://deno.land/x/hono/mod.ts';
|
||||
import { cors } from 'https://deno.land/x/hono/middleware/cors/index.ts';
|
||||
import {
|
||||
createChallenge,
|
||||
verifySolution,
|
||||
} from 'https://deno.land/x/altcha/mod.ts';
|
||||
|
||||
// Configure your secret HMAC key
|
||||
const HMAC_KEY = 'secret.hmac.key';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
function url(ref: string, hash: string = '') {
|
||||
const url = new URL(ref);
|
||||
url.hash = hash;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
app.use('/*', cors());
|
||||
|
||||
// Configure this endpoint as `challengeurl`
|
||||
app.get('/altcha', async (c) =>
|
||||
c.json(
|
||||
await createChallenge({
|
||||
hmacKey: HMAC_KEY,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Form submission endpoint
|
||||
app.post('/submit', async (c) => {
|
||||
const ref = c.req.header('referer');
|
||||
const form = await c.req.formData();
|
||||
|
||||
// The verification payload is received as `altcha` field
|
||||
const payload = form.get('altcha');
|
||||
|
||||
console.log('Body:', Object.fromEntries(form));
|
||||
|
||||
if (!payload) {
|
||||
return c.text('Invalid payload', 400);
|
||||
}
|
||||
|
||||
if (await verifySolution(payload as string, HMAC_KEY)) {
|
||||
console.log('VERIFIED.');
|
||||
if (ref) {
|
||||
return c.redirect(url(ref, '#success'));
|
||||
}
|
||||
return c.text('Submitted!');
|
||||
}
|
||||
|
||||
console.log('INVALID ALTCHA.');
|
||||
if (ref) {
|
||||
return c.redirect(url(ref, '#failure'));
|
||||
}
|
||||
return c.text('ALTCHA check failed.', 400);
|
||||
});
|
||||
|
||||
Deno.serve(app.fetch);
|
||||
@@ -1,79 +0,0 @@
|
||||
import { createHash, createHmac, randomInt, randomBytes } from 'node:crypto';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
const ALTCHA_ALG = 'SHA-256';
|
||||
const ALTCHA_ALG_NODE = ALTCHA_ALG.replace('-', ''); // node doesn't support alg with dashes
|
||||
const ALTCHA_NUM_RANGE = [10000, 50000]; // adjust complexity
|
||||
const ALTCHA_HMAC_KEY = 'secret.hmac.key'; // !! change this key !!
|
||||
|
||||
function createALTCHA(salt = randomBytes(12).toString('hex'), number = randomInt(...ALTCHA_NUM_RANGE)) {
|
||||
const challenge = createHash(ALTCHA_ALG_NODE).update(salt + number).digest('hex');
|
||||
return {
|
||||
algorithm: ALTCHA_ALG,
|
||||
challenge,
|
||||
maxnumber: ALTCHA_NUM_RANGE[1],
|
||||
salt,
|
||||
signature: createHmac(ALTCHA_ALG_NODE, ALTCHA_HMAC_KEY).update(challenge).digest('hex'),
|
||||
};
|
||||
}
|
||||
|
||||
function verifyALTCHA(payload) {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(atob(payload));
|
||||
} catch {
|
||||
// invalid payload
|
||||
return false;
|
||||
}
|
||||
if (json) {
|
||||
const { algorithm, challenge, salt, signature, number } = json;
|
||||
const check = createALTCHA(salt, number);
|
||||
if (algorithm === check.algorithm && challenge === check.challenge && signature == check.signature) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function url(ref, hash = '') {
|
||||
const url = new URL(ref);
|
||||
url.hash = hash;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.urlencoded());
|
||||
|
||||
// Endpoint to fetch a new challenge
|
||||
app.get('/altcha', function (req, res) {
|
||||
res.send(createALTCHA());
|
||||
});
|
||||
|
||||
// Endpoint to handle form submission
|
||||
app.post('/submit', function (req, res) {
|
||||
const ref = req.header('referer');
|
||||
const { altcha } = req.body || {};
|
||||
if (verifyALTCHA(altcha)) {
|
||||
console.log('VERIFIED. Body:', req.body);
|
||||
if (ref) {
|
||||
res.redirect(url(ref, '#success'));
|
||||
} else {
|
||||
res.send('Submitted!');
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('INVALID ALTCHA. Body:', req.body);
|
||||
if (ref) {
|
||||
res.redirect(url(ref, '#failure'));
|
||||
} else {
|
||||
res.status(403);
|
||||
res.send('ALTCHA check failed.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
@@ -1,16 +1,26 @@
|
||||
<svelte:options customElement={{
|
||||
<svelte:options
|
||||
customElement={{
|
||||
tag: 'altcha-widget',
|
||||
shadow: 'none',
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy, onMount, tick } from 'svelte';
|
||||
import InlineWorker from './worker?worker&inline';
|
||||
import { solveChallenge, createTestChallenge } from './helpers';
|
||||
import { State } from './types';
|
||||
import type { Configure, Payload, Challenge, Solution } from './types';
|
||||
import type {
|
||||
Configure,
|
||||
Payload,
|
||||
Challenge,
|
||||
Solution,
|
||||
SpamFilter,
|
||||
ServerVerificationPayload,
|
||||
} from './types';
|
||||
|
||||
export let auto: 'onload' | 'onsubmit' | undefined = undefined;
|
||||
export let auto: 'onfocus' | 'onload' | 'onsubmit' | undefined = undefined;
|
||||
export let blockspam: boolean | undefined = undefined;
|
||||
export let challengeurl: string | undefined = undefined;
|
||||
export let challengejson: string | undefined = undefined;
|
||||
export let debug: boolean = false;
|
||||
@@ -21,13 +31,16 @@
|
||||
export let maxnumber: number = 1e6;
|
||||
export let mockerror: boolean = false;
|
||||
export let refetchonexpire: boolean = true;
|
||||
export let spamfilter: boolean | SpamFilter = false;
|
||||
export let strings: string | undefined = undefined;
|
||||
export let test: boolean = false;
|
||||
export let test: boolean | number = false;
|
||||
export let verifyurl: string | undefined = undefined;
|
||||
export let workers: number = navigator.hardwareConcurrency || 8;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const allowedAlgs = ['SHA-256', 'SHA-384', 'SHA-512'];
|
||||
const website = 'https://altcha.org/';
|
||||
const documentLocale = document.documentElement.lang?.split('-')?.[0];
|
||||
|
||||
let checked: boolean = false;
|
||||
let el: HTMLElement;
|
||||
@@ -37,13 +50,15 @@
|
||||
let state: State = State.UNVERIFIED;
|
||||
let expireTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
$: parsedChallenge = challengejson ? parseJsonAttribute(challengejson) : undefined;
|
||||
$: parsedChallenge = challengejson
|
||||
? parseJsonAttribute(challengejson)
|
||||
: undefined;
|
||||
$: parsedStrings = strings ? parseJsonAttribute(strings) : {};
|
||||
$: _strings = {
|
||||
error: 'Verification failed. Try again later.',
|
||||
expired: 'Verification expired. Try again.',
|
||||
footer: `Protected by <a href="${website}" target="_blank">ALTCHA</a>`,
|
||||
label: 'I\'m not a robot',
|
||||
label: "I'm not a robot",
|
||||
verified: 'Verified',
|
||||
verifying: 'Verifying...',
|
||||
waitAlert: 'Verifying... please wait.',
|
||||
@@ -55,6 +70,7 @@
|
||||
if (elForm) {
|
||||
elForm.removeEventListener('submit', onFormSubmit);
|
||||
elForm.removeEventListener('reset', onFormReset);
|
||||
elForm.removeEventListener('focusin', onFormFocusIn);
|
||||
elForm = null;
|
||||
}
|
||||
});
|
||||
@@ -75,6 +91,9 @@
|
||||
if (elForm) {
|
||||
elForm.addEventListener('submit', onFormSubmit);
|
||||
elForm.addEventListener('reset', onFormReset);
|
||||
if (auto === 'onfocus') {
|
||||
elForm.addEventListener('focusin', onFormFocusIn);
|
||||
}
|
||||
}
|
||||
if (auto === 'onload') {
|
||||
verify();
|
||||
@@ -87,6 +106,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function onFormFocusIn(ev: FocusEvent) {
|
||||
verify();
|
||||
}
|
||||
|
||||
function onFormSubmit(ev: SubmitEvent) {
|
||||
if (elForm && auto === 'onsubmit' && state === State.UNVERIFIED) {
|
||||
ev.preventDefault();
|
||||
@@ -106,7 +129,8 @@
|
||||
}
|
||||
|
||||
function createAltchaPayload(data: Challenge, solution: Solution): string {
|
||||
return btoa(JSON.stringify({
|
||||
return btoa(
|
||||
JSON.stringify({
|
||||
algorithm: data.algorithm,
|
||||
challenge: data!.challenge,
|
||||
number: solution.number,
|
||||
@@ -114,7 +138,8 @@
|
||||
signature: data.signature,
|
||||
test: test ? true : undefined,
|
||||
took: solution.took,
|
||||
} satisfies Payload));
|
||||
} satisfies Payload)
|
||||
);
|
||||
}
|
||||
|
||||
function validateChallenge(data: Challenge) {
|
||||
@@ -125,7 +150,9 @@
|
||||
throw new Error('Invalid challenge. Property signature is missing.');
|
||||
}
|
||||
if (!allowedAlgs.includes(data.algorithm.toUpperCase())) {
|
||||
throw new Error(`Unknown algorithm value. Allowed values: ${allowedAlgs.join(', ')}`);
|
||||
throw new Error(
|
||||
`Unknown algorithm value. Allowed values: ${allowedAlgs.join(', ')}`
|
||||
);
|
||||
}
|
||||
if (!data.challenge || data.challenge.length < 40) {
|
||||
throw new Error('Challenge is too short. Min. 40 chars.');
|
||||
@@ -139,25 +166,43 @@
|
||||
if (mockerror) {
|
||||
log('mocking error');
|
||||
throw new Error('Mocked error.');
|
||||
|
||||
} else if (parsedChallenge) {
|
||||
log('using provided json data');
|
||||
return parsedChallenge;
|
||||
|
||||
} else if (test) {
|
||||
log('generating test challenge');
|
||||
return createTestChallenge();
|
||||
|
||||
return createTestChallenge(typeof test !== 'boolean' ? +test : undefined);
|
||||
} else {
|
||||
if (!challengeurl) {
|
||||
throw new Error(`Attribute challengeurl not set.`);
|
||||
}
|
||||
log('fetching challenge from', challengeurl);
|
||||
const resp = await fetch(challengeurl);
|
||||
const resp = await fetch(challengeurl, {
|
||||
headers: {
|
||||
'x-altcha-spam-filter': !!spamfilter ? '1' : '0',
|
||||
},
|
||||
});
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(`Server responded with ${resp.status}.`);
|
||||
}
|
||||
const expHeader = resp.headers.get('Expires');
|
||||
const configHeader = resp.headers.get('X-Altcha-Config');
|
||||
if (configHeader) {
|
||||
try {
|
||||
const config = JSON.parse(configHeader);
|
||||
if (config && typeof config === 'object') {
|
||||
if (config.verifyurl) {
|
||||
config.verifyurl = new URL(
|
||||
config.verifyurl,
|
||||
new URL(challengeurl)
|
||||
).toString();
|
||||
}
|
||||
configure(config);
|
||||
}
|
||||
} catch (err) {
|
||||
log('unable to configure from X-Altcha-Config', err);
|
||||
}
|
||||
}
|
||||
if (!expire && expHeader?.length) {
|
||||
const parsed = Date.parse(expHeader);
|
||||
if (parsed) {
|
||||
@@ -175,19 +220,25 @@
|
||||
if (challengeurl && refetchonexpire && state === State.VERIFIED) {
|
||||
// re-fetch challenge and verify again
|
||||
verify();
|
||||
|
||||
} else {
|
||||
reset(State.EXPIRED, _strings.expired);
|
||||
}
|
||||
}
|
||||
|
||||
async function run(data: Challenge): Promise<{ data: Challenge, solution: Solution | null }> {
|
||||
async function run(
|
||||
data: Challenge
|
||||
): Promise<{ data: Challenge; solution: Solution | null }> {
|
||||
let solution: Solution | null = null;
|
||||
if ('Worker' in window) {
|
||||
try {
|
||||
solution = await runWorker(data.challenge, data.salt, data.algorithm, data.maxnumber);
|
||||
solution = await runWorker(
|
||||
data.challenge,
|
||||
data.salt,
|
||||
data.algorithm,
|
||||
data.maxnumber
|
||||
);
|
||||
} catch (err) {
|
||||
log(err)
|
||||
log(err);
|
||||
}
|
||||
if (solution?.number !== undefined) {
|
||||
return {
|
||||
@@ -198,11 +249,22 @@
|
||||
}
|
||||
return {
|
||||
data,
|
||||
solution: await solveChallenge(data.challenge, data.salt, data.algorithm, data.maxnumber || maxnumber).promise,
|
||||
}
|
||||
solution: await solveChallenge(
|
||||
data.challenge,
|
||||
data.salt,
|
||||
data.algorithm,
|
||||
data.maxnumber || maxnumber
|
||||
).promise,
|
||||
};
|
||||
}
|
||||
|
||||
async function runWorker(challenge: string, salt: string, alg?: string, max: number = maxnumber, concurrency: number = Math.ceil(workers)): Promise<Solution | null> {
|
||||
async function runWorker(
|
||||
challenge: string,
|
||||
salt: string,
|
||||
alg?: string,
|
||||
max: number = maxnumber,
|
||||
concurrency: number = Math.ceil(workers)
|
||||
): Promise<Solution | null> {
|
||||
const workers: Worker[] = [];
|
||||
if (concurrency < 1) {
|
||||
throw new Error('Wrong number of workers configured.');
|
||||
@@ -210,11 +272,12 @@
|
||||
if (concurrency > 16) {
|
||||
throw new Error('Too many workers. Max. 16 allowed workers.');
|
||||
}
|
||||
for (let i = 0; i < concurrency; i ++) {
|
||||
for (let i = 0; i < concurrency; i++) {
|
||||
workers.push(new InlineWorker());
|
||||
}
|
||||
const step = Math.ceil(max / concurrency);
|
||||
const solutions = await Promise.all(workers.map((worker, i) => {
|
||||
const solutions = await Promise.all(
|
||||
workers.map((worker, i) => {
|
||||
const start = i * step;
|
||||
return new Promise((resolve) => {
|
||||
worker.addEventListener('message', (message: MessageEvent) => {
|
||||
@@ -238,7 +301,8 @@
|
||||
type: 'work',
|
||||
});
|
||||
}) as Promise<Solution | null>;
|
||||
}));
|
||||
})
|
||||
);
|
||||
for (const worker of workers) {
|
||||
worker.terminate();
|
||||
}
|
||||
@@ -247,7 +311,11 @@
|
||||
|
||||
function onCheckedChange() {
|
||||
if ([State.UNVERIFIED, State.ERROR, State.EXPIRED].includes(state)) {
|
||||
if (spamfilter && elForm?.reportValidity() === false) {
|
||||
checked = false;
|
||||
} else {
|
||||
verify();
|
||||
}
|
||||
} else {
|
||||
checked = true;
|
||||
}
|
||||
@@ -260,15 +328,107 @@
|
||||
}
|
||||
|
||||
function setExpire(duration: number) {
|
||||
log('expire', duration)
|
||||
log('expire', duration);
|
||||
clearTimeout(expireTimeout);
|
||||
if(duration < 1) {
|
||||
if (duration < 1) {
|
||||
expireChallenge();
|
||||
} else {
|
||||
expireTimeout = setTimeout(expireChallenge, duration);
|
||||
}
|
||||
}
|
||||
|
||||
function getEmail(name?: string) {
|
||||
const elInput = elForm?.querySelector(
|
||||
typeof name === 'string' ? `input[name="${name}"]` : 'input[type="email"]:not([data-no-spamfilter])'
|
||||
) as HTMLInputElement;
|
||||
return elInput?.value?.slice(elInput.value.indexOf('@')) || void 1;
|
||||
}
|
||||
|
||||
function getTextFields(names?: string[]) {
|
||||
const elInputs = [
|
||||
...(elForm?.querySelectorAll(
|
||||
names?.length
|
||||
? names.map((name) => `input[name="${name}"]`).join(', ')
|
||||
: 'input[type="text"]:not([data-no-spamfilter]), textarea:not([data-no-spamfilter])'
|
||||
) || []),
|
||||
] as HTMLInputElement[];
|
||||
return elInputs.reduce(
|
||||
(acc, el) => {
|
||||
const name = el.name;
|
||||
const value = el.value.trim();
|
||||
if (name && value) {
|
||||
acc[name] = value;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
}
|
||||
|
||||
function getTimeZone() {
|
||||
try {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
async function requestServerVerification(verificationPayload: string) {
|
||||
if (!verifyurl) {
|
||||
throw new Error('Attribute verifyurl not set.');
|
||||
}
|
||||
log('requesting server verification from', verifyurl);
|
||||
const body: ServerVerificationPayload = {
|
||||
payload: verificationPayload,
|
||||
};
|
||||
if (spamfilter) {
|
||||
const {
|
||||
email,
|
||||
expectedLanguages,
|
||||
expectedCountries,
|
||||
fields,
|
||||
ipAddress,
|
||||
timeZone,
|
||||
} =
|
||||
typeof spamfilter === 'object'
|
||||
? spamfilter
|
||||
: {
|
||||
email: void 0,
|
||||
expectedCountries: void 0,
|
||||
expectedLanguages: void 0,
|
||||
fields: void 0,
|
||||
ipAddress: void 0,
|
||||
timeZone: void 0,
|
||||
};
|
||||
body.ipAddress = ipAddress === false ? void 0 : ipAddress || 'auto';
|
||||
body.email = email === false ? void 0 : getEmail(email);
|
||||
body.fields = fields === false ? void 0 : getTextFields(fields);
|
||||
body.timeZone = timeZone === false ? void 0 : timeZone || getTimeZone();
|
||||
body.expectedCountries = expectedCountries;
|
||||
body.expectedLanguages =
|
||||
expectedLanguages || (documentLocale ? [documentLocale] : void 0);
|
||||
}
|
||||
const resp = await fetch(verifyurl, {
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
});
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(`Server responded with ${resp.status}.`);
|
||||
}
|
||||
const json = await resp.json();
|
||||
if (json?.payload) {
|
||||
payload = json.payload;
|
||||
}
|
||||
dispatch('serververification', json);
|
||||
if (blockspam && json.classification === 'BAD') {
|
||||
throw new Error('SpamFilter returned negative classification.');
|
||||
}
|
||||
}
|
||||
|
||||
export function configure(options: Configure) {
|
||||
if (options.auto !== void 0) {
|
||||
auto = options.auto;
|
||||
@@ -305,18 +465,27 @@
|
||||
if (options.refetchonexpire !== void 0) {
|
||||
refetchonexpire = !!options.refetchonexpire;
|
||||
}
|
||||
if (options.spamfilter !== void 0) {
|
||||
spamfilter = options.spamfilter;
|
||||
}
|
||||
if (options.strings) {
|
||||
parsedStrings = options.strings;
|
||||
}
|
||||
if (options.test !== void 0) {
|
||||
test = !!options.test;
|
||||
}
|
||||
if (options.verifyurl !== void 0) {
|
||||
verifyurl = options.verifyurl;
|
||||
}
|
||||
if (options.workers !== void 0) {
|
||||
workers = +options.workers;
|
||||
}
|
||||
}
|
||||
|
||||
export function reset(newState: State = State.UNVERIFIED, err: string | null = null) {
|
||||
export function reset(
|
||||
newState: State = State.UNVERIFIED,
|
||||
err: string | null = null
|
||||
) {
|
||||
clearTimeout(expireTimeout);
|
||||
checked = false;
|
||||
error = err;
|
||||
@@ -335,19 +504,29 @@
|
||||
.then(({ data, solution }) => {
|
||||
log('solution', solution);
|
||||
if (solution?.number !== undefined) {
|
||||
log('verified');
|
||||
state = State.VERIFIED;
|
||||
checked = true;
|
||||
if (verifyurl) {
|
||||
return requestServerVerification(
|
||||
createAltchaPayload(data, solution)
|
||||
);
|
||||
} else {
|
||||
payload = createAltchaPayload(data, solution);
|
||||
log('payload', payload);
|
||||
tick().then(() => {
|
||||
dispatch('verified', { payload });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log('Unable to find a solution. Ensure that the \'maxnumber\' attribute is greater than the randomly generated number.');
|
||||
log(
|
||||
"Unable to find a solution. Ensure that the 'maxnumber' attribute is greater than the randomly generated number."
|
||||
);
|
||||
throw new Error('Unexpected result returned.');
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
tick().then(() => {
|
||||
state = State.VERIFIED;
|
||||
checked = true;
|
||||
log('verified');
|
||||
dispatch('verified', { payload });
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
log(err);
|
||||
state = State.ERROR;
|
||||
@@ -377,12 +556,15 @@
|
||||
>
|
||||
{/if}
|
||||
|
||||
<div class="altcha-checkbox" class:altcha-hidden={state === State.VERIFYING}>
|
||||
<div
|
||||
class="altcha-checkbox"
|
||||
class:altcha-hidden={state === State.VERIFYING}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="{name}_checkbox"
|
||||
required={auto !== 'onsubmit'}
|
||||
bind:checked={checked}
|
||||
bind:checked
|
||||
on:change={onCheckedChange}
|
||||
on:invalid={onInvalid}
|
||||
/>
|
||||
@@ -391,7 +573,7 @@
|
||||
<div class="altcha-label">
|
||||
{#if state === State.VERIFIED}
|
||||
<span>{@html _strings.verified}</span>
|
||||
<input type="hidden" name={name} value={payload} />
|
||||
<input type="hidden" {name} value={payload} />
|
||||
{:else if state === State.VERIFYING}
|
||||
<span>{@html _strings.verifying}</span>
|
||||
{:else}
|
||||
@@ -402,10 +584,25 @@
|
||||
{#if hidelogo !== true}
|
||||
<div>
|
||||
<a href={website} target="_blank" class="altcha-logo">
|
||||
<svg width="22" height="22" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.33955 16.4279C5.88954 20.6586 12.1971 21.2105 16.4279 17.6604C18.4699 15.947 19.6548 13.5911 19.9352 11.1365L17.9886 10.4279C17.8738 12.5624 16.909 14.6459 15.1423 16.1284C11.7577 18.9684 6.71167 18.5269 3.87164 15.1423C1.03163 11.7577 1.4731 6.71166 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577C16.9767 5.86872 17.5322 7.02798 17.804 8.2324L19.9522 9.01429C19.7622 7.07737 19.0059 5.17558 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956C-0.658625 5.88958 -1.21046 12.1971 2.33955 16.4279Z" fill="currentColor"/>
|
||||
<path d="M3.57212 2.33956C1.65755 3.94607 0.496389 6.11731 0.12782 8.40523L2.04639 9.13961C2.26047 7.15832 3.21057 5.25375 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577L13.8302 6.78606L19.9633 9.13364C19.7929 7.15555 19.0335 5.20847 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956Z" fill="currentColor"/>
|
||||
<path d="M7 10H5C5 12.7614 7.23858 15 10 15C12.7614 15 15 12.7614 15 10H13C13 11.6569 11.6569 13 10 13C8.3431 13 7 11.6569 7 10Z" fill="currentColor"/>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.33955 16.4279C5.88954 20.6586 12.1971 21.2105 16.4279 17.6604C18.4699 15.947 19.6548 13.5911 19.9352 11.1365L17.9886 10.4279C17.8738 12.5624 16.909 14.6459 15.1423 16.1284C11.7577 18.9684 6.71167 18.5269 3.87164 15.1423C1.03163 11.7577 1.4731 6.71166 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577C16.9767 5.86872 17.5322 7.02798 17.804 8.2324L19.9522 9.01429C19.7622 7.07737 19.0059 5.17558 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956C-0.658625 5.88958 -1.21046 12.1971 2.33955 16.4279Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.57212 2.33956C1.65755 3.94607 0.496389 6.11731 0.12782 8.40523L2.04639 9.13961C2.26047 7.15832 3.21057 5.25375 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577L13.8302 6.78606L19.9633 9.13364C19.7929 7.15555 19.0335 5.20847 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M7 10H5C5 12.7614 7.23858 15 10 15C12.7614 15 15 12.7614 15 10H13C13 11.6569 11.6569 13 10 13C8.3431 13 7 11.6569 7 10Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
@@ -414,8 +611,20 @@
|
||||
|
||||
{#if error || state === State.EXPIRED}
|
||||
<div class="altcha-error">
|
||||
<svg width="14" height="14" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
{#if state === State.EXPIRED}
|
||||
<div title={error}>{@html _strings.expired}</div>
|
||||
@@ -435,7 +644,8 @@
|
||||
<style global>
|
||||
.altcha {
|
||||
background: var(--altcha-color-base, transparent);
|
||||
border: var(--altcha-border-width, 1px) solid var(--altcha-color-border, #a0a0a0);
|
||||
border: var(--altcha-border-width, 1px) solid
|
||||
var(--altcha-color-border, #a0a0a0);
|
||||
border-radius: var(--altcha-border-radius, 3px);
|
||||
color: var(--altcha-color-text, currentColor);
|
||||
display: flex;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import Altcha from "./Altcha.svelte";
|
||||
import { onMount } from 'svelte';
|
||||
import Altcha from './Altcha.svelte';
|
||||
|
||||
const success = location.hash.includes('success');
|
||||
const failure = location.hash.includes('failure');
|
||||
@@ -22,13 +22,29 @@
|
||||
<h1>ALTCHA</h1>
|
||||
|
||||
<div>
|
||||
<label for="challengeUrl">Challenge URL <small>(to fetch the challenge from)</small>:</label>
|
||||
<input type="url" id="challengeUrl" placeholder="http://..." disabled={test} bind:value={challengeurl} />
|
||||
<label for="challengeUrl"
|
||||
>Challenge URL <small>(to fetch the challenge from)</small>:</label
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
id="challengeUrl"
|
||||
placeholder="http://..."
|
||||
disabled={test}
|
||||
bind:value={challengeurl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="submitUrl">Submit URL <small>(to submit the data to)</small>:</label>
|
||||
<input type="url" id="submitUrl" placeholder="http://..." disabled={test} bind:value={submiturl} />
|
||||
<label for="submitUrl"
|
||||
>Submit URL <small>(to submit the data to)</small>:</label
|
||||
>
|
||||
<input
|
||||
type="url"
|
||||
id="submitUrl"
|
||||
placeholder="http://..."
|
||||
disabled={test}
|
||||
bind:value={submiturl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -44,24 +60,24 @@
|
||||
<form
|
||||
action={submiturl}
|
||||
method="post"
|
||||
on:submit={(ev) => test ? ev.preventDefault() : undefined}
|
||||
on:submit={(ev) => (test ? ev.preventDefault() : undefined)}
|
||||
>
|
||||
<div>Test form</div>
|
||||
|
||||
{#if success}
|
||||
<div class="success">
|
||||
Form successfully submitted.
|
||||
</div>
|
||||
<div class="success">Form successfully submitted.</div>
|
||||
{/if}
|
||||
|
||||
{#if failure}
|
||||
<div class="failure">
|
||||
Failed to submit form.
|
||||
</div>
|
||||
<div class="failure">Failed to submit form.</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<input type="text" name="test_field" placeholder="Test field..." />
|
||||
<input
|
||||
type="text"
|
||||
name="test_field"
|
||||
placeholder="Test field..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Altcha
|
||||
@@ -72,6 +88,8 @@
|
||||
{test}
|
||||
on:statechange={(ev) => console.log('Event: statechange:', ev.detail)}
|
||||
on:verified={(ev) => console.log('Event: verified:', ev.detail)}
|
||||
on:serververification={(ev) =>
|
||||
console.log('Event: serververification:', ev.detail)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
@@ -79,7 +97,6 @@
|
||||
<button type="reset">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -92,7 +109,7 @@
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]) {
|
||||
input:not([type='checkbox']) {
|
||||
box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import Altcha from './Altcha.svelte';
|
||||
|
||||
export {
|
||||
Altcha,
|
||||
};
|
||||
export { Altcha };
|
||||
|
||||
@@ -3,10 +3,16 @@ import type { Solution } from './types';
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
export function ab2hex(ab: ArrayBuffer) {
|
||||
return [...new Uint8Array(ab)].map(x => x.toString(16).padStart(2, '0')).join('');
|
||||
return [...new Uint8Array(ab)]
|
||||
.map((x) => x.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
||||
|
||||
export async function createTestChallenge(num?: number, algorithm: string = 'SHA-256', max: number = 1e5) {
|
||||
export async function createTestChallenge(
|
||||
num?: number,
|
||||
algorithm: string = 'SHA-256',
|
||||
max: number = 1e5
|
||||
) {
|
||||
const salt = Date.now().toString(16);
|
||||
if (!num) {
|
||||
num = Math.round(Math.random() * max);
|
||||
@@ -20,11 +26,26 @@ export async function createTestChallenge(num?: number, algorithm: string = 'SHA
|
||||
};
|
||||
}
|
||||
|
||||
export async function hashChallenge(salt: string, num: number, algorithm: string) {
|
||||
return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(salt + num)));
|
||||
export async function hashChallenge(
|
||||
salt: string,
|
||||
num: number,
|
||||
algorithm: string
|
||||
) {
|
||||
return ab2hex(
|
||||
await crypto.subtle.digest(
|
||||
algorithm.toUpperCase(),
|
||||
encoder.encode(salt + num)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function solveChallenge(challenge: string, salt: string, algorithm: string = 'SHA-256', max: number = 1e6, start: number = 0): { promise: Promise<Solution | null>; controller: AbortController } {
|
||||
export function solveChallenge(
|
||||
challenge: string,
|
||||
salt: string,
|
||||
algorithm: string = 'SHA-256',
|
||||
max: number = 1e6,
|
||||
start: number = 0
|
||||
): { promise: Promise<Solution | null>; controller: AbortController } {
|
||||
const controller = new AbortController();
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const startTime = Date.now();
|
||||
@@ -32,7 +53,8 @@ export function solveChallenge(challenge: string, salt: string, algorithm: strin
|
||||
if (controller.signal.aborted || n > max) {
|
||||
resolve(null);
|
||||
} else {
|
||||
hashChallenge(salt, n, algorithm).then((t) => {
|
||||
hashChallenge(salt, n, algorithm)
|
||||
.then((t) => {
|
||||
if (t === challenge) {
|
||||
resolve({
|
||||
number: n,
|
||||
@@ -41,7 +63,8 @@ export function solveChallenge(challenge: string, salt: string, algorithm: strin
|
||||
} else {
|
||||
next(n + 1);
|
||||
}
|
||||
}).catch(reject);
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
};
|
||||
next(start);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import App from './App.svelte'
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')!,
|
||||
})
|
||||
});
|
||||
|
||||
export default app
|
||||
export default app;
|
||||
|
||||
25
src/types.ts
25
src/types.ts
@@ -9,7 +9,7 @@ export interface Strings {
|
||||
}
|
||||
|
||||
export interface Configure {
|
||||
auto?: 'onload' | 'onsubmit';
|
||||
auto?: 'onfocus' | 'onload' | 'onsubmit';
|
||||
challenge?: Challenge;
|
||||
debug?: boolean;
|
||||
expire?: number;
|
||||
@@ -20,11 +20,32 @@ export interface Configure {
|
||||
mockerror?: boolean;
|
||||
name?: string;
|
||||
refetchonexpire?: boolean;
|
||||
spamfilter?: boolean;
|
||||
strings?: Partial<Strings>;
|
||||
test?: boolean;
|
||||
verifyurl?: string;
|
||||
workers?: number;
|
||||
}
|
||||
|
||||
export interface SpamFilter {
|
||||
email?: string | false;
|
||||
expectedLanguages?: string[];
|
||||
expectedCountries?: string[];
|
||||
fields?: string[] | false;
|
||||
ipAddress?: string | false;
|
||||
timeZone?: string | false;
|
||||
}
|
||||
|
||||
export interface ServerVerificationPayload {
|
||||
email?: string;
|
||||
expectedCountries?: string[];
|
||||
expectedLanguages?: string[];
|
||||
fields?: Record<string, string>;
|
||||
ipAddress?: string;
|
||||
payload: string;
|
||||
timeZone?: string;
|
||||
}
|
||||
|
||||
export interface Solution {
|
||||
number: number;
|
||||
took: number;
|
||||
@@ -55,4 +76,4 @@ export enum State {
|
||||
VERIFYING = 'verifying',
|
||||
UNVERIFIED = 'unverified',
|
||||
EXPIRED = 'expired',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { solveChallenge } from './helpers';
|
||||
let controller: AbortController | undefined = undefined;
|
||||
|
||||
onmessage = async (message) => {
|
||||
const { type, payload } = message.data
|
||||
const { type, payload } = message.data;
|
||||
if (type === 'abort') {
|
||||
controller?.abort();
|
||||
controller = undefined;
|
||||
|
||||
Reference in New Issue
Block a user