Server-side engine and Express adapter for the CV Challenge interactive verification flow.
npm install @cv-challenge/serverServer-side renderer, token manager, and Express adapter for CV Challenge.
``bash`
pnpm add @cv-challenge/server
- ffmpeg available in PATH
- OpenCV for opencv4nodejs
`ts
import Motion3DChallenge from '@cv-challenge/server';
const engine = new Motion3DChallenge(180, 60, 3, 20);
const { videoBuffer, hitbox, debug } = await engine.generate({ failureCount: 3 });
const ok = engine.validate({ x: 42, y: 12 }, hitbox);
`
Constructor defaults:
- width: 180
- height: 60
- durationSec: 3
- objectCount: 20
generate optionally accepts { failureCount } to shrink cube scale and increase cube count as failures rise.
`ts
import express from 'express';
import Motion3DChallenge, {
createChallengeExpressRouter,
createChallengeTokenManager
} from '@cv-challenge/server';
const app = express();
app.use(express.json({ limit: '1mb' }));
const engine = new Motion3DChallenge();
const tokenManager = createChallengeTokenManager<{ sessionId: string }>({
secret: process.env.CHALLENGE_JWT_SECRET ?? 'dev-only-change-me',
tokenTtlSec: 20,
successTokenTtlSec: 60
});
const router = createChallengeExpressRouter<{ sessionId: string }>({
challenge: engine,
tokenManager,
onChallenge: ({ req }) => String(req.headers['x-session-id'] ?? req.ip ?? ''),
onVerified: async ({ req }) => {
const sessionId = String(req.headers['x-session-id'] ?? '');
if (!sessionId) return null;
return { expiresInSec: 60, payload: { sessionId } };
},
validateSuccessToken: (payload, { req }) => {
const sessionId = String(req.headers['x-session-id'] ?? '');
return payload.payload?.sessionId === sessionId;
},
debug: 'info'
});
app.use(router);
`
GET /challenge
- Response: video/webmX-Challenge-Token
- Headers:
- X-Challenge-Expires-At
- X-Challenge-Expires-In
- X-Challenge-Success-Token
- Optional request header:
- onChallenge
- 429 response when identifies an active challenge:{ error: "challenge-already-issued", challengeExpiresAt, challengeExpiresIn }
- Body: Retry-After
- Header: { error: "challenge-backoff", backoffExpiresAt, backoffExpiresIn }
- 429 response when backoff is active for the requester:
- Body: Retry-After
- Header:
POST /challenge/verify
- Body: { token: string, x: number, y: number }{ success, reload, successToken, successTokenExpiresAt, successTokenExpiresIn }
- Response:
- Challenge tokens are encrypted with the provided secret.
- Success tokens are encoded only (alg "none"), intended as a short-lived hint to skip cold start.
- Use validateSuccessToken to bind success tokens to your own session or user data.
- Success tokens are invalidated after 3 consecutive failed verifications tied to them.
- Failed verification blacklists the challenge token JTI until expiry.
createChallengeTokenManager(options)
- secret (required): encryption key for challenge tokens.tokenTtlSec
- (default 20): challenge token lifetime.successTokenTtlSec
- (default 60): success token lifetime.
- Pass a generic type parameter to type the success token payload.
createChallengeExpressRouter(options)
- challenge (required): the engine instance.tokenManager
- (required): token manager from createChallengeTokenManager.onChallenge
- : optional callback that returns a unique key for the requester (session id, IP, etc). When provided, only one active challenge per key is allowed; additional GET /challenge requests return 429 until the prior challenge is verified or expires.backoff
- : optional per-key verification backoff (requires onChallenge). Defaults to a 10-minute window, reset on success, and a schedule of [0, 0, 2000, 5000, 10000, 20000, 35000, 55000, 75000] ms (cap 75s). Expired challenges that are never verified count as a failure within the window. Set enabled: false to disable when onChallenge is present.onVerified
- : optional callback; return undefined for default success token, object to override TTL/payload, or null to skip.validateSuccessToken
- : optional validator for decoded success token payloads.debug
- : "none" | "error" | "info" (default "none").SuccessTokenPayload`.
- Pass a matching generic type parameter to type