better docs
This commit is contained in:
@@ -31,7 +31,7 @@ You can either run it on any JavaScript runtime, or use the standalone mode with
|
||||
- **Standalone mode**
|
||||
Run Cap anywhere with a Docker container with analytics & more
|
||||
|
||||
- **Invisible**
|
||||
- **Programmatic**
|
||||
Hide Cap's widget and solve challenges in the background
|
||||
|
||||
- **M2M**
|
||||
|
||||
@@ -167,7 +167,7 @@ export default withMermaid({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "Usage", link: "/guide/widget.md" },
|
||||
{ text: "Invisible mode", link: "/guide/invisible.md" },
|
||||
{ text: "Programmatic mode", link: "/guide/programmatic.md" },
|
||||
{ text: "Floating mode", link: "/guide/floating.md" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -10,10 +10,10 @@ Cap doesn't use cookies or telemetry by default. No data is collected or stored
|
||||
|
||||
## Why proof-of-work?
|
||||
|
||||
Every CAPTCHA can eventually be solved, whether by AIs, algorithms, reverse-engineering and spoofing fingerprints, or humans paid via CAPTCHA farms — this results in an endless cat-and-mouse game between attackers and defenders. The crucial difference lies in the cost imposed on attackers.
|
||||
Every CAPTCHA can eventually be solved, whether by AIs, algorithms, reverse-engineering and spoofing fingerprints, or humans paid via CAPTCHA farms, and this results in an endless cat-and-mouse game between attackers and defenders. The crucial difference lies in the cost imposed on attackers.
|
||||
|
||||
Cap's goal is to make automated abuse expensive while keeping the experience fast and virtually invisible for real users. Proof-of-work is a perfect balance for this issue, stopping abuse by requiring computational effort rather than relying solely on human verification methods that bots continuously learn to mimic.
|
||||
|
||||
Imagine sending 10,000 spam messages costs $1, potentially earning $10 – a profitable venture. If Cap increases the computational cost so that sending those messages now costs $100, the spammer loses $90. This eliminates the financial incentive.
|
||||
|
||||
Cap's proof-of-work is heavily inspired by [Hashcash](https://www.researchgate.net/publication/2482110_Hashcash_-_A_Denial_of_Service_Counter-Measure).
|
||||
Cap's proof-of-work is heavily inspired by [Hashcash](https://www.researchgate.net/publication/2482110_Hashcash_-_A_Denial_of_Service_Counter-Measure). Our instrumentation challenges are inspired by Twitter and YouTube's own custom challenges.
|
||||
|
||||
+115
-26
@@ -14,61 +14,150 @@ Cap consists of a client-side widget, which solves challenges and displays the c
|
||||
|
||||
<Demo />
|
||||
|
||||
## Widget
|
||||
## 1. Setting up your server
|
||||
|
||||
In order for your users to be able to solve Cap challenges, you'll need to install the widget library to either use the [invisible mode](./invisible.md) or add the checkbox component.
|
||||
We recommend starting with Cap Standalone. You're gonna need [Docker](https://docs.docker.com/get-docker/) and about 100mb of free memory (it uses about 50mb idle). It supports multiple site keys and is compatible with reCAPTCHA's siteverify API, so you can even run it alongside reCAPTCHA and switch over gradually.
|
||||
|
||||
You can find example snippets for multiple frameworks on the [widget docs](./widget.md#usage). We're gonna assume a basic vanilla implementation here for simplicity
|
||||
Start by creating a `docker-compose.yml` file:
|
||||
|
||||
Add the following to your website's HTML:
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
cap:
|
||||
image: tiago2/cap:latest
|
||||
container_name: cap
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
ADMIN_KEY: your_secret_password
|
||||
volumes:
|
||||
- cap-data:/usr/src/app/.data
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
cap-data:
|
||||
```
|
||||
|
||||
::: tip Tips
|
||||
|
||||
- `ADMIN_KEY` is your dashboard login. We recommend making it at least 32 characters.
|
||||
- Change `3000:3000` if that port is already in use on your host.
|
||||
- If the dashboard is unreachable, try adding `network_mode: "host"` under the `cap` service.
|
||||
|
||||
:::
|
||||
|
||||
Start the container:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` (or your server's IP/domain on port 3000) to access the dashboard. Log in with your admin key, create a site key, and note down both the **site key** and its **secret key** - you'll need both.
|
||||
|
||||
If you want the best protection and can handle higher memory usage, we also recommend turning on instrumentation challenges and potentially headless browser detection.
|
||||
|
||||
## 2. Adding the widget
|
||||
|
||||
You can find example snippets for multiple frameworks on the [widget docs](./widget.md#usage). We're gonna assume a basic vanilla implementation here for simplicity.
|
||||
|
||||
Add the widget script to your website's HTML:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
|
||||
<!-- we recommend pinning a version in production -->
|
||||
```
|
||||
|
||||
Next, you can either add the widget component directly to your code:
|
||||
Then add the widget component, pointing it at your instance:
|
||||
|
||||
```html
|
||||
<cap-widget data-cap-api-endpoint="https://<your-instance>/<site-key>/"></cap-widget>
|
||||
```
|
||||
|
||||
You'll need to start a server with the Cap API running at the same URL as specified in the `data-cap-api-endpoint` attribute. We'll tell you how to set this up in the next section.
|
||||
- `<your-instance>` — the public URL of your Cap Standalone instance (e.g. `cap.example.com`)
|
||||
- `<site-key>` — the site key from your dashboard
|
||||
|
||||
Then, in your JavaScript, listen for the `solve` event to capture the token when generated:
|
||||
Example:
|
||||
|
||||
```js{3}
|
||||
```html
|
||||
<cap-widget data-cap-api-endpoint="https://cap.example.com/d9256640cb53/"></cap-widget>
|
||||
```
|
||||
|
||||
In your JavaScript, listen for the `solve` event to capture the token:
|
||||
|
||||
```js
|
||||
const widget = document.querySelector("cap-widget");
|
||||
|
||||
widget.addEventListener("solve", function (e) {
|
||||
const token = e.detail.token;
|
||||
|
||||
// Handle the token as needed
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, you can wrap the widget in a `<form></form>`, where Cap will automatically submit the token alongside other form data as `cap-token`.
|
||||
Alternatively, you can wrap the widget in a `<form></form>` and Cap will automatically submit the token alongside other form data as `cap-token`.
|
||||
|
||||
You can also use get a token programmatically without displaying the widget by using the [invisible mode](./invisible.md):
|
||||
You can also use get a token programmatically without displaying the widget by using the [programmatic mode](./programmatic.md).
|
||||
|
||||
```js
|
||||
const cap = new Cap({
|
||||
apiEndpoint: "https://<your-instance>/<site-key>/",
|
||||
});
|
||||
const solution = await cap.solve();
|
||||
## 3. Verifying tokens
|
||||
|
||||
// you can attach event listeners to track progress
|
||||
cap.addEventListener("progress", (event) => {
|
||||
console.log(`Solving... ${event.detail.progress}% done`);
|
||||
});
|
||||
Once a user completes the CAPTCHA, your backend must verify the token before trusting it. Send a `POST` request to your instance's `/siteverify` endpoint:
|
||||
|
||||
console.log(solution.token);
|
||||
::: code-group
|
||||
|
||||
```sh [curl]
|
||||
curl "https://<your-instance>/<site-key>/siteverify" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
|
||||
```
|
||||
|
||||
## Server-side
|
||||
```js [fetch]
|
||||
const { success } = await (
|
||||
await fetch("https://<your-instance>/<site-key>/siteverify", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ secret: "<key_secret>", response: "<captcha_token>" }),
|
||||
})
|
||||
).json();
|
||||
```
|
||||
|
||||
Cap is fully self-hosted, so you'll need to start a server exposing an API for Cap's protocol.
|
||||
```py [python]
|
||||
import requests
|
||||
success = requests.post(
|
||||
"https://<your-instance>/<site-key>/siteverify",
|
||||
json={"secret": "<key_secret>", "response": "<captcha_token>"}
|
||||
).json().get("success")
|
||||
|
||||
For most use cases, **we recommend using the Docker [Standalone server](./standalone/index.md)**, as it exposes a simple HTTP API and provides a simple web dashboard with analytics. It also supports our JavaScript instrumentation challenges, which force bots to use real browsers, along with optional headless browser detection.
|
||||
print(success)
|
||||
```
|
||||
|
||||
However, if you can't use Docker or need a more lightweight solution, you can use the [server library](./server.md) on Node and Bun or a [community library](./community.md) in other languages.
|
||||
```php [php]
|
||||
<?php
|
||||
$data = json_decode(file_get_contents("https://<your-instance>/<site-key>/siteverify",
|
||||
false, stream_context_create([
|
||||
"http" => [
|
||||
"method" => "POST",
|
||||
"header" => "Content-Type: application/json",
|
||||
"content" => json_encode(["secret"=>"<key_secret>","response"=>"<captcha_token>"])
|
||||
]
|
||||
])
|
||||
), true);
|
||||
var_dump($data['success'] ?? false);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
- `<key_secret>` — the secret key from your dashboard (**not** the dashboard admin key)
|
||||
- `<captcha_token>` — the token generated by the widget
|
||||
|
||||
A successful verification returns:
|
||||
|
||||
```json
|
||||
{ "success": true }
|
||||
```
|
||||
|
||||
That's it! Cap is fully set up. Your users solve challenges client-side, your server verifies tokens, and you own all the data.
|
||||
|
||||
## Next steps
|
||||
|
||||
**You're mostly done.** If you'd like, you can:
|
||||
|
||||
- [Customize your widget](./widget#options)'s look and feel
|
||||
- [Fully configure Cap Standalone](./standalone/options.html) to set up CORS or make sure rate-limiting works properly
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Invisible mode
|
||||
# Programmatic mode
|
||||
|
||||
You can use `new Cap({ ... })` in your client-side JavaScript to create a new Cap instance and use the `solve()` method to solve the challenge.
|
||||
|
||||
@@ -19,7 +19,6 @@ const cap = new Cap({
|
||||
});
|
||||
|
||||
cap.addEventListener("progress", (event) => {
|
||||
// [!code focus]
|
||||
console.log(`Solving... ${event.detail.progress}% done`);
|
||||
});
|
||||
```
|
||||
@@ -1,16 +1,15 @@
|
||||
# Cap Standalone
|
||||
|
||||
Cap Standalone is the default way of self-hosting Cap's backend along with instrumentation challenges. It provides an API for the widget and a siteverify endpoint compatible with hCaptcha or reCAPTCHA's, along with the ability to use multiple site keys.
|
||||
Cap Standalone is the recommended way to self-host Cap's backend. It runs on Bun and keeps idle memory usage around 50mb. It ships with built-in support for instrumentation challenges, which significantly raise the bar for bots, a siteverify API compatible with reCAPTCHA, and a web dashboard for managing multiple site keys.
|
||||
|
||||
We recommend using [Docker](https://docs.docker.com/get-docker/) to run Cap Standalone.
|
||||
|
||||
## Installation
|
||||
|
||||
You'll need to have [Docker Engine 20.10 or higher](https://docs.docker.com/get-docker/) installed on your server.
|
||||
|
||||
Start by creating a docker compose file named `docker-compose.yml` with the following content:
|
||||
Create a `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
cap:
|
||||
image: tiago2/cap:latest
|
||||
@@ -22,18 +21,15 @@ services:
|
||||
volumes:
|
||||
- cap-data:/usr/src/app/.data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
cap-data:
|
||||
```
|
||||
|
||||
::: tip Tips
|
||||
|
||||
- Make sure to add an admin key to log into the web UI. It should be at least 12 characters long.
|
||||
|
||||
- If your port 3000 is already in use, feel free to change it to something else.
|
||||
|
||||
- If you're having trouble accessing the dashboard, add `network_mode: "host"` under the Cap service.
|
||||
- `ADMIN_KEY` is your dashboard's login. We recommend making it at least 32 characters
|
||||
- Change `3000:3000` if that port is already in use on your host.
|
||||
- If the dashboard is unreachable, try adding `network_mode: "host"` under the `cap` service.
|
||||
:::
|
||||
|
||||
Start the container:
|
||||
@@ -42,26 +38,24 @@ Start the container:
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
And access your web dashboard at `http://localhost:3000` or by accessing port 3000 on your machine.
|
||||
Open `http://localhost:3000` (or your server's IP/domain on port 3000) to access the dashboard. Log in with your admin key, create a site key, and note down both the **site key** and its **secret key**, you'll need both.
|
||||
|
||||
Log in with your admin key and create a new site key. Take note of both the site key and secret key.
|
||||
If you want the best protection and can handle higher memory usage, we also recommend turning on instrumentation challenges and potentially headless browser detection.
|
||||
|
||||
You'll also need to make the server publicly accessible from the internet, as the widget needs to be able to reach it. If you're using a reverse proxy, make sure to check [the options guide](/guide/standalone/options.md) to configure rate-limiting properly.
|
||||
Your Cap Standalone instance must be publicly reachable from the internet so the widget can communicate with it. If you're using a reverse proxy, review the [options guide](/guide/standalone/options.md) to configure rate-limiting correctly.
|
||||
|
||||
## Usage
|
||||
|
||||
### Client-side
|
||||
|
||||
You'll need to configure your widget to use your self-hosted Cap Standalone server. To do this, set the widget's API endpoint option to:
|
||||
Point the widget at your instance by setting the `data-cap-api-endpoint` attribute:
|
||||
|
||||
```
|
||||
https://<instance_url>/<site_key>/
|
||||
```
|
||||
|
||||
Make sure to replace:
|
||||
|
||||
- `<instance_url>`: The actual URL where your Cap Standalone instance is running. This URL must be publicly accessible from the internet.
|
||||
- `<site_key>`: Your site key from this dashboard.
|
||||
- `<instance_url>` — the public URL of your Cap Standalone instance
|
||||
- `<site_key>` — the site key from your dashboard
|
||||
|
||||
Example:
|
||||
|
||||
@@ -69,23 +63,11 @@ Example:
|
||||
<cap-widget data-cap-api-endpoint="https://cap.example.com/d9256640cb53/"></cap-widget>
|
||||
```
|
||||
|
||||
We recommend reading our [widget documentation](../widget.md) for more details and example snippets for multiple frameworks.
|
||||
|
||||
### Server-side
|
||||
|
||||
After a user completes the CAPTCHA on your site, your backend needs to verify their token using this server's API.
|
||||
|
||||
You can do this by sending a `POST` request from your server to the following endpoint:
|
||||
|
||||
```
|
||||
https://<instance_url>/<site_key>/siteverify
|
||||
```
|
||||
|
||||
Your request needs to include the following data:
|
||||
|
||||
- `secret`: Your key secret from this dashboard. This is **not** the admin key, but rather your site key's secret.
|
||||
|
||||
- `response`: The CAPTCHA token generated by the widget on the client-side
|
||||
|
||||
Example using `curl`:
|
||||
Once a user completes the CAPTCHA, your backend must verify the token before trusting it. Send a `POST` request to your instance's `/siteverify` endpoint with the following JSON body:
|
||||
|
||||
```bash
|
||||
curl "https://<instance_url>/<site_key>/siteverify" \
|
||||
@@ -94,16 +76,10 @@ curl "https://<instance_url>/<site_key>/siteverify" \
|
||||
-d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
|
||||
```
|
||||
|
||||
A successful response will return:
|
||||
Where `<key_secret>` is the secret key from your dashboard (**not** the dashboard admin key), and `<captcha_token>` is the challenge token generated by the widget.
|
||||
|
||||
A successful verification returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
::: tip Client-side library storage
|
||||
|
||||
Cap Standalone can also serve the widget and floating client-side library files. [Learn more](options.md#asset-server)
|
||||
|
||||
:::
|
||||
{ "success": true }
|
||||
```
|
||||
@@ -43,9 +43,7 @@ By default, Standalone will use Elysia's built-in `server.requestIP` function to
|
||||
|
||||
If so, you can change the IP extraction logic to simply read from a header set in `RATELIMIT_IP_HEADER` in your env. For example, if you were using Cloudflare, you might set `RATELIMIT_IP_HEADER` to `cf-connecting-ip`. On most setups, this is `x-forwarded-for`.
|
||||
|
||||
The `/siteverify` endpoint is intended for server-to-server use, so each request checks if your key's secret is valid. If it is, the request is allowed. If not, the request is denied and the client's IP is temporarily blocked for a few hundred milliseconds. This prevents database strain without affecting legitimate requests.
|
||||
|
||||
If you're interested in an option to fully disable ratelimiting, let us know using GitHub issues.
|
||||
The `/siteverify` endpoint is intended for server-to-server use, so it's not ratelimited by default.
|
||||
|
||||
## Custom data path
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ outline: [2, 3, 4]
|
||||
|
||||
# Widget
|
||||
|
||||
Cap's client-side widget handles requesting, solving and displaying challenges using a native web component and rust-flavoured WASM. It also includes the [invisible mode](./invisible).
|
||||
Cap's client-side widget handles requesting, solving and displaying challenges using a native web component and rust-flavoured WASM. It also includes the [programmatic mode](./programmatic).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -228,9 +228,9 @@ export default component$(() => {
|
||||
});
|
||||
```
|
||||
|
||||
## Invisible mode
|
||||
## Programmatic mode
|
||||
|
||||
If you don't want a visible widget, for example when protecting a background action like a post submission, use the [programmatic API](./invisible):
|
||||
If you don't want a visible widget, for example when protecting a background action like a post submission, use the [programmatic mode](./programmatic):
|
||||
|
||||
```js
|
||||
import Cap from "@cap.js/widget";
|
||||
|
||||
@@ -9,13 +9,13 @@ By the way, this is a more technical explanation of how Cap works. If you're loo
|
||||
|
||||
#### Requesting the challenge
|
||||
|
||||
3. When a solution is requested, the widget sends a request to the server. The server will return a token and the configuration for the challenges to solve.
|
||||
3. When a solution is requested, the widget sends a request to the server. The server will return a token, the configuration for the challenges to solve and optionally compressed instrumentation data.
|
||||
|
||||
4. The widget then generates multiple challenges using a set seed (the challenge token) and the configuration provided by the server.
|
||||
4. The widget then generates multiple challenges using a set seed (the challenge token) and the configuration provided by the server. If instrumentation data is provided, it's decompressed and solved in a sandboxed iframe.
|
||||
|
||||
#### Computing the solution
|
||||
|
||||
5. The widget uses WASM and Web Workers to solve the challenges in parallel:
|
||||
5. The widget uses Rust-flavoured WASM and Web Workers to solve the challenges in parallel:
|
||||
- Each worker attempts to find a valid nonce by repeatedly:
|
||||
- Combining the salt with different nonce values
|
||||
- Computing the SHA-256 hash of this combination
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ features:
|
||||
title: Standalone mode
|
||||
details: Run Cap anywhere with a Docker container with analytics & more
|
||||
- icon: 💨
|
||||
title: Invisible
|
||||
title: Programmatic
|
||||
details: Hide Cap's widget and solve challenges in the background
|
||||
- icon: 🤖
|
||||
title: M2M
|
||||
|
||||
Reference in New Issue
Block a user