better docs

This commit is contained in:
tiago
2026-03-02 20:06:39 +00:00
parent 11dbd0f962
commit 923de58e43
10 changed files with 149 additions and 87 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ You can either run it on any JavaScript runtime, or use the standalone mode with
- **Standalone mode** - **Standalone mode**
Run Cap anywhere with a Docker container with analytics & more Run Cap anywhere with a Docker container with analytics & more
- **Invisible** - **Programmatic**
Hide Cap's widget and solve challenges in the background Hide Cap's widget and solve challenges in the background
- **M2M** - **M2M**
+1 -1
View File
@@ -167,7 +167,7 @@ export default withMermaid({
collapsed: false, collapsed: false,
items: [ items: [
{ text: "Usage", link: "/guide/widget.md" }, { 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" }, { text: "Floating mode", link: "/guide/floating.md" },
], ],
}, },
+2 -2
View File
@@ -10,10 +10,10 @@ Cap doesn't use cookies or telemetry by default. No data is collected or stored
## Why proof-of-work? ## 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. 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. 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
View File
@@ -14,61 +14,150 @@ Cap consists of a client-side widget, which solves challenges and displays the c
<Demo /> <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 ```html
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script> <script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
<!-- we recommend pinning a version in production --> <!-- 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 ```html
<cap-widget data-cap-api-endpoint="https://<your-instance>/<site-key>/"></cap-widget> <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"); const widget = document.querySelector("cap-widget");
widget.addEventListener("solve", function (e) { widget.addEventListener("solve", function (e) {
const token = e.detail.token; const token = e.detail.token;
// Handle the token as needed // 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 ## 3. Verifying tokens
const cap = new Cap({
apiEndpoint: "https://<your-instance>/<site-key>/",
});
const solution = await cap.solve();
// you can attach event listeners to track progress 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:
cap.addEventListener("progress", (event) => {
console.log(`Solving... ${event.detail.progress}% done`);
});
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. 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) => { cap.addEventListener("progress", (event) => {
// [!code focus]
console.log(`Solving... ${event.detail.progress}% done`); console.log(`Solving... ${event.detail.progress}% done`);
}); });
``` ```
+21 -45
View File
@@ -1,16 +1,15 @@
# Cap Standalone # 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 ## Installation
You'll need to have [Docker Engine 20.10 or higher](https://docs.docker.com/get-docker/) installed on your server. Create a `docker-compose.yml` file:
Start by creating a docker compose file named `docker-compose.yml` with the following content:
```yaml ```yaml
version: "3.8" version: "3.8"
services: services:
cap: cap:
image: tiago2/cap:latest image: tiago2/cap:latest
@@ -22,18 +21,15 @@ services:
volumes: volumes:
- cap-data:/usr/src/app/.data - cap-data:/usr/src/app/.data
restart: unless-stopped restart: unless-stopped
volumes: volumes:
cap-data: cap-data:
``` ```
::: tip Tips ::: tip Tips
- Make sure to add an admin key to log into the web UI. It should be at least 12 characters long. - `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 your port 3000 is already in use, feel free to change it to something else. - If the dashboard is unreachable, try adding `network_mode: "host"` under the `cap` service.
- If you're having trouble accessing the dashboard, add `network_mode: "host"` under the Cap service.
::: :::
Start the container: Start the container:
@@ -42,26 +38,24 @@ Start the container:
docker compose up -d 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 ## Usage
### Client-side ### 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>/ https://<instance_url>/<site_key>/
``` ```
Make sure to replace: - `<instance_url>` — the public URL of your Cap Standalone instance
- `<site_key>` — the site key from your dashboard
- `<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.
Example: Example:
@@ -69,23 +63,11 @@ Example:
<cap-widget data-cap-api-endpoint="https://cap.example.com/d9256640cb53/"></cap-widget> <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 ### Server-side
After a user completes the CAPTCHA on your site, your backend needs to verify their token using this server's API. 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:
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`:
```bash ```bash
curl "https://<instance_url>/<site_key>/siteverify" \ 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>" }' -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 ```json
{ { "success": true }
"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)
:::
+1 -3
View File
@@ -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`. 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. The `/siteverify` endpoint is intended for server-to-server use, so it's not ratelimited by default.
If you're interested in an option to fully disable ratelimiting, let us know using GitHub issues.
## Custom data path ## Custom data path
+3 -3
View File
@@ -4,7 +4,7 @@ outline: [2, 3, 4]
# Widget # 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 ## 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 ```js
import Cap from "@cap.js/widget"; import Cap from "@cap.js/widget";
+3 -3
View File
@@ -9,13 +9,13 @@ By the way, this is a more technical explanation of how Cap works. If you're loo
#### Requesting the challenge #### 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 #### 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: - Each worker attempts to find a valid nonce by repeatedly:
- Combining the salt with different nonce values - Combining the salt with different nonce values
- Computing the SHA-256 hash of this combination - Computing the SHA-256 hash of this combination
+1 -1
View File
@@ -37,7 +37,7 @@ features:
title: Standalone mode title: Standalone mode
details: Run Cap anywhere with a Docker container with analytics & more details: Run Cap anywhere with a Docker container with analytics & more
- icon: 💨 - icon: 💨
title: Invisible title: Programmatic
details: Hide Cap's widget and solve challenges in the background details: Hide Cap's widget and solve challenges in the background
- icon: 🤖 - icon: 🤖
title: M2M title: M2M