@cv-challenge/server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @cv-challenge/server
|
|
2
|
+
|
|
3
|
+
Server-side renderer, token manager, and Express adapter for CV Challenge.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @cv-challenge/server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- ffmpeg available in PATH
|
|
14
|
+
- OpenCV for opencv4nodejs
|
|
15
|
+
|
|
16
|
+
## Engine usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import Motion3DChallenge from '@cv-challenge/server';
|
|
20
|
+
|
|
21
|
+
const engine = new Motion3DChallenge(180, 60, 3, 20);
|
|
22
|
+
const { videoBuffer, hitbox, debug } = await engine.generate();
|
|
23
|
+
const ok = engine.validate({ x: 42, y: 12 }, hitbox);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Constructor defaults:
|
|
27
|
+
|
|
28
|
+
- width: 180
|
|
29
|
+
- height: 60
|
|
30
|
+
- durationSec: 3
|
|
31
|
+
- objectCount: 20 (capped at 20)
|
|
32
|
+
|
|
33
|
+
## Express adapter
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import express from 'express';
|
|
37
|
+
import Motion3DChallenge, {
|
|
38
|
+
createChallengeExpressRouter,
|
|
39
|
+
createChallengeTokenManager
|
|
40
|
+
} from '@cv-challenge/server';
|
|
41
|
+
|
|
42
|
+
const app = express();
|
|
43
|
+
app.use(express.json({ limit: '1mb' }));
|
|
44
|
+
|
|
45
|
+
const engine = new Motion3DChallenge();
|
|
46
|
+
const tokenManager = createChallengeTokenManager<{ sessionId: string }>({
|
|
47
|
+
secret: process.env.CHALLENGE_JWT_SECRET ?? 'dev-only-change-me',
|
|
48
|
+
tokenTtlSec: 20,
|
|
49
|
+
successTokenTtlSec: 60
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const router = createChallengeExpressRouter<{ sessionId: string }>({
|
|
53
|
+
challenge: engine,
|
|
54
|
+
tokenManager,
|
|
55
|
+
onVerified: async ({ req }) => {
|
|
56
|
+
const sessionId = String(req.headers['x-session-id'] ?? '');
|
|
57
|
+
if (!sessionId) return null;
|
|
58
|
+
return { expiresInSec: 60, payload: { sessionId } };
|
|
59
|
+
},
|
|
60
|
+
validateSuccessToken: (payload, { req }) => {
|
|
61
|
+
const sessionId = String(req.headers['x-session-id'] ?? '');
|
|
62
|
+
return payload.payload?.sessionId === sessionId;
|
|
63
|
+
},
|
|
64
|
+
debug: 'info'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
app.use(router);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Routes
|
|
71
|
+
|
|
72
|
+
`GET /challenge`
|
|
73
|
+
|
|
74
|
+
- Response: `video/webm`
|
|
75
|
+
- Headers:
|
|
76
|
+
- `X-Challenge-Token`
|
|
77
|
+
- `X-Challenge-Expires-At`
|
|
78
|
+
- `X-Challenge-Expires-In`
|
|
79
|
+
- Optional request header:
|
|
80
|
+
- `X-Challenge-Success-Token`
|
|
81
|
+
|
|
82
|
+
`POST /challenge/verify`
|
|
83
|
+
|
|
84
|
+
- Body: `{ token: string, x: number, y: number }`
|
|
85
|
+
- Response: `{ success, reload, successToken, successTokenExpiresAt, successTokenExpiresIn }`
|
|
86
|
+
|
|
87
|
+
## Token behavior
|
|
88
|
+
|
|
89
|
+
- Challenge tokens are encrypted with the provided secret.
|
|
90
|
+
- Success tokens are encoded only (alg "none"), intended as a short-lived hint to skip cold start.
|
|
91
|
+
- Use `validateSuccessToken` to bind success tokens to your own session or user data.
|
|
92
|
+
- Success tokens are invalidated after 3 consecutive failed verifications tied to them.
|
|
93
|
+
- Failed verification blacklists the challenge token JTI until expiry.
|
|
94
|
+
|
|
95
|
+
## API options
|
|
96
|
+
|
|
97
|
+
`createChallengeTokenManager(options)`
|
|
98
|
+
|
|
99
|
+
- `secret` (required): encryption key for challenge tokens.
|
|
100
|
+
- `tokenTtlSec` (default 20): challenge token lifetime.
|
|
101
|
+
- `successTokenTtlSec` (default 60): success token lifetime.
|
|
102
|
+
- Pass a generic type parameter to type the success token payload.
|
|
103
|
+
|
|
104
|
+
`createChallengeExpressRouter(options)`
|
|
105
|
+
|
|
106
|
+
- `challenge` (required): the engine instance.
|
|
107
|
+
- `tokenManager` (required): token manager from `createChallengeTokenManager`.
|
|
108
|
+
- `onVerified`: optional callback; return `undefined` for default success token, object to override TTL/payload, or `null` to skip.
|
|
109
|
+
- `validateSuccessToken`: optional validator for decoded success token payloads.
|
|
110
|
+
- `debug`: `"none"` | `"error"` | `"info"` (default `"none"`).
|
|
111
|
+
- Pass a matching generic type parameter to type `SuccessTokenPayload`.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cv-challenge/server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Server-side engine and Express adapter for the CV Challenge interactive verification flow.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"verification",
|
|
7
|
+
"challenge",
|
|
8
|
+
"express",
|
|
9
|
+
"opencv"
|
|
10
|
+
],
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"opencv4nodejs": {
|
|
25
|
+
"disableAutoBuild": 1
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"express": "^5.2.1",
|
|
29
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
30
|
+
"jose": "^5.9.3",
|
|
31
|
+
"opencv4nodejs": "^5.6.0",
|
|
32
|
+
"simplex-noise": "^4.0.3"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/express": "^5.0.3",
|
|
36
|
+
"@types/fluent-ffmpeg": "^2.1.25",
|
|
37
|
+
"@types/node": "^22.18.0",
|
|
38
|
+
"typescript": "^5.9.2"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.json"
|
|
42
|
+
}
|
|
43
|
+
}
|