@dupecom/botcha-cloudflare 0.18.0 → 0.20.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 CHANGED
@@ -11,9 +11,9 @@ Reverse CAPTCHA that verifies AI agents and blocks humans. Running at the edge.
11
11
  - **Agent Registry** - Persistent agent identities (POST /v1/agents/register)
12
12
  - **Email-tied apps** - Email verification, account recovery, secret rotation
13
13
  - **Dashboard** - Per-app metrics with agent-first auth (device code flow)
14
- - **JWT security** - 5-min access tokens, refresh tokens, audience claims, IP binding, revocation
14
+ - **JWT security** - 1-hr access tokens, refresh tokens, audience claims, IP binding, revocation
15
15
  - **Multi-tenant** - Per-app isolation, scoped tokens, rate limiting
16
- - **Server-side SDKs** - @botcha/verify (TS) + botcha-verify (Python)
16
+ - **Server-side SDKs** - @dupecom/botcha-verify (TS) + botcha-verify (Python)
17
17
 
18
18
  ## Features
19
19
 
package/dist/apps.d.ts CHANGED
@@ -22,6 +22,7 @@ export type KVNamespace = {
22
22
  */
23
23
  export interface AppConfig {
24
24
  app_id: string;
25
+ name?: string;
25
26
  secret_hash: string;
26
27
  created_at: number;
27
28
  rate_limit: number;
@@ -35,6 +36,7 @@ export interface AppConfig {
35
36
  */
36
37
  export interface CreateAppResult {
37
38
  app_id: string;
39
+ name?: string;
38
40
  app_secret: string;
39
41
  email: string;
40
42
  email_verified: boolean;
@@ -45,6 +47,7 @@ export interface CreateAppResult {
45
47
  */
46
48
  export type PublicAppConfig = {
47
49
  app_id: string;
50
+ name?: string;
48
51
  created_at: number;
49
52
  rate_limit: number;
50
53
  email: string;
@@ -90,9 +93,10 @@ export declare function generateVerificationCode(): string;
90
93
  *
91
94
  * @param kv - KV namespace for storage
92
95
  * @param email - Required owner email address
93
- * @returns {app_id, app_secret, email, email_verified, verification_required}
96
+ * @param name - Optional human-readable label for the app
97
+ * @returns {app_id, name, app_secret, email, email_verified, verification_required}
94
98
  */
95
- export declare function createApp(kv: KVNamespace, email: string): Promise<CreateAppResult>;
99
+ export declare function createApp(kv: KVNamespace, email: string, name?: string): Promise<CreateAppResult>;
96
100
  /**
97
101
  * Get the plaintext verification code for an app (internal use only — for sending via email).
98
102
  *
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"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"}
package/dist/apps.js CHANGED
@@ -79,9 +79,10 @@ export function generateVerificationCode() {
79
79
  *
80
80
  * @param kv - KV namespace for storage
81
81
  * @param email - Required owner email address
82
- * @returns {app_id, app_secret, email, email_verified, verification_required}
82
+ * @param name - Optional human-readable label for the app
83
+ * @returns {app_id, name, app_secret, email, email_verified, verification_required}
83
84
  */
84
- export async function createApp(kv, email) {
85
+ export async function createApp(kv, email, name) {
85
86
  const app_id = generateAppId();
86
87
  const app_secret = generateAppSecret();
87
88
  const secret_hash = await hashSecret(app_secret);
@@ -90,6 +91,7 @@ export async function createApp(kv, email) {
90
91
  const verificationCodeHash = await hashSecret(verificationCode);
91
92
  const config = {
92
93
  app_id,
94
+ ...(name && { name }),
93
95
  secret_hash,
94
96
  created_at: Date.now(),
95
97
  rate_limit: 100, // Default: 100 requests/hour
@@ -105,6 +107,7 @@ export async function createApp(kv, email) {
105
107
  ]);
106
108
  return {
107
109
  app_id,
110
+ ...(name && { name }),
108
111
  app_secret, // ONLY returned at creation time!
109
112
  email,
110
113
  email_verified: false,
@@ -242,6 +245,7 @@ export async function getApp(kv, app_id) {
242
245
  // Return config WITHOUT secret_hash (security)
243
246
  return {
244
247
  app_id: config.app_id,
248
+ ...(config.name && { name: config.name }),
245
249
  created_at: config.created_at,
246
250
  rate_limit: config.rate_limit,
247
251
  email: config.email,
package/dist/auth.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * - JTI (JWT ID) for revocation
6
6
  * - Audience claims for API scoping
7
7
  * - Client IP binding for additional security
8
- * - Short-lived access tokens (5 min) with refresh tokens (1 hour)
8
+ * - Short-lived access tokens (1 hour) with refresh tokens (1 hour)
9
9
  * - Token revocation via KV storage
10
10
  */
11
11
  /**
@@ -61,15 +61,40 @@ export interface TokenGenerationOptions {
61
61
  clientIp?: string;
62
62
  app_id?: string;
63
63
  }
64
+ /**
65
+ * ES256 private key in JWK format
66
+ * { kty: "EC", crv: "P-256", x: "...", y: "...", d: "..." }
67
+ */
68
+ export interface ES256SigningKeyJWK {
69
+ kty: string;
70
+ crv: string;
71
+ x: string;
72
+ y: string;
73
+ d: string;
74
+ kid?: string;
75
+ }
76
+ /**
77
+ * Derive the public key JWK from an ES256 private key JWK.
78
+ * Strips the `d` (private) parameter and sets standard fields.
79
+ * Used by the JWKS endpoint to publish the signing key.
80
+ */
81
+ export declare function getSigningPublicKeyJWK(privateKeyJwk: ES256SigningKeyJWK): Omit<ES256SigningKeyJWK, 'd'> & {
82
+ kid: string;
83
+ use: string;
84
+ alg: string;
85
+ };
64
86
  /**
65
87
  * Generate JWT tokens (access + refresh) after successful challenge verification
66
88
  *
67
- * Access token: 5 minutes, used for API access
68
- * Refresh token: 1 hour, used to get new access tokens
89
+ * Access token: 1 hour, used for API access
90
+ * Refresh token: 1 hour, used to get new access tokens without re-solving challenges
91
+ *
92
+ * When signingKey (ES256 JWK) is provided, tokens are signed with ES256.
93
+ * Otherwise falls back to HS256 with the shared secret (backward compat).
69
94
  */
70
95
  export declare function generateToken(challengeId: string, solveTimeMs: number, secret: string, env?: {
71
96
  CHALLENGES: KVNamespace;
72
- }, options?: TokenGenerationOptions): Promise<TokenCreationResult>;
97
+ }, options?: TokenGenerationOptions, signingKey?: ES256SigningKeyJWK): Promise<TokenCreationResult>;
73
98
  /**
74
99
  * Revoke a token by its JTI
75
100
  *
@@ -81,11 +106,20 @@ export declare function revokeToken(jti: string, env: {
81
106
  /**
82
107
  * Refresh an access token using a valid refresh token
83
108
  *
84
- * Verifies the refresh token, checks revocation, and issues a new access token
109
+ * Verifies the refresh token, checks revocation, and issues a new access token.
110
+ * Supports both ES256 (asymmetric) and HS256 (symmetric) refresh tokens.
85
111
  */
86
112
  export declare function refreshAccessToken(refreshToken: string, env: {
87
113
  CHALLENGES: KVNamespace;
88
- }, secret: string, options?: TokenGenerationOptions): Promise<{
114
+ }, secret: string, options?: TokenGenerationOptions, signingKey?: ES256SigningKeyJWK, publicKey?: {
115
+ kty: string;
116
+ crv: string;
117
+ x: string;
118
+ y: string;
119
+ kid?: string;
120
+ use?: string;
121
+ alg?: string;
122
+ }): Promise<{
89
123
  success: boolean;
90
124
  tokens?: Omit<TokenCreationResult, 'refresh_token' | 'refresh_expires_in'> & {
91
125
  access_token: string;
@@ -96,6 +130,9 @@ export declare function refreshAccessToken(refreshToken: string, env: {
96
130
  /**
97
131
  * Verify a JWT token with security checks
98
132
  *
133
+ * Supports both ES256 (asymmetric) and HS256 (symmetric) tokens.
134
+ * The algorithm is detected from the token's protected header.
135
+ *
99
136
  * Checks:
100
137
  * - Token signature and expiry
101
138
  * - Revocation status (via JTI)
@@ -107,6 +144,14 @@ export declare function verifyToken(token: string, secret: string, env?: {
107
144
  }, options?: {
108
145
  requiredAud?: string;
109
146
  clientIp?: string;
147
+ }, publicKey?: {
148
+ kty: string;
149
+ crv: string;
150
+ x: string;
151
+ y: string;
152
+ kid?: string;
153
+ use?: string;
154
+ alg?: string;
110
155
  }): Promise<{
111
156
  valid: boolean;
112
157
  payload?: BotchaTokenPayload;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/auth.js CHANGED
@@ -5,23 +5,54 @@
5
5
  * - JTI (JWT ID) for revocation
6
6
  * - Audience claims for API scoping
7
7
  * - Client IP binding for additional security
8
- * - Short-lived access tokens (5 min) with refresh tokens (1 hour)
8
+ * - Short-lived access tokens (1 hour) with refresh tokens (1 hour)
9
9
  * - Token revocation via KV storage
10
10
  */
11
- import { SignJWT, jwtVerify } from 'jose';
11
+ import { SignJWT, jwtVerify, importJWK, decodeProtectedHeader } from 'jose';
12
+ /**
13
+ * Derive the public key JWK from an ES256 private key JWK.
14
+ * Strips the `d` (private) parameter and sets standard fields.
15
+ * Used by the JWKS endpoint to publish the signing key.
16
+ */
17
+ export function getSigningPublicKeyJWK(privateKeyJwk) {
18
+ const { d: _d, ...publicKey } = privateKeyJwk;
19
+ return {
20
+ ...publicKey,
21
+ kid: privateKeyJwk.kid || 'botcha-signing-1',
22
+ use: 'sig',
23
+ alg: 'ES256',
24
+ };
25
+ }
12
26
  /**
13
27
  * Generate JWT tokens (access + refresh) after successful challenge verification
14
28
  *
15
- * Access token: 5 minutes, used for API access
16
- * Refresh token: 1 hour, used to get new access tokens
29
+ * Access token: 1 hour, used for API access
30
+ * Refresh token: 1 hour, used to get new access tokens without re-solving challenges
31
+ *
32
+ * When signingKey (ES256 JWK) is provided, tokens are signed with ES256.
33
+ * Otherwise falls back to HS256 with the shared secret (backward compat).
17
34
  */
18
- export async function generateToken(challengeId, solveTimeMs, secret, env, options) {
19
- const encoder = new TextEncoder();
20
- const secretKey = encoder.encode(secret);
35
+ export async function generateToken(challengeId, solveTimeMs, secret, env, options, signingKey) {
36
+ // Determine signing algorithm and key
37
+ let signKey;
38
+ let protectedHeader;
39
+ if (signingKey) {
40
+ // ES256 asymmetric signing
41
+ signKey = (await importJWK(signingKey, 'ES256'));
42
+ protectedHeader = {
43
+ alg: 'ES256',
44
+ kid: signingKey.kid || 'botcha-signing-1',
45
+ };
46
+ }
47
+ else {
48
+ // HS256 symmetric signing (legacy fallback)
49
+ signKey = new TextEncoder().encode(secret);
50
+ protectedHeader = { alg: 'HS256' };
51
+ }
21
52
  // Generate unique JTIs for both tokens
22
53
  const accessJti = crypto.randomUUID();
23
54
  const refreshJti = crypto.randomUUID();
24
- // Access token: 5 minutes
55
+ // Access token: 1 hour
25
56
  const accessTokenPayload = {
26
57
  type: 'botcha-verified',
27
58
  solveTime: solveTimeMs,
@@ -38,11 +69,12 @@ export async function generateToken(challengeId, solveTimeMs, secret, env, optio
38
69
  accessTokenPayload.app_id = options.app_id;
39
70
  }
40
71
  const accessToken = await new SignJWT(accessTokenPayload)
41
- .setProtectedHeader({ alg: 'HS256' })
72
+ .setProtectedHeader(protectedHeader)
42
73
  .setSubject(challengeId)
74
+ .setIssuer('botcha.ai')
43
75
  .setIssuedAt()
44
- .setExpirationTime('5m') // 5 minutes
45
- .sign(secretKey);
76
+ .setExpirationTime('1h') // 1 hour
77
+ .sign(signKey);
46
78
  // Refresh token: 1 hour
47
79
  const refreshTokenPayload = {
48
80
  type: 'botcha-refresh',
@@ -54,16 +86,20 @@ export async function generateToken(challengeId, solveTimeMs, secret, env, optio
54
86
  refreshTokenPayload.app_id = options.app_id;
55
87
  }
56
88
  const refreshToken = await new SignJWT(refreshTokenPayload)
57
- .setProtectedHeader({ alg: 'HS256' })
89
+ .setProtectedHeader(protectedHeader)
58
90
  .setSubject(challengeId)
91
+ .setIssuer('botcha.ai')
59
92
  .setIssuedAt()
60
93
  .setExpirationTime('1h') // 1 hour
61
- .sign(secretKey);
94
+ .sign(signKey);
62
95
  // Store refresh token JTI in KV if env provided (for revocation tracking)
63
96
  // Also store aud, client_ip, and app_id so they carry over on refresh
64
97
  if (env?.CHALLENGES) {
65
98
  try {
66
- const refreshData = { sub: challengeId, iat: Date.now() };
99
+ const refreshData = {
100
+ sub: challengeId,
101
+ iat: Date.now(),
102
+ };
67
103
  if (options?.aud) {
68
104
  refreshData.aud = options.aud;
69
105
  }
@@ -83,7 +119,7 @@ export async function generateToken(challengeId, solveTimeMs, secret, env, optio
83
119
  }
84
120
  return {
85
121
  access_token: accessToken,
86
- expires_in: 300, // 5 minutes in seconds
122
+ expires_in: 3600, // 1 hour in seconds
87
123
  refresh_token: refreshToken,
88
124
  refresh_expires_in: 3600, // 1 hour in seconds
89
125
  };
@@ -105,15 +141,26 @@ export async function revokeToken(jti, env) {
105
141
  /**
106
142
  * Refresh an access token using a valid refresh token
107
143
  *
108
- * Verifies the refresh token, checks revocation, and issues a new access token
144
+ * Verifies the refresh token, checks revocation, and issues a new access token.
145
+ * Supports both ES256 (asymmetric) and HS256 (symmetric) refresh tokens.
109
146
  */
110
- export async function refreshAccessToken(refreshToken, env, secret, options) {
147
+ export async function refreshAccessToken(refreshToken, env, secret, options, signingKey, publicKey) {
111
148
  try {
112
- const encoder = new TextEncoder();
113
- const secretKey = encoder.encode(secret);
149
+ // Detect algorithm from token header and verify accordingly
150
+ const header = decodeProtectedHeader(refreshToken);
151
+ let verifyKey;
152
+ let algorithms;
153
+ if (header.alg === 'ES256' && publicKey) {
154
+ verifyKey = (await importJWK(publicKey, 'ES256'));
155
+ algorithms = ['ES256'];
156
+ }
157
+ else {
158
+ verifyKey = new TextEncoder().encode(secret);
159
+ algorithms = ['HS256'];
160
+ }
114
161
  // Verify refresh token
115
- const { payload } = await jwtVerify(refreshToken, secretKey, {
116
- algorithms: ['HS256'],
162
+ const { payload } = await jwtVerify(refreshToken, verifyKey, {
163
+ algorithms,
117
164
  });
118
165
  // Check token type
119
166
  if (payload.type !== 'botcha-refresh') {
@@ -168,6 +215,20 @@ export async function refreshAccessToken(refreshToken, env, secret, options) {
168
215
  console.error('Failed to verify refresh token in KV:', error);
169
216
  }
170
217
  }
218
+ // Determine signing algorithm and key for the new access token
219
+ let signKey;
220
+ let protectedHeaderObj;
221
+ if (signingKey) {
222
+ signKey = (await importJWK(signingKey, 'ES256'));
223
+ protectedHeaderObj = {
224
+ alg: 'ES256',
225
+ kid: signingKey.kid || 'botcha-signing-1',
226
+ };
227
+ }
228
+ else {
229
+ signKey = new TextEncoder().encode(secret);
230
+ protectedHeaderObj = { alg: 'HS256' };
231
+ }
171
232
  // Generate new access token
172
233
  const newAccessJti = crypto.randomUUID();
173
234
  const accessTokenPayload = {
@@ -189,16 +250,17 @@ export async function refreshAccessToken(refreshToken, env, secret, options) {
189
250
  accessTokenPayload.app_id = effectiveAppId;
190
251
  }
191
252
  const accessToken = await new SignJWT(accessTokenPayload)
192
- .setProtectedHeader({ alg: 'HS256' })
253
+ .setProtectedHeader(protectedHeaderObj)
193
254
  .setSubject(payload.sub || '')
255
+ .setIssuer('botcha.ai')
194
256
  .setIssuedAt()
195
- .setExpirationTime('5m') // 5 minutes
196
- .sign(secretKey);
257
+ .setExpirationTime('1h') // 1 hour
258
+ .sign(signKey);
197
259
  return {
198
260
  success: true,
199
261
  tokens: {
200
262
  access_token: accessToken,
201
- expires_in: 300, // 5 minutes in seconds
263
+ expires_in: 3600, // 1 hour in seconds
202
264
  },
203
265
  };
204
266
  }
@@ -212,18 +274,33 @@ export async function refreshAccessToken(refreshToken, env, secret, options) {
212
274
  /**
213
275
  * Verify a JWT token with security checks
214
276
  *
277
+ * Supports both ES256 (asymmetric) and HS256 (symmetric) tokens.
278
+ * The algorithm is detected from the token's protected header.
279
+ *
215
280
  * Checks:
216
281
  * - Token signature and expiry
217
282
  * - Revocation status (via JTI)
218
283
  * - Audience claim (if provided)
219
284
  * - Client IP binding (if provided)
220
285
  */
221
- export async function verifyToken(token, secret, env, options) {
286
+ export async function verifyToken(token, secret, env, options, publicKey) {
222
287
  try {
223
- const encoder = new TextEncoder();
224
- const secretKey = encoder.encode(secret);
225
- const { payload } = await jwtVerify(token, secretKey, {
226
- algorithms: ['HS256'],
288
+ // Detect algorithm from the token header
289
+ const header = decodeProtectedHeader(token);
290
+ let verifyKey;
291
+ let algorithms;
292
+ if (header.alg === 'ES256' && publicKey) {
293
+ // ES256 asymmetric verification
294
+ verifyKey = (await importJWK(publicKey, 'ES256'));
295
+ algorithms = ['ES256'];
296
+ }
297
+ else {
298
+ // HS256 symmetric verification (legacy/fallback)
299
+ verifyKey = new TextEncoder().encode(secret);
300
+ algorithms = ['HS256'];
301
+ }
302
+ const { payload } = await jwtVerify(token, verifyKey, {
303
+ algorithms,
227
304
  });
228
305
  // Check token type (must be access token, not refresh token)
229
306
  if (payload.type !== 'botcha-verified') {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * BOTCHA API Documentation Page
3
+ *
4
+ * Public API docs at /docs — no authentication required.
5
+ * Renders all endpoints grouped by category with request/response
6
+ * examples, install instructions, and quick-start guides.
7
+ *
8
+ * Follows the ShowcasePage pattern: self-contained JSX with own
9
+ * HTML shell, inline CSS, and the shared DASHBOARD_CSS base.
10
+ */
11
+ import type { FC } from 'hono/jsx';
12
+ export declare const DocsPage: FC<{
13
+ version: string;
14
+ }>;
15
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/dashboard/docs.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAsbnC,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAqpB5C,CAAC"}