From 4a2a64d4d711941eaa7f9cf65fee769a3a2866f0 Mon Sep 17 00:00:00 2001 From: Tiago <70700766+tiagorangel1@users.noreply.github.com> Date: Sat, 12 Apr 2025 13:39:56 +0100 Subject: [PATCH] improve docs --- docs/.vitepress/config.mjs | 1 - docs/.vitepress/theme/index.js | 6 -- docs/guide/effectiveness.md | 4 +- docs/guide/floating.md | 20 +++-- docs/guide/index.md | 129 ++++++++++++++++++++++++++++----- docs/guide/invisible.md | 23 ++++-- docs/guide/server.md | 129 +++++++++++++++++++++++++++++++-- docs/guide/standalone.md | 19 ++--- docs/guide/vulnerabilities.md | 2 - docs/guide/widget.md | 36 +++++---- docs/package.json | 1 - 11 files changed, 292 insertions(+), 78 deletions(-) delete mode 100644 docs/guide/vulnerabilities.md 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": {