Skip to content

freddiehdxd/yourcaptcha

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yourcaptcha

Behavioral analysis + proof-of-work CAPTCHA. Drop-in "I'm not a robot" protection for any site.

  • No third-party services — runs entirely on your infrastructure
  • Adaptive difficulty — real users solve in ~100ms, bots get punished with up to 20s of compute
  • 18-point bot detection — mouse patterns, keystroke timing, automation flags, headless browser detection
  • Cryptographically signed — HMAC-SHA256 challenges with expiry and replay prevention

Install

npm install yourcaptcha

Quick Start (Next.js)

1. Set your secret

# .env.local
YOURCAPTCHA_SECRET=your-secret-here
# Generate one: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

2. Create API routes

// app/api/captcha/challenge/route.ts
export { challengeHandler as POST } from "yourcaptcha/nextjs";
// app/api/captcha/verify/route.ts
export { verifyHandler as POST } from "yourcaptcha/nextjs";

3. Add the widget to your form

"use client";

import { useState } from "react";
import { CaptchaWidget, useBehaviorCollector } from "yourcaptcha/client";
import type { CaptchaPayload } from "yourcaptcha/client";

export default function RegisterPage() {
  const { getSignals } = useBehaviorCollector();
  const [captcha, setCaptcha] = useState<CaptchaPayload | null>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!captcha) return alert("Please complete the captcha");

    const res = await fetch("/api/register", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ captcha, /* ...other form data */ }),
    });
    // handle response...
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* your form fields */}
      <CaptchaWidget onVerified={setCaptcha} getSignals={getSignals} />
      <button type="submit" disabled={!captcha}>Register</button>
    </form>
  );
}

4. Verify on the server

// app/api/register/route.ts
import { verifyCaptcha } from "yourcaptcha/server";

export async function POST(request: Request) {
  const { captcha, ...formData } = await request.json();

  const result = await verifyCaptcha(captcha, {
    hmacSecret: process.env.YOURCAPTCHA_SECRET!,
  });

  if (!result.verified) {
    return Response.json({ error: result.reason }, { status: 400 });
  }

  // Captcha is valid — proceed with registration
}

Usage with Express / any Node.js server

import express from "express";
import { createChallenge, verifyCaptcha } from "yourcaptcha/server";

const app = express();
app.use(express.json());

const HMAC_SECRET = process.env.YOURCAPTCHA_SECRET!;

app.post("/api/captcha/challenge", (req, res) => {
  const result = createChallenge(
    { hmacSecret: HMAC_SECRET },
    req.body.signals
  );
  res.json({
    algorithm: result.algorithm,
    challenge: result.challenge,
    maxnumber: result.maxnumber,
    salt: result.salt,
    signature: result.signature,
  });
});

app.post("/api/captcha/verify", async (req, res) => {
  const result = await verifyCaptcha(req.body, { hmacSecret: HMAC_SECRET });
  res.json(result);
});

Widget Props

Prop Type Default Description
onVerified (payload: CaptchaPayload) => void required Called when challenge is solved
getSignals () => BehavioralSignals required From useBehaviorCollector()
challengeUrl string "/api/captcha/challenge" Your challenge API endpoint
brandText string "Protected by YourCaptcha" Branding text below checkbox
className string CSS class for the outer button

Server API

createChallenge(options, signals?)

Generates a proof-of-work challenge with adaptive difficulty.

import { createChallenge } from "yourcaptcha/server";

const result = createChallenge({
  hmacSecret: "your-secret",
  expiryMs: 5 * 60 * 1000,      // default: 5 minutes
  defaultMaxNumber: 100_000,     // default difficulty when no signals
}, signals);
// Returns: { algorithm, challenge, maxnumber, salt, signature, riskScore }

verifyCaptcha(payload, options)

Verifies a solved captcha payload with full cryptographic + behavioral validation.

import { verifyCaptcha } from "yourcaptcha/server";

const result = await verifyCaptcha(payload, {
  hmacSecret: "your-secret",
  maxRiskScore: 0.85,     // reject if behavioral score exceeds this
  maxScoreDrift: 0.4,     // reject if signals were tampered
  minSubmitTime: 3000,    // minimum ms between page load and submit
  checkReplay: async (challenge) => {
    // Optional: check Redis/DB for replay attacks
    // Return true if challenge was already used
    const used = await redis.get(`captcha:${challenge}`);
    if (used) return true;
    await redis.set(`captcha:${challenge}`, "1", "EX", 300);
    return false;
  },
});
// Returns: { verified: boolean, reason?: string, riskScore?: number }

scoreSignals(signals)

Run the behavioral scoring engine standalone (useful for logging/monitoring).

import { scoreSignals } from "yourcaptcha/server";

const { score, reasons, maxnumber } = scoreSignals(signals);
console.log(`Risk score: ${score}, reasons:`, reasons);

How it works

  1. User loads the page — useBehaviorCollector() starts tracking mouse, keyboard, and environment signals
  2. User clicks "I'm not a robot" — signals are sent to your challenge endpoint
  3. Server scores the signals and returns a SHA-256 proof-of-work challenge with adaptive difficulty
  4. Web Worker solves the challenge off-main-thread
  5. Solved payload is submitted with your form
  6. Server verifies: HMAC signature, proof-of-work solution, expiry, and re-scores behavioral signals

License

MIT

About

Behavioral analysis + proof-of-work CAPTCHA. Drop-in "I'm not a robot" protection for any site.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors