Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/content/docs/de/basics/client-side-rendering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Sie können beim Rendern der Procaptcha-Komponente jeden der folgenden CAPTCHA-T

Verschiedene Frameworks wurden mit Procaptcha integriert. Die Dokumentation für jedes Framework finden Sie unten:

- [React Integration](/de/framework-integrations/angular-integration/)
- [React Integration](/de/framework-integrations/react-integration/)
- [Vue Integration](/de/framework-integrations/vue-integration/)
- [Angular Integration](/de/framework-integrations/angular-integration/)
- [Svelte Integration](/de/framework-integrations/svelte-integration/)
Expand Down
10 changes: 5 additions & 5 deletions src/content/docs/de/basics/invisible-captcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Unsichtbares CAPTCHA befindet sich derzeit in der **Beta**-Phase. Funktionen und
:::

:::note[Stufen-Beschränkung]
Unsichtbares CAPTCHA ist nur für Benutzer der **Pro- und Enterprise**-Stufen verfügbar. Benutzer der kostenlosen Stufe können nicht auf diese Funktion zugreifen.
Unsichtbares CAPTCHA ist nur für Benutzer der **Professional- und Enterprise**-Stufen verfügbar. Benutzer der kostenlosen Stufe können nicht auf diese Funktion zugreifen.
:::

## Übersicht
Expand Down Expand Up @@ -219,18 +219,18 @@ const response = await fetch('https://api.prosopo.io/siteverify', {
},
body: JSON.stringify({
secret: 'your_secret_key',
response: token, // Token from Procaptcha callback
remoteip: userIP // Optional
token: token, // Token from Procaptcha callback
ip: userIP // Optional
})
});

const result = await response.json();
if (result.success) {
if (result.verified) {
// Procaptcha verified successfully
console.log('Verification successful');
} else {
// Verification failed
console.log('Verification failed:', result['error-codes']);
console.log('Verification failed:', result.status);
}
```

Expand Down
13 changes: 9 additions & 4 deletions src/content/docs/de/basics/server-side-verification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ async function verifyToken(token) {
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ secret: 'your_secret_key', token }),
});
return response.json().verified || false; // Return verified field, default to false
const data = await response.json();
return data.verified || false; // Return verified field, default to false
}
```
</section>
Expand Down Expand Up @@ -193,7 +194,8 @@ Um eine Benutzerantwort mit JavaScript / TypeScript zu verifizieren, importieren
die `procaptcha-response` POST-Daten. Typen können aus `@prosopo/types` importiert werden.

```typescript
import {ProsopoServer} from '@prosopo/server'
import {ProsopoServer, getServerConfig} from '@prosopo/server'
import {getPair} from '@prosopo/keyring'
import {ApiParams} from '@prosopo/types'

...
Expand All @@ -204,10 +206,13 @@ const payload = JSON.parse(event.body)
const procaptchaResponse = payload[ApiParams.procaptchaResponse]

// initialise the `ProsopoServer` class
const prosopoServer = new ProsopoServer(config)
const config = getServerConfig()
const pair = getPair(process.env.PROSOPO_SITE_PRIVATE_KEY, config.account.address)
const prosopoServer = new ProsopoServer(config, pair)

// check if the captcha response is verified
if (await prosopoServer.isVerified(procaptchaResponse)) {
const result = await prosopoServer.isVerified(procaptchaResponse)
if (result.verified) {
// perform CAPTCHA protected action
}
```
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/de/welcome/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Sie können ein vollständiges Beispiel zur Implementierung von Procaptcha in ei

Verschiedene Frameworks wurden mit Procaptcha integriert. Die Dokumentation für jedes Framework finden Sie unten:

- [React Integration](/de/framework-integrations/angular-integration/)
- [React Integration](/de/framework-integrations/react-integration/)
- [Vue Integration](/de/framework-integrations/vue-integration/)
- [Angular Integration](/de/framework-integrations/angular-integration/)
- [Svelte Integration](/de/framework-integrations/svelte-integration/)
Expand Down
20 changes: 20 additions & 0 deletions src/content/docs/en/advanced/access-control-rules.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ Match requests from specific countries using ISO 3166-1 alpha-2 country codes.

**Use case:** Apply stricter or more lenient policies for specific geographic regions.

#### ASN

Match the Autonomous System Number (ASN) of the IP's network. Useful for blocking or restricting traffic from a specific hosting provider, ISP, or VPN/proxy network without enumerating every IP they own.

**Format:** Numeric AS number (e.g., `14061`, `32934`)

**Examples:** `14061` (DigitalOcean), `32934` (Meta), `13335` (Cloudflare)

**Use case:** Restrict or block traffic from cloud hosting networks frequently used by bots, or apply policies to whole ISPs in response to abuse patterns.

### Operators

Currently, only the **equals** operator is supported. The condition matches when the field value exactly equals the specified value.
Expand Down Expand Up @@ -296,6 +306,16 @@ JA4 Hash equals "t13d1516h2_8daaf6152771_a278895b5b6a"
Policy: Block
```

### ASN-Based Blocking

Restrict traffic from a whole hosting provider or ISP by AS number:

```typescript
// Restrict traffic from a cloud hosting ASN
ASN equals "14061"
Policy: Proof of Work, difficulty 5
```

## Considerations

### Account-Wide Scope
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/en/advanced/traffic-filter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ Traffic filter checks run before other verification logic (captcha correctness,

## Providing the IP Address

Traffic filters require the user's IP address. Pass it in the `ip` field when calling the server-side verification endpoint:
Traffic filters always run. By default they evaluate the IP recorded when the user initiated the captcha session (the browser's IP at session-start time, captured by the provider when the widget first contacted it).

If the end user's IP may have changed between solving the captcha and your server calling `/verify` (e.g. they moved networks), pass the current IP in the optional `ip` field. The provider then re-resolves IP info against that "now" IP and runs the filters on the fresh result:

```json
{
Expand All @@ -48,8 +50,6 @@ Traffic filters require the user's IP address. Pass it in the `ip` field when ca

See the [Server-side Verification](/en/basics/server-side-verification/#optional-ip-address) docs for details.

If no IP address is provided, traffic filters are still evaluated but only the abusive-network filter will fire (using the connecting IP from the request itself).

## Filter Details

### VPN
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/en/basics/client-side-rendering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ You can choose to implement any of the following types of captcha when rendering

Various frameworks have been integrated with Procaptcha. You can find the documentation for each framework below:

- [React Integration](/en/framework-integrations/angular-integration/)
- [React Integration](/en/framework-integrations/react-integration/)
- [Vue Integration](/en/framework-integrations/vue-integration/)
- [Angular Integration](/en/framework-integrations/angular-integration/)
- [Svelte Integration](/en/framework-integrations/svelte-integration/)
Expand Down
10 changes: 5 additions & 5 deletions src/content/docs/en/basics/invisible-captcha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Invisible CAPTCHA is currently in **beta**. Features and behavior may change in
:::

:::note[Tier Restriction]
Invisible CAPTCHA is only available for **Pro and Enterprise** tier users. Free tier users cannot access this feature.
Invisible CAPTCHA is only available for **Professional and Enterprise** tier users. Free tier users cannot access this feature.
:::

## Overview
Expand Down Expand Up @@ -219,18 +219,18 @@ const response = await fetch('https://api.prosopo.io/siteverify', {
},
body: JSON.stringify({
secret: 'your_secret_key',
response: token, // Token from Procaptcha callback
remoteip: userIP // Optional
token: token, // Token from Procaptcha callback
ip: userIP // Optional
})
});

const result = await response.json();
if (result.success) {
if (result.verified) {
// Procaptcha verified successfully
console.log('Verification successful');
} else {
// Verification failed
console.log('Verification failed:', result['error-codes']);
console.log('Verification failed:', result.status);
}
```

Expand Down
13 changes: 9 additions & 4 deletions src/content/docs/en/basics/server-side-verification.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ on the request. This is optional, but recommended for better accuracy. To do thi
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({secret: 'your_secret_key', token}),
});
return response.json().verified || false; // Return verified field, default to false
const data = await response.json();
return data.verified || false; // Return verified field, default to false
}
```
</section>
Expand Down Expand Up @@ -210,7 +211,8 @@ To verify a user's response using JavaScript / TypeScript, simpy import the `ver
the `procaptcha-response` POST data. Types can be imported from `@prosopo/types`.

```typescript
import {ProsopoServer} from '@prosopo/server'
import {ProsopoServer, getServerConfig} from '@prosopo/server'
import {getPair} from '@prosopo/keyring'
import {ApiParams} from '@prosopo/types'

...
Expand All @@ -221,10 +223,13 @@ const payload = JSON.parse(event.body)
const procaptchaResponse = payload[ApiParams.procaptchaResponse]

// initialise the `ProsopoServer` class
const prosopoServer = new ProsopoServer(config)
const config = getServerConfig()
const pair = getPair(process.env.PROSOPO_SITE_PRIVATE_KEY, config.account.address)
const prosopoServer = new ProsopoServer(config, pair)

// check if the captcha response is verified
if (await prosopoServer.isVerified(procaptchaResponse)) {
const result = await prosopoServer.isVerified(procaptchaResponse)
if (result.verified) {
// perform CAPTCHA protected action
}
```
Expand Down
151 changes: 151 additions & 0 deletions src/content/docs/en/protect-edge/cloudflare-worker.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: Cloudflare Worker
description: Deploy the Prosopo Protect Cloudflare Worker in front of your origin so proxy, VPN, datacenter and Tor traffic is blocked at Cloudflare's edge.
i18nReady: true
---

The Prosopo Protect Cloudflare Worker sits in front of your origin and runs Protect's decision on every request. On `allow` it forwards to your origin; on `block` / `challenge` it returns immediately with a branded interstitial (or JSON error for API calls), no origin fetch.

## What you get

A single JavaScript file, `worker.js`, delivered per site and per environment. It's self-contained — no dependencies, ~50 KB.

## Prerequisites

- A Cloudflare account with **Workers Scripts: Read + Edit** on the API token you'll use.
- [`wrangler`](https://developers.cloudflare.com/workers/wrangler/install-and-update/) installed locally (`npm i -g wrangler`).
- A Cloudflare zone (for a custom domain) or a workers.dev subdomain (fine for testing).
- A CNAME record for your Protect API hostname — see the next section.

## DNS: point your Protect subdomain at Prosopo

The worker talks to your Protect API over HTTPS at a hostname you own — typically `protect.<your-domain>`. Create a CNAME record on your DNS pointing that hostname at Prosopo:

```
protect.<your-domain> CNAME protect.prosopo.io
```

Then enter the same hostname (e.g. `protect.example.com`) in the **CNAME** field of your site's Protect settings on your [Prosopo dashboard](https://portal.prosopo.io/). Prosopo uses this to route TLS requests arriving at that SNI to your site's configuration.

Set `BUMBLEBEE_URL` in `wrangler.toml` (below) to `https://protect.<your-domain>` — the CNAMEd hostname, not `protect.prosopo.io` directly. TLS certificate handling is managed by Prosopo automatically for the CNAMEd hostname.

Verify the DNS is live before deploying the worker:

```bash
dig protect.<your-domain>
# should show a CNAME to protect.prosopo.io and an A record.
```

**Do not use NS delegation.** Only a CNAME record is supported; NS would delegate the whole subdomain to Prosopo's nameservers, which isn't the model.

## Deploy

Cloudflare Workers is configured by a small `wrangler.toml` alongside the bundle. Create one next to `worker.js`:

```toml
name = "prosopo-protect"
main = "worker.js"
compatibility_date = "2025-01-15"
compatibility_flags = ["nodejs_compat"]

# Optional: bind static assets you want the worker to serve when a
# request is allowed. Delete this block if your worker fronts an
# upstream origin instead.
[assets]
directory = "./public"
binding = "ASSETS"
run_worker_first = true

[vars]
BUMBLEBEE_URL = "https://protect.prosopo.io"
```

Then deploy:

```bash
export CLOUDFLARE_API_TOKEN=<token-with-Workers-Scripts-Read+Edit>

# 1. Push the authentication token as a wrangler secret (once per environment)
echo "<your-CLIENT_JWT>" | wrangler secret put CLIENT_JWT

# 2. Deploy
wrangler deploy
```

The worker is now live at `https://<name>.<your-subdomain>.workers.dev`. To attach to a custom hostname, add a **Route** in `wrangler.toml` (e.g. `route = "example.com/*"`) and redeploy.

Your account manager will let you know when a new bundle is available. Replace `worker.js` and re-run `wrangler deploy`. The secret persists across deploys.

## Verify

```bash
WORKER=https://your-worker.workers.dev

# HTML — should serve your origin (200)
curl -sD - -H "Accept: text/html" -H "User-Agent: Mozilla/5.0" "$WORKER/" | head

# JSON, no cookie — should 401 with no-session status
curl -sD - -H "Accept: application/json" "$WORKER/api/anything" | head
# HTTP/2 401
# x-prosopo-status: no-session
# x-prosopo-request-id: <cf-ray>

# From a proxy or VPN (only if you've enabled proxy/VPN blocking on
# your Prosopo dashboard — see the next section) — should 403 with
# the branded interstitial
curl -sD - -H "Accept: text/html" -x http://your-proxy:port "$WORKER/" | head
# HTTP/2 403
# x-prosopo-decision: block
```

`X-Prosopo-Request-Id` is echoed on every response and written to Protect's verdict audit log — grep from a support ticket straight to the verdict.

## What the worker protects

The worker runs Protect logic on every request before serving anything. You can point it at:

- **Static assets** bound via `[assets]` in `wrangler.toml` (as in the example above). Set `run_worker_first = true` so the worker runs before Cloudflare's cache — without this, static files are served directly and Protect never sees the request.
- **An upstream origin** — swap the assets binding for a `fetch(rewrittenUrl, request)` against your upstream, and set the origin host in `wrangler.toml`. The Protect decision above the fetch is unchanged.

## Configuring proxy, VPN and datacenter blocking

Blocking policy lives on your [Prosopo dashboard](https://portal.prosopo.io/), not in the bundle. Two independent rule sources feed the no-cookie edge path:

- **Site settings** — recognised IP categories: `tor`, `datacenter`, `proxy`, `vpn`, `abuser`, `mobile`, `crawler`. Configure per-category verdicts (`allow` / `challenge` / `block`).
- **Tiered access rules** — support the full rule set: IP CIDR, ASN, IP category, country, User-Agent substring, JA4 TLS fingerprint. Rules can be scoped to your site or applied globally by Prosopo.

Either source firing blocks the request at the edge. When both match, the more restrictive verdict wins (`Block > Challenge > Allow`). Policy changes take effect on the next request — no redeploy needed.

## Rotating credentials

The authentication token is stored as a wrangler secret, so rotating it is a one-liner and doesn't require a redeploy:

```bash
echo "<new-CLIENT_JWT>" | wrangler secret put CLIENT_JWT
```

The next request picks up the new value.

## Performance

Protect's API calls from a Cloudflare Worker typically return in under 40 ms (under 2 ms for cached lookups) and are budgeted to a 500 ms hard timeout, with fail-open on transient errors. On an `allow` verdict the request continues to your origin as normal; on `block` or `challenge` the worker returns immediately without an origin fetch, so protected requests are strictly faster than unprotected ones for the blocked path.

## TLS caveats

Cloudflare Workers doesn't expose a way to skip TLS verification on outbound fetches. If the Protect API URL you're configured with has an invalid or expired certificate, `fetch` returns HTTP 526 and Protect can't be reached. Point `BUMBLEBEE_URL` at a hostname with a valid certificate.

## Live logs

While debugging a deploy, `wrangler tail` streams request-by-request logs from the deployed worker:

```bash
wrangler tail --format=pretty
```

## Troubleshooting

- **`wrangler deploy` returns `Authentication error [code: 10000]`** — the token lacks **Workers Scripts: Read + Edit**. "Edit" alone isn't enough; wrangler needs both.
- **Bundle deploys but every request 401s** — the `CLIENT_JWT` secret isn't set or has expired. Re-run `wrangler secret put CLIENT_JWT` and check `wrangler secret list`.
- **HTTP 526 in `wrangler tail`** — the Protect API host's certificate is invalid or expired; see [TLS caveats](#tls-caveats).
- **HTML `Access Denied` served instead of JSON on API paths** — the endpoint's declared content type is HTML. Update the site's `defaultPathType` (or add an endpoint rule) on your Prosopo dashboard.
- **`Unknown site` in logs** — your site isn't registered with Protect. Check your dashboard or contact your account manager.
Loading