diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs
index 5e58afb..f837d46 100644
--- a/docs/.vitepress/config.mjs
+++ b/docs/.vitepress/config.mjs
@@ -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/" },
],
diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js
index 3096a59..082d795 100644
--- a/docs/.vitepress/theme/index.js
+++ b/docs/.vitepress/theme/index.js
@@ -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)
}
}
diff --git a/docs/guide/effectiveness.md b/docs/guide/effectiveness.md
index 31e5ced..5c86bad 100644
--- a/docs/guide/effectiveness.md
+++ b/docs/guide/effectiveness.md
@@ -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)
\ No newline at end of file
diff --git a/docs/guide/floating.md b/docs/guide/floating.md
index 1246612..979763b 100644
--- a/docs/guide/floating.md
+++ b/docs/guide/floating.md
@@ -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
-
+
-
+
```
You'll also need to import both the Cap library and the floating mode script from JSDelivr:
+
```html{2}
-
+
```
-> [!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.
\ No newline at end of file
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 80e8528..646afe7 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -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 `` component to your HTML.
```html
-
+
```
> [!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('//...', ...)`.
-
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 `` (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 })`.
\ No newline at end of file
diff --git a/docs/guide/invisible.md b/docs/guide/invisible.md
index a2422a3..be3121f 100644
--- a/docs/guide/invisible.md
+++ b/docs/guide/invisible.md
@@ -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)
\ No newline at end of file
+Listens for an event for the cap widget. See [supported events](widget.md#supported-events)
\ No newline at end of file
diff --git a/docs/guide/server.md b/docs/guide/server.md
index 830d05b..5562342 100644
--- a/docs/guide/server.md
+++ b/docs/guide/server.md
@@ -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("")` 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
diff --git a/docs/guide/standalone.md b/docs/guide/standalone.md
index 767e458..81b0a68 100644
--- a/docs/guide/standalone.md
+++ b/docs/guide/standalone.md
@@ -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:////siteverify" \
- -X POST \
- -d "secret=&response="
-
-# You can also send JSON:
curl "https:////siteverify" \
-X POST \
-H "Content-Type: application/json" \
- -d '{
- "secret": "",
- "response": ""
- }'
+ -d '{ "secret": "", "response": "" }'
```
Remember to replace:
diff --git a/docs/guide/vulnerabilities.md b/docs/guide/vulnerabilities.md
deleted file mode 100644
index ef05c30..0000000
--- a/docs/guide/vulnerabilities.md
+++ /dev/null
@@ -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)
\ No newline at end of file
diff --git a/docs/guide/widget.md b/docs/guide/widget.md
index 3bee280..f6b3fb6 100644
--- a/docs/guide/widget.md
+++ b/docs/guide/widget.md
@@ -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 `` component in your HTML.
```html
-
+
```
> [!NOTE]
@@ -22,34 +24,38 @@ You can now use the `` 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 `` (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
+
+
+```
+
## 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).
\ No newline at end of file
diff --git a/docs/package.json b/docs/package.json
index 2746fca..ca74d3a 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -6,7 +6,6 @@
"preview": "vitepress preview"
},
"devDependencies": {
- "vitepress-plugin-nprogress": "^0.0.4",
"vue": "^3.5.13"
},
"dependencies": {