improve docs

This commit is contained in:
Tiago
2025-04-12 13:39:56 +01:00
parent 450e59b3c3
commit 4a2a64d4d7
11 changed files with 292 additions and 78 deletions
-1
View File
@@ -36,7 +36,6 @@ export default defineConfig({
{ text: '@cap.js/widget', link: '/guide/widget.md' }
]
},
{ text: "Vulnerabilities", link: "/guide/vulnerabilities.md" },
{ text: "Demo", link: "https://cap-starter.glitch.me/" },
],
-6
View File
@@ -3,9 +3,6 @@ import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import './style.css'
import vitepressNprogress from 'vitepress-plugin-nprogress'
import 'vitepress-plugin-nprogress/lib/css/index.css'
/** @type {import('vitepress').Theme} */
export default {
extends: DefaultTheme,
@@ -13,8 +10,5 @@ export default {
return h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
})
},
enhanceApp: (ctx) => {
vitepressNprogress(ctx)
}
}
+1 -3
View File
@@ -17,6 +17,4 @@ Cap is fully compliant with GDPR and CCPA. It doesn't use cookies or track you i
## Security
- IP addresses are not stored by default
- Requests are stored in-memory in make sure they are not tampered with (hashed tokens only, this is stored in .data/tokensList.json by default)
- Confirmation tokens reset after 20 minutes
- Challenges are only valid for 10 minutes
- Challenges are stored in memory to make sure they are not tampered with (expire after 10 minutes by default), while tokens are stored in a file (hashed tokens only, this is `.data/tokensList.json` by default and expire after 20 minutes)
+12 -8
View File
@@ -1,24 +1,28 @@
# Floating mode
Cap can automatically hide the captcha until the form is submitted. To use this feature, add the `data-cap-floating` attribute to the Cap widget with the query selector of the `cap-widget` element you want to use.
Cap can automatically hide the CAPTCHA until a button is pressed. To use this, add the `data-cap-floating` attribute to the Cap widget with the query selector of the `cap-widget` element you want to use.
```html
<cap-widget id="floating" onsolve="alert(`Verification token: ${event.detail.token}`)" data-cap-api-endpoint="<api endpoint>"></cap-widget>
<cap-widget
id="floating"
onsolve="console.log(`token: ${event.detail.token}`)"
data-cap-api-endpoint="<api endpoint>"
></cap-widget>
<button data-cap-floating="#floating" data-cap-floating-position="bottom">Trigger floating mode</button>
<button data-cap-floating="#floating" data-cap-floating-position="bottom">
Trigger floating mode
</button>
```
You'll also need to import both the Cap library and the floating mode script from JSDelivr:
```html{2}
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap-floating.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap-floating.min.js"></script> <!-- [!code ++] -->
```
> [!NOTE]
> You'll not need to re-import the main Cap library if you've already done it.
The following attributes are supported:
- `data-cap-floating`: The query selector of the `cap-widget` element you want to use.
- `data-cap-floating`: The CSS selector of the `cap-widget` element you want to use.
- `data-cap-floating-position`: The position of the floating widget. Can be `top` or `bottom`.
- `data-cap-floating-offset`: The offset of the floating widget from the trigger element.
+112 -17
View File
@@ -3,12 +3,14 @@ outline: deep
---
# Quickstart
[[toc]]
## Requirements
* **Server-side library:** At least Node 14. Most modern Bun or Deno versions should work too. If you're using Glitch, make sure to set node 14 or higher in your `engines` field in `package.json`
* **Client-side widget:** All modern browsers should be supported, but the build script specifically targets the last 10 versions of Chrome, Firefox, Safari and Edge.
- **Server-side library:** At least Node 14. Most modern Bun or Deno versions should work too. If you're using Glitch, make sure to set Node 14 or higher in your `engines` field in `package.json`. If you don't use a JavaScript runtime, consider using the [Standalone](standalone.md) server.
- **Client-side widget:** All modern browsers should be supported. A [compatibility version](widget.md#compatibility-version) is available too.
## Client-side
@@ -21,22 +23,24 @@ Start by adding importing the Cap widget library from a CDN:
Next, add the `<cap-widget>` component to your HTML.
```html
<cap-widget id="cap" data-cap-api-endpoint="<your cap api endpoint>"></cap-widget>
<cap-widget
id="cap"
data-cap-api-endpoint="<your cap api endpoint>"
></cap-widget>
```
> [!NOTE]
> 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.
> In the server-side example we provided, it's set to `/api`, but you can change this by replacing every `app.post('/api/...', ...)` to `app.post('/<endpoint>/...', ...)`.
Then, in your JavaScript, listen for the `solve` event to capture the token when generated:
```js{3}
const widget = document.querySelector("#cap");
widget.addEventListener("solve", function (e) {
widget.addEventListener("solve", function (e) {
const token = e.detail.token;
// Handle the token as needed
});
```
@@ -44,6 +48,7 @@ widget.addEventListener("solve", function (e) {
Alternatively, you can use `onsolve=""` directly within the widget or wrap the widget in a `<form></form>` (where Cap will automatically submit the token alongside other form data).
## Server-side
Cap is fully self-hosted, so 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. This is easy since we've already pre-made a library to help you generate and validate challenges for you.
Start by installing it using npm or bun:
@@ -55,24 +60,114 @@ npm i @cap.js/server
> [!NOTE]
> It is recommended to use at least Node.js 14 or Bun 1.0.0. You might experience multiple issues on older versions of these runtimes.
Now, you'll need to change your server code to add the routes that Cap needs to work. Here's an example with Express.js:
Now, you'll need to change your server code to add the routes that Cap needs to work. Here's an example:
```js
const express = require('express');
const Cap = require('@cap.js/server');
::: code-group
```js [Elysia]
import { Elysia } from "elysia";
import Cap from "@cap.js/server";
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
new Elysia()
.post("/api/challenge", () => {
return cap.createChallenge();
})
.post("/api/redeem", async ({ body, set }) => {
const { token, solutions } = body;
if (!token || !solutions) {
set.status = 400;
return { success: false };
}
return await cap.redeemChallenge({ token, solutions });
})
.listen(3000);
console.log(`🦊 Elysia is running at http://localhost:3000`);
```
```js [Fastify]
import Fastify from "fastify";
import Cap from "@cap.js/server";
const fastify = Fastify();
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
fastify.post("/api/challenge", (req, res) => {
res.send(cap.createChallenge());
});
fastify.post("/api/redeem", async (req, res) => {
const { token, solutions } = req.body;
if (!token || !solutions) {
return res.code(400).send({ success: false });
}
res.send(await cap.redeemChallenge({ token, solutions }));
});
fastify.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
console.log("Server is running on http://localhost:3000");
});
```
```js [Bun.serve]
import Cap from "@cap.js/server";
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
Bun.serve({
port: 3000,
routes: {
"/api/challenge": {
POST: () => {
return Response.json(cap.createChallenge());
},
},
"/api/redeem": {
POST: async (req) => {
const body = await req.json();
const { token, solutions } = body;
if (!token || !solutions) {
return Response.json({ success: false }, { status: 400 });
}
return Response.json(await cap.redeemChallenge({ token, solutions }));
},
},
},
});
console.log(`Server running at http://localhost:3000`);
```
```js [Express]
import express from "express";
import Cap from "@cap.js/server";
const app = express();
app.use(express.json());
const cap = new Cap({
tokens_store_path: '.data/tokensList.json' // make sure this file has already been created and added to your gitignore
tokens_store_path: ".data/tokensList.json",
});
app.post('/api/challenge', (req, res) => {
app.post("/api/challenge", (req, res) => {
res.json(cap.createChallenge());
});
app.post('/api/redeem', async (req, res) => {
app.post("/api/redeem", async (req, res) => {
const { token, solutions } = req.body;
if (!token || !solutions) {
return res.status(400).json({ success: false });
@@ -81,18 +176,18 @@ app.post('/api/redeem', async (req, res) => {
});
app.listen(3000, () => {
console.log('Listening on port 3000');
})
console.log("Listening on port 3000");
});
```
It should be pretty easy to replicate this code but with other frameworks such as Hono.
:::
### Token Validation
Once the token is generated and captured, you can use it later to validate the user's identity. You can do this by calling `await cap.validateToken` in your server-side code:
```js
await cap.validateToken("...") // returns { success: Boolean }
await cap.validateToken("..."); // returns { success: Boolean }
```
Note that the token will immediately be deleted after this. To prevent this, use `await cap.validateToken("...", { keepToken: true })`.
+18 -5
View File
@@ -2,16 +2,29 @@
You can use `new Cap({ ... })` in your client-side JavaScript to create a new Cap instance and use the `solve()` method to solve the challenge. This is helpful for situations where you don't want the Cap widget to be visible but still want security, e.g. on a social media app when posting something.
Behind the scenes, Cap creates a hidden `cap-widget` element and uses it to solve the challenge.
```js
const cap = new Cap({
apiEndpoint: "/api/"
});
const result = await cap.solve();
console.log(result.token);
const solution = await cap.solve();
console.log(solution.token);
```
You can also set up [event listeners](widget.md#supported-events):
```js
const cap = new Cap({
apiEndpoint: "/api/"
});
cap.addEventListener("progress", (event) => { // [!code focus]
console.log(`Solving... ${event.detail.progress}% done`); // [!code focus]
}); // [!code focus]
```
Behind the scenes, Cap creates a hidden `cap-widget` element and uses it to solve the challenge.
## Supported methods and arguments
The following methods are supported:
@@ -39,4 +52,4 @@ Returns the token from the latest solve
Resets `cap.token`
#### `cap.addEventListener(..., function () { ... })`
Listens for an event for the cap widget. See [Widget: Supported events](widget.md#supported-events)
Listens for an event for the cap widget. See [supported events](widget.md#supported-events)
+122 -7
View File
@@ -1,26 +1,129 @@
# @cap.js/server
`@cap.js/server` is Cap's server-side library. It helps you create and validate challenges for your users. Start by installing it using npm or bun:
`@cap.js/server` is Cap's server-side library. It helps you create and validate challenges for your users. Start by installing it using bun (recommended), npm, or pnpm:
::: code-group
```bash [bun]
bun add @cap.js/server
```
```bash [npm]
npm i @cap.js/server
```
```bash [pnpm]
pnpm i @cap.js/server
```
:::
> [!NOTE]
> It is recommended to use at least Node.js 14 or Bun 1.0.0. You might experience multiple issues on older versions of these runtimes.
> If you're using Glitch, make sure to set node 14 or higher in your `engines` field in `package.json`
> It is recommended to use at least Node.js 14 or Bun v1.0.0. You might experience multiple issues on older versions of these runtimes.
> If you're using Glitch, make sure to set your Node version to 14 in your `engines` field in `package.json`
## Example code
```js
const express = require('express');
const Cap = require('@cap.js/server');
::: code-group
```js [Elysia]
import { Elysia } from 'elysia';
import Cap from "@cap.js/server";
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
new Elysia()
.post("/api/challenge", () => {
return cap.createChallenge();
})
.post("/api/redeem", async ({ body, set }) => {
const { token, solutions } = body;
if (!token || !solutions) {
set.status = 400;
return { success: false };
}
return await cap.redeemChallenge({ token, solutions });
})
.listen(3000);
console.log(`🦊 Elysia is running at http://localhost:3000`);
```
```js [Fastify]
import Fastify from "fastify";
import Cap from "@cap.js/server";
const fastify = Fastify();
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
fastify.post("/api/challenge", (req, res) => {
res.send(
cap.createChallenge()
);
});
fastify.post("/api/redeem", async (req, res) => {
const { token, solutions } = req.body;
if (!token || !solutions) {
return res.code(400).send({ success: false });
}
res.send(await cap.redeemChallenge({ token, solutions }));
});
fastify.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
console.log("Server is running on http://localhost:3000");
});
```
```js [Bun.serve]
import Cap from "@cap.js/server";
const cap = new Cap({
tokens_store_path: ".data/tokensList.json",
});
Bun.serve({
port: 3000,
routes: {
"/api/challenge": {
POST: () => {
return Response.json(cap.createChallenge());
},
},
"/api/redeem": {
POST: async (req) => {
const body = await req.json();
const { token, solutions } = body;
if (!token || !solutions) {
return Response.json({ success: false }, { status: 400 });
}
return Response.json(await cap.redeemChallenge({ token, solutions }));
},
},
},
});
console.log(`Server running at http://localhost:3000`);
```
```js [Express]
import express from "express";
import Cap from "@cap.js/server";
const app = express();
app.use(express.json());
const cap = new Cap({
tokens_store_path: '.data/tokensList.json' // make sure this file has already been created and added to your gitignore
tokens_store_path: '.data/tokensList.json'
});
app.post('/api/challenge', (req, res) => {
@@ -39,6 +142,18 @@ app.listen(3000, () => {
console.log('Listening on port 3000');
})
```
:::
Then, you can verify the CAPTCHA tokens on your server by calling the `await cap.validateToken("<token>")` method. Example:
```js
const { success } = await cap.validateToken("9363220f..."); // [!code highlight]
if (success) {
console.log("Valid token");
} else {
console.log("Invalid token");
}
```
## Supported methods and arguments
+6 -13
View File
@@ -1,17 +1,18 @@
# Cap Standalone client
# Standalone server
## Installation
Cap Standalone is a self-hosted version of Cap's backend that allows you to spin up a server to validate and create challenges so you can use it with languages other than JS.
To install Cap Standalone, you need to have Docker installed on your server. You can find instructions on how to install Docker [here](https://docs.docker.com/get-docker/).
Once you have Docker installed, you can run the following command to pull the image:
To install Cap Standalone, you need to have [Docker](https://docs.docker.com/get-docker/) installed on your server. Once you have it installed, you can run the following command to pull the image:
```bash
docker pull tiago2/cap:latest
```
> [!NOTE]
> Both `x86_64` (amd64) and `arm64` architectures are supported. Docker Engine 20.10 or higher is recommended
Then, to run the server, use the following command:
```bash
@@ -71,18 +72,10 @@ Your request needs to include the following data:
Example using `curl`:
```bash
curl "https://<instance_url>/<key_id>/siteverify" \
-X POST \
-d "secret=<key_secret>&response=<captcha_token>"
# You can also send JSON:
curl "https://<instance_url>/<key_id>/siteverify" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"secret": "<key_secret>",
"response": "<captcha_token>"
}'
-d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
```
Remember to replace:
-2
View File
@@ -1,2 +0,0 @@
# Vulnerabilities
So far there have been no publicly disclosed vulnerabilities. If you find any, please let us know [here](https://github.com/tiagorangel1/cap/security/advisories/new)
+21 -15
View File
@@ -1,9 +1,8 @@
# @cap.js/widget
> [!NOTE]
> [!NOTE]
> **Requirements:** All modern browsers should be supported, but the build script specifically targets the last 10 versions of Chrome, Firefox, Safari and Edge.
`@cap.js/widget` is Cap's client-side library. It includes the `cap-widget` web component, the invisible mode and the Captcha solver. It is recommended to use unpkg to install it:
```html
@@ -13,7 +12,10 @@
You can now use the `<cap-widget>` component in your HTML.
```html
<cap-widget id="cap" data-cap-api-endpoint="<your cap api endpoint>"></cap-widget>
<cap-widget
id="cap"
data-cap-api-endpoint="<your cap api endpoint>"
></cap-widget>
```
> [!NOTE]
@@ -22,34 +24,38 @@ You can now use the `<cap-widget>` component in your HTML.
> [!TIP]
> The following attributes are supported:
>
> * `data-cap-api-endpoint`: API endpoint (required)
> * `data-cap-worker-count`: Number of workers to use (defaults to `navigator.hardwareConcurrency || 8`)
>
> - `data-cap-api-endpoint`: API endpoint (required)
> - `data-cap-worker-count`: Number of workers to use (defaults to `navigator.hardwareConcurrency || 8`)
Then, in your JavaScript, listen for the `solve` event to capture the token when generated:
```js{3}
const widget = document.querySelector("#cap");
widget.addEventListener("solve", function (e) {
widget.addEventListener("solve", function (e) {
const token = e.detail.token;
// Handle the token as needed
});
```
Alternatively, you can use `onsolve=""` directly within the widget or wrap the widget in a `<form></form>` (where Cap will automatically submit the token alongside other form data).
## Compatibility version
Use this script instead if you want compatibility with more browsers but a slightly bigger code size:
```html
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=default,fetch,CustomEvent,TextEncoder,Element,CustomElements,ShadowDOM&flags=gated"></script>
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget/cap.compat.min.js"></script>
```
## Supported events
The following custom events are supported:
The following custom events are supported:
- `solve`: Triggered when the token is generated.
- `error`: Triggered when an error occurs.
- `reset`: Triggered when the widget is reset.
- `progress`: Triggered when the there's a progress update while in verification.
## Invisible mode
For docs on how to use the invisible mode, see ["Invisible mode"](invisible.md).
## Floating mode
For docs on how to use the floating mode, see ["Floating mode"](floating.md).
-1
View File
@@ -6,7 +6,6 @@
"preview": "vitepress preview"
},
"devDependencies": {
"vitepress-plugin-nprogress": "^0.0.4",
"vue": "^3.5.13"
},
"dependencies": {