@dupecom/botcha-cloudflare 0.9.0 → 0.11.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 +9 -8
- package/dist/agents.d.ts +68 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +123 -0
- package/dist/apps.d.ts +73 -7
- package/dist/apps.d.ts.map +1 -1
- package/dist/apps.js +164 -9
- package/dist/challenges.d.ts.map +1 -1
- package/dist/challenges.js +5 -4
- package/dist/dashboard/api.d.ts +70 -0
- package/dist/dashboard/api.d.ts.map +1 -0
- package/dist/dashboard/api.js +553 -0
- package/dist/dashboard/auth.d.ts +183 -0
- package/dist/dashboard/auth.d.ts.map +1 -0
- package/dist/dashboard/auth.js +401 -0
- package/dist/dashboard/device-code.d.ts +43 -0
- package/dist/dashboard/device-code.d.ts.map +1 -0
- package/dist/dashboard/device-code.js +77 -0
- package/dist/dashboard/index.d.ts +31 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +64 -0
- package/dist/dashboard/landing.d.ts +20 -0
- package/dist/dashboard/landing.d.ts.map +1 -0
- package/dist/dashboard/landing.js +45 -0
- package/dist/dashboard/layout.d.ts +54 -0
- package/dist/dashboard/layout.d.ts.map +1 -0
- package/dist/dashboard/layout.js +55 -0
- package/dist/dashboard/pages.d.ts +11 -0
- package/dist/dashboard/pages.d.ts.map +1 -0
- package/dist/dashboard/pages.js +18 -0
- package/dist/dashboard/styles.d.ts +11 -0
- package/dist/dashboard/styles.d.ts.map +1 -0
- package/dist/dashboard/styles.js +731 -0
- package/dist/email.d.ts +41 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +116 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +666 -189
- package/dist/routes/stream.js +2 -2
- package/dist/static.d.ts +392 -4
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +511 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# @dupecom/botcha-cloudflare
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **BOTCHA** - Prove you're a bot. Humans need not apply.
|
|
4
4
|
>
|
|
5
|
-
> **Cloudflare Workers Edition v0.
|
|
5
|
+
> **Cloudflare Workers Edition v0.11.0** - Identity layer for AI agents
|
|
6
6
|
|
|
7
7
|
Reverse CAPTCHA that verifies AI agents and blocks humans. Running at the edge.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## What's New in v0.11.0
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- **Agent Registry** - Persistent agent identities (POST /v1/agents/register)
|
|
12
|
+
- **Email-tied apps** - Email verification, account recovery, secret rotation
|
|
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
|
|
15
|
+
- **Multi-tenant** - Per-app isolation, scoped tokens, rate limiting
|
|
16
|
+
- **Server-side SDKs** - @botcha/verify (TS) + botcha-verify (Python)
|
|
16
17
|
|
|
17
18
|
## Features
|
|
18
19
|
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOTCHA Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* Agent registration and management:
|
|
5
|
+
* - Crypto-random agent IDs
|
|
6
|
+
* - KV storage for agent metadata
|
|
7
|
+
* - App-scoped agent lists
|
|
8
|
+
* - Fail-open design for resilience
|
|
9
|
+
*/
|
|
10
|
+
export type KVNamespace = {
|
|
11
|
+
get: (key: string, type?: 'text' | 'json' | 'arrayBuffer' | 'stream') => Promise<any>;
|
|
12
|
+
put: (key: string, value: string, options?: {
|
|
13
|
+
expirationTtl?: number;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
delete: (key: string) => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Agent record stored in KV
|
|
19
|
+
*/
|
|
20
|
+
export interface Agent {
|
|
21
|
+
agent_id: string;
|
|
22
|
+
app_id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
operator?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
created_at: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate a crypto-random agent ID
|
|
30
|
+
* Format: 'agent_' + 16 hex chars
|
|
31
|
+
*
|
|
32
|
+
* Example: agent_a1b2c3d4e5f6a7b8
|
|
33
|
+
*/
|
|
34
|
+
export declare function generateAgentId(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Create a new agent and add it to the app's agent list
|
|
37
|
+
*
|
|
38
|
+
* Stores in KV at:
|
|
39
|
+
* - `agent:{agent_id}` — Agent record
|
|
40
|
+
* - `agents:{app_id}` — Array of agent_ids for this app
|
|
41
|
+
*
|
|
42
|
+
* @param kv - KV namespace for storage
|
|
43
|
+
* @param app_id - Parent app that owns this agent
|
|
44
|
+
* @param data - Agent metadata (name, operator, version)
|
|
45
|
+
* @returns Agent record with generated agent_id
|
|
46
|
+
*/
|
|
47
|
+
export declare function createAgent(kv: KVNamespace, app_id: string, data: {
|
|
48
|
+
name: string;
|
|
49
|
+
operator?: string;
|
|
50
|
+
version?: string;
|
|
51
|
+
}): Promise<Agent | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Get agent by agent_id
|
|
54
|
+
*
|
|
55
|
+
* @param kv - KV namespace
|
|
56
|
+
* @param agent_id - The agent ID to retrieve
|
|
57
|
+
* @returns Agent record or null if not found
|
|
58
|
+
*/
|
|
59
|
+
export declare function getAgent(kv: KVNamespace, agent_id: string): Promise<Agent | null>;
|
|
60
|
+
/**
|
|
61
|
+
* List all agents for an app
|
|
62
|
+
*
|
|
63
|
+
* @param kv - KV namespace
|
|
64
|
+
* @param app_id - The app ID to list agents for
|
|
65
|
+
* @returns Array of agent records (empty array if none found)
|
|
66
|
+
*/
|
|
67
|
+
export declare function listAgents(kv: KVNamespace, app_id: string): Promise<Agent[]>;
|
|
68
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAIF;;GAEG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAID;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAwCvB;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,WAAW,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAcvB;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,EAAE,CAAC,CAsBlB"}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BOTCHA Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* Agent registration and management:
|
|
5
|
+
* - Crypto-random agent IDs
|
|
6
|
+
* - KV storage for agent metadata
|
|
7
|
+
* - App-scoped agent lists
|
|
8
|
+
* - Fail-open design for resilience
|
|
9
|
+
*/
|
|
10
|
+
// ============ CRYPTO UTILITIES ============
|
|
11
|
+
/**
|
|
12
|
+
* Generate a crypto-random agent ID
|
|
13
|
+
* Format: 'agent_' + 16 hex chars
|
|
14
|
+
*
|
|
15
|
+
* Example: agent_a1b2c3d4e5f6a7b8
|
|
16
|
+
*/
|
|
17
|
+
export function generateAgentId() {
|
|
18
|
+
const bytes = new Uint8Array(8); // 8 bytes = 16 hex chars
|
|
19
|
+
crypto.getRandomValues(bytes);
|
|
20
|
+
const hexString = Array.from(bytes)
|
|
21
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
22
|
+
.join('');
|
|
23
|
+
return `agent_${hexString}`;
|
|
24
|
+
}
|
|
25
|
+
// ============ AGENT MANAGEMENT ============
|
|
26
|
+
/**
|
|
27
|
+
* Create a new agent and add it to the app's agent list
|
|
28
|
+
*
|
|
29
|
+
* Stores in KV at:
|
|
30
|
+
* - `agent:{agent_id}` — Agent record
|
|
31
|
+
* - `agents:{app_id}` — Array of agent_ids for this app
|
|
32
|
+
*
|
|
33
|
+
* @param kv - KV namespace for storage
|
|
34
|
+
* @param app_id - Parent app that owns this agent
|
|
35
|
+
* @param data - Agent metadata (name, operator, version)
|
|
36
|
+
* @returns Agent record with generated agent_id
|
|
37
|
+
*/
|
|
38
|
+
export async function createAgent(kv, app_id, data) {
|
|
39
|
+
try {
|
|
40
|
+
const agent_id = generateAgentId();
|
|
41
|
+
const agent = {
|
|
42
|
+
agent_id,
|
|
43
|
+
app_id,
|
|
44
|
+
name: data.name,
|
|
45
|
+
operator: data.operator,
|
|
46
|
+
version: data.version,
|
|
47
|
+
created_at: Date.now(),
|
|
48
|
+
};
|
|
49
|
+
// Get existing agent list for this app (if any)
|
|
50
|
+
let agentIds = [];
|
|
51
|
+
try {
|
|
52
|
+
const existingList = await kv.get(`agents:${app_id}`, 'text');
|
|
53
|
+
if (existingList) {
|
|
54
|
+
agentIds = JSON.parse(existingList);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.warn(`Failed to fetch existing agent list for app ${app_id}, starting fresh:`, error);
|
|
59
|
+
// Fail-open: Continue with empty list
|
|
60
|
+
}
|
|
61
|
+
// Add new agent_id to the list
|
|
62
|
+
agentIds.push(agent_id);
|
|
63
|
+
// Store agent record and updated list in parallel
|
|
64
|
+
await Promise.all([
|
|
65
|
+
kv.put(`agent:${agent_id}`, JSON.stringify(agent)),
|
|
66
|
+
kv.put(`agents:${app_id}`, JSON.stringify(agentIds)),
|
|
67
|
+
]);
|
|
68
|
+
return agent;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(`Failed to create agent for app ${app_id}:`, error);
|
|
72
|
+
// Fail-open: Return null instead of throwing
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get agent by agent_id
|
|
78
|
+
*
|
|
79
|
+
* @param kv - KV namespace
|
|
80
|
+
* @param agent_id - The agent ID to retrieve
|
|
81
|
+
* @returns Agent record or null if not found
|
|
82
|
+
*/
|
|
83
|
+
export async function getAgent(kv, agent_id) {
|
|
84
|
+
try {
|
|
85
|
+
const data = await kv.get(`agent:${agent_id}`, 'text');
|
|
86
|
+
if (!data) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return JSON.parse(data);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`Failed to get agent ${agent_id}:`, error);
|
|
93
|
+
// Fail-open: Return null instead of throwing
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* List all agents for an app
|
|
99
|
+
*
|
|
100
|
+
* @param kv - KV namespace
|
|
101
|
+
* @param app_id - The app ID to list agents for
|
|
102
|
+
* @returns Array of agent records (empty array if none found)
|
|
103
|
+
*/
|
|
104
|
+
export async function listAgents(kv, app_id) {
|
|
105
|
+
try {
|
|
106
|
+
// Get the list of agent IDs for this app
|
|
107
|
+
const agentIdsData = await kv.get(`agents:${app_id}`, 'text');
|
|
108
|
+
if (!agentIdsData) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const agentIds = JSON.parse(agentIdsData);
|
|
112
|
+
// Fetch all agent records in parallel
|
|
113
|
+
const agentPromises = agentIds.map(agent_id => getAgent(kv, agent_id));
|
|
114
|
+
const agents = await Promise.all(agentPromises);
|
|
115
|
+
// Filter out any null results (failed fetches) and return
|
|
116
|
+
return agents.filter((agent) => agent !== null);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error(`Failed to list agents for app ${app_id}:`, error);
|
|
120
|
+
// Fail-open: Return empty array instead of throwing
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
package/dist/apps.d.ts
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* - SHA-256 secret hashing (never store plaintext)
|
|
7
7
|
* - KV storage for app configs
|
|
8
8
|
* - Rate limit tracking per app
|
|
9
|
+
* - Email verification for account recovery
|
|
10
|
+
* - Email→app_id reverse index for recovery lookups
|
|
11
|
+
* - Secret rotation with email notification
|
|
9
12
|
*/
|
|
10
13
|
export type KVNamespace = {
|
|
11
14
|
get: (key: string, type?: 'text' | 'json' | 'arrayBuffer' | 'stream') => Promise<any>;
|
|
@@ -22,6 +25,10 @@ export interface AppConfig {
|
|
|
22
25
|
secret_hash: string;
|
|
23
26
|
created_at: number;
|
|
24
27
|
rate_limit: number;
|
|
28
|
+
email: string;
|
|
29
|
+
email_verified: boolean;
|
|
30
|
+
email_verification_code?: string;
|
|
31
|
+
email_verification_expires?: number;
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
27
34
|
* Result of app creation (includes plaintext secret - only shown once!)
|
|
@@ -29,7 +36,20 @@ export interface AppConfig {
|
|
|
29
36
|
export interface CreateAppResult {
|
|
30
37
|
app_id: string;
|
|
31
38
|
app_secret: string;
|
|
39
|
+
email: string;
|
|
40
|
+
email_verified: boolean;
|
|
41
|
+
verification_required: boolean;
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Public app info returned by getApp (excludes secrets and internal fields)
|
|
45
|
+
*/
|
|
46
|
+
export type PublicAppConfig = {
|
|
47
|
+
app_id: string;
|
|
48
|
+
created_at: number;
|
|
49
|
+
rate_limit: number;
|
|
50
|
+
email: string;
|
|
51
|
+
email_verified: boolean;
|
|
52
|
+
};
|
|
33
53
|
/**
|
|
34
54
|
* Generate a crypto-random app ID
|
|
35
55
|
* Format: 'app_' + 16 hex chars
|
|
@@ -52,6 +72,10 @@ export declare function generateAppSecret(): string;
|
|
|
52
72
|
* @returns SHA-256 hash as hex string
|
|
53
73
|
*/
|
|
54
74
|
export declare function hashSecret(secret: string): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Generate a 6-digit numeric verification code
|
|
77
|
+
*/
|
|
78
|
+
export declare function generateVerificationCode(): string;
|
|
55
79
|
/**
|
|
56
80
|
* Create a new app with crypto-random credentials
|
|
57
81
|
*
|
|
@@ -60,15 +84,52 @@ export declare function hashSecret(secret: string): Promise<string>;
|
|
|
60
84
|
* - app_secret: 'sk_' + 32 hex chars
|
|
61
85
|
*
|
|
62
86
|
* Stores in KV at key `app:{app_id}` with:
|
|
63
|
-
* - app_id
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* - rate_limit (default: 100 req/hour)
|
|
87
|
+
* - app_id, secret_hash (SHA-256), created_at, rate_limit, email, email_verified
|
|
88
|
+
*
|
|
89
|
+
* Also stores email→app_id reverse index at `email:{email}` for recovery lookups.
|
|
67
90
|
*
|
|
68
91
|
* @param kv - KV namespace for storage
|
|
69
|
-
* @
|
|
92
|
+
* @param email - Required owner email address
|
|
93
|
+
* @returns {app_id, app_secret, email, email_verified, verification_required}
|
|
94
|
+
*/
|
|
95
|
+
export declare function createApp(kv: KVNamespace, email: string): Promise<CreateAppResult>;
|
|
96
|
+
/**
|
|
97
|
+
* Get the plaintext verification code for an app (internal use only — for sending via email).
|
|
98
|
+
*
|
|
99
|
+
* This is a separate step because createApp returns the code hash, not the plaintext.
|
|
100
|
+
* Instead, we generate and return code in createApp flow; this function regenerates
|
|
101
|
+
* a new code for resend scenarios.
|
|
70
102
|
*/
|
|
71
|
-
export declare function
|
|
103
|
+
export declare function regenerateVerificationCode(kv: KVNamespace, app_id: string): Promise<{
|
|
104
|
+
code: string;
|
|
105
|
+
} | null>;
|
|
106
|
+
/**
|
|
107
|
+
* Verify email with the 6-digit code
|
|
108
|
+
*
|
|
109
|
+
* @returns { verified: true } on success, { verified: false, reason } on failure
|
|
110
|
+
*/
|
|
111
|
+
export declare function verifyEmailCode(kv: KVNamespace, app_id: string, code: string): Promise<{
|
|
112
|
+
verified: boolean;
|
|
113
|
+
reason?: string;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* Look up app_id by email (for account recovery)
|
|
117
|
+
*
|
|
118
|
+
* Uses the email→app_id reverse index stored in KV.
|
|
119
|
+
* Only works for apps with verified emails.
|
|
120
|
+
*/
|
|
121
|
+
export declare function getAppByEmail(kv: KVNamespace, email: string): Promise<{
|
|
122
|
+
app_id: string;
|
|
123
|
+
email_verified: boolean;
|
|
124
|
+
} | null>;
|
|
125
|
+
/**
|
|
126
|
+
* Rotate app secret — generates a new secret and invalidates the old one.
|
|
127
|
+
*
|
|
128
|
+
* @returns New app_secret (plaintext, only returned once) or null on failure
|
|
129
|
+
*/
|
|
130
|
+
export declare function rotateAppSecret(kv: KVNamespace, app_id: string): Promise<{
|
|
131
|
+
app_secret: string;
|
|
132
|
+
} | null>;
|
|
72
133
|
/**
|
|
73
134
|
* Get app configuration by app_id
|
|
74
135
|
*
|
|
@@ -78,7 +139,12 @@ export declare function createApp(kv: KVNamespace): Promise<CreateAppResult>;
|
|
|
78
139
|
* @param app_id - The app ID to retrieve
|
|
79
140
|
* @returns App config (without secret_hash) or null if not found
|
|
80
141
|
*/
|
|
81
|
-
export declare function getApp(kv: KVNamespace, app_id: string): Promise<
|
|
142
|
+
export declare function getApp(kv: KVNamespace, app_id: string): Promise<PublicAppConfig | null>;
|
|
143
|
+
/**
|
|
144
|
+
* Get raw app config (internal use only — includes secret_hash)
|
|
145
|
+
* Used by validateAppSecret and dashboard auth.
|
|
146
|
+
*/
|
|
147
|
+
export declare function getAppRaw(kv: KVNamespace, app_id: string): Promise<AppConfig | null>;
|
|
82
148
|
/**
|
|
83
149
|
* Validate an app secret against stored hash
|
|
84
150
|
*
|
package/dist/apps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../src/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACtF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAIF;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAO1C;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOhE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAiCxF;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAqBlC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCjD;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAiB7D;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAkBxC;AAED;;;;;;;;GAQG;AACH,wBAAsB,MAAM,CAC1B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAsBjC;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAS3B;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CA6BlB"}
|
package/dist/apps.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* - SHA-256 secret hashing (never store plaintext)
|
|
7
7
|
* - KV storage for app configs
|
|
8
8
|
* - Rate limit tracking per app
|
|
9
|
+
* - Email verification for account recovery
|
|
10
|
+
* - Email→app_id reverse index for recovery lookups
|
|
11
|
+
* - Secret rotation with email notification
|
|
9
12
|
*/
|
|
10
13
|
// ============ CRYPTO UTILITIES ============
|
|
11
14
|
/**
|
|
@@ -51,6 +54,16 @@ export async function hashSecret(secret) {
|
|
|
51
54
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
52
55
|
return hashHex;
|
|
53
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate a 6-digit numeric verification code
|
|
59
|
+
*/
|
|
60
|
+
export function generateVerificationCode() {
|
|
61
|
+
const bytes = new Uint8Array(4);
|
|
62
|
+
crypto.getRandomValues(bytes);
|
|
63
|
+
// Convert to number and mod 1,000,000 to get 6 digits
|
|
64
|
+
const num = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0;
|
|
65
|
+
return (num % 1000000).toString().padStart(6, '0');
|
|
66
|
+
}
|
|
54
67
|
// ============ APP MANAGEMENT ============
|
|
55
68
|
/**
|
|
56
69
|
* Create a new app with crypto-random credentials
|
|
@@ -60,32 +73,156 @@ export async function hashSecret(secret) {
|
|
|
60
73
|
* - app_secret: 'sk_' + 32 hex chars
|
|
61
74
|
*
|
|
62
75
|
* Stores in KV at key `app:{app_id}` with:
|
|
63
|
-
* - app_id
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* - rate_limit (default: 100 req/hour)
|
|
76
|
+
* - app_id, secret_hash (SHA-256), created_at, rate_limit, email, email_verified
|
|
77
|
+
*
|
|
78
|
+
* Also stores email→app_id reverse index at `email:{email}` for recovery lookups.
|
|
67
79
|
*
|
|
68
80
|
* @param kv - KV namespace for storage
|
|
69
|
-
* @
|
|
81
|
+
* @param email - Required owner email address
|
|
82
|
+
* @returns {app_id, app_secret, email, email_verified, verification_required}
|
|
70
83
|
*/
|
|
71
|
-
export async function createApp(kv) {
|
|
84
|
+
export async function createApp(kv, email) {
|
|
72
85
|
const app_id = generateAppId();
|
|
73
86
|
const app_secret = generateAppSecret();
|
|
74
87
|
const secret_hash = await hashSecret(app_secret);
|
|
88
|
+
// Generate email verification code
|
|
89
|
+
const verificationCode = generateVerificationCode();
|
|
90
|
+
const verificationCodeHash = await hashSecret(verificationCode);
|
|
75
91
|
const config = {
|
|
76
92
|
app_id,
|
|
77
93
|
secret_hash,
|
|
78
94
|
created_at: Date.now(),
|
|
79
95
|
rate_limit: 100, // Default: 100 requests/hour
|
|
96
|
+
email,
|
|
97
|
+
email_verified: false,
|
|
98
|
+
email_verification_code: verificationCodeHash,
|
|
99
|
+
email_verification_expires: Date.now() + 10 * 60 * 1000, // 10 minutes
|
|
80
100
|
};
|
|
81
|
-
// Store
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
// Store app config and email→app_id index in parallel
|
|
102
|
+
await Promise.all([
|
|
103
|
+
kv.put(`app:${app_id}`, JSON.stringify(config)),
|
|
104
|
+
kv.put(`email:${email.toLowerCase()}`, app_id),
|
|
105
|
+
]);
|
|
84
106
|
return {
|
|
85
107
|
app_id,
|
|
86
108
|
app_secret, // ONLY returned at creation time!
|
|
109
|
+
email,
|
|
110
|
+
email_verified: false,
|
|
111
|
+
verification_required: true,
|
|
87
112
|
};
|
|
88
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the plaintext verification code for an app (internal use only — for sending via email).
|
|
116
|
+
*
|
|
117
|
+
* This is a separate step because createApp returns the code hash, not the plaintext.
|
|
118
|
+
* Instead, we generate and return code in createApp flow; this function regenerates
|
|
119
|
+
* a new code for resend scenarios.
|
|
120
|
+
*/
|
|
121
|
+
export async function regenerateVerificationCode(kv, app_id) {
|
|
122
|
+
try {
|
|
123
|
+
const data = await kv.get(`app:${app_id}`, 'text');
|
|
124
|
+
if (!data)
|
|
125
|
+
return null;
|
|
126
|
+
const config = JSON.parse(data);
|
|
127
|
+
if (config.email_verified)
|
|
128
|
+
return null; // Already verified
|
|
129
|
+
const code = generateVerificationCode();
|
|
130
|
+
const codeHash = await hashSecret(code);
|
|
131
|
+
config.email_verification_code = codeHash;
|
|
132
|
+
config.email_verification_expires = Date.now() + 10 * 60 * 1000;
|
|
133
|
+
await kv.put(`app:${app_id}`, JSON.stringify(config));
|
|
134
|
+
return { code };
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error(`Failed to regenerate verification code for ${app_id}:`, error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Verify email with the 6-digit code
|
|
143
|
+
*
|
|
144
|
+
* @returns { verified: true } on success, { verified: false, reason } on failure
|
|
145
|
+
*/
|
|
146
|
+
export async function verifyEmailCode(kv, app_id, code) {
|
|
147
|
+
try {
|
|
148
|
+
const data = await kv.get(`app:${app_id}`, 'text');
|
|
149
|
+
if (!data) {
|
|
150
|
+
return { verified: false, reason: 'App not found' };
|
|
151
|
+
}
|
|
152
|
+
const config = JSON.parse(data);
|
|
153
|
+
if (config.email_verified) {
|
|
154
|
+
return { verified: false, reason: 'Email already verified' };
|
|
155
|
+
}
|
|
156
|
+
if (!config.email_verification_code || !config.email_verification_expires) {
|
|
157
|
+
return { verified: false, reason: 'No verification pending' };
|
|
158
|
+
}
|
|
159
|
+
if (Date.now() > config.email_verification_expires) {
|
|
160
|
+
return { verified: false, reason: 'Verification code expired' };
|
|
161
|
+
}
|
|
162
|
+
// Compare hashed codes
|
|
163
|
+
const providedHash = await hashSecret(code);
|
|
164
|
+
if (providedHash !== config.email_verification_code) {
|
|
165
|
+
return { verified: false, reason: 'Invalid verification code' };
|
|
166
|
+
}
|
|
167
|
+
// Mark email as verified, clear verification fields
|
|
168
|
+
config.email_verified = true;
|
|
169
|
+
delete config.email_verification_code;
|
|
170
|
+
delete config.email_verification_expires;
|
|
171
|
+
await kv.put(`app:${app_id}`, JSON.stringify(config));
|
|
172
|
+
return { verified: true };
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.error(`Failed to verify email for ${app_id}:`, error);
|
|
176
|
+
return { verified: false, reason: 'Verification failed' };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Look up app_id by email (for account recovery)
|
|
181
|
+
*
|
|
182
|
+
* Uses the email→app_id reverse index stored in KV.
|
|
183
|
+
* Only works for apps with verified emails.
|
|
184
|
+
*/
|
|
185
|
+
export async function getAppByEmail(kv, email) {
|
|
186
|
+
try {
|
|
187
|
+
const app_id = await kv.get(`email:${email.toLowerCase()}`, 'text');
|
|
188
|
+
if (!app_id)
|
|
189
|
+
return null;
|
|
190
|
+
const data = await kv.get(`app:${app_id}`, 'text');
|
|
191
|
+
if (!data)
|
|
192
|
+
return null;
|
|
193
|
+
const config = JSON.parse(data);
|
|
194
|
+
return {
|
|
195
|
+
app_id: config.app_id,
|
|
196
|
+
email_verified: config.email_verified,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error(`Failed to look up app by email:`, error);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Rotate app secret — generates a new secret and invalidates the old one.
|
|
206
|
+
*
|
|
207
|
+
* @returns New app_secret (plaintext, only returned once) or null on failure
|
|
208
|
+
*/
|
|
209
|
+
export async function rotateAppSecret(kv, app_id) {
|
|
210
|
+
try {
|
|
211
|
+
const data = await kv.get(`app:${app_id}`, 'text');
|
|
212
|
+
if (!data)
|
|
213
|
+
return null;
|
|
214
|
+
const config = JSON.parse(data);
|
|
215
|
+
const new_secret = generateAppSecret();
|
|
216
|
+
const new_hash = await hashSecret(new_secret);
|
|
217
|
+
config.secret_hash = new_hash;
|
|
218
|
+
await kv.put(`app:${app_id}`, JSON.stringify(config));
|
|
219
|
+
return { app_secret: new_secret };
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error(`Failed to rotate secret for ${app_id}:`, error);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
89
226
|
/**
|
|
90
227
|
* Get app configuration by app_id
|
|
91
228
|
*
|
|
@@ -107,6 +244,8 @@ export async function getApp(kv, app_id) {
|
|
|
107
244
|
app_id: config.app_id,
|
|
108
245
|
created_at: config.created_at,
|
|
109
246
|
rate_limit: config.rate_limit,
|
|
247
|
+
email: config.email,
|
|
248
|
+
email_verified: config.email_verified,
|
|
110
249
|
};
|
|
111
250
|
}
|
|
112
251
|
catch (error) {
|
|
@@ -114,6 +253,22 @@ export async function getApp(kv, app_id) {
|
|
|
114
253
|
return null;
|
|
115
254
|
}
|
|
116
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Get raw app config (internal use only — includes secret_hash)
|
|
258
|
+
* Used by validateAppSecret and dashboard auth.
|
|
259
|
+
*/
|
|
260
|
+
export async function getAppRaw(kv, app_id) {
|
|
261
|
+
try {
|
|
262
|
+
const data = await kv.get(`app:${app_id}`, 'text');
|
|
263
|
+
if (!data)
|
|
264
|
+
return null;
|
|
265
|
+
return JSON.parse(data);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error(`Failed to get raw app ${app_id}:`, error);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
117
272
|
/**
|
|
118
273
|
* Validate an app secret against stored hash
|
|
119
274
|
*
|
package/dist/challenges.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"challenges.d.ts","sourceRoot":"","sources":["../src/challenges.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"challenges.d.ts","sourceRoot":"","sources":["../src/challenges.ts"],"names":[],"mappings":"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"}
|
package/dist/challenges.js
CHANGED
|
@@ -120,9 +120,10 @@ export async function generateSpeedChallenge(kv, clientTimestamp, app_id) {
|
|
|
120
120
|
};
|
|
121
121
|
// Store in KV with 5 minute TTL (safety buffer for time checks)
|
|
122
122
|
await storeChallenge(kv, id, challenge, 300);
|
|
123
|
+
const pipelineHint = ' Tip: compute all hashes and submit in a single HTTP request. Sequential shell commands will likely exceed the time limit.';
|
|
123
124
|
const instructions = rttMs > 0
|
|
124
|
-
? `Compute SHA256 of each number, return first 8 hex chars of each. Submit as array. You have ${adjustedTimeLimit}ms (adjusted for your ${rttMs}ms network latency)
|
|
125
|
-
:
|
|
125
|
+
? `Compute SHA256 of each number, return first 8 hex chars of each. Submit as array. You have ${adjustedTimeLimit}ms (adjusted for your ${rttMs}ms network latency).${pipelineHint}`
|
|
126
|
+
: `Compute SHA256 of each number, return first 8 hex chars of each. Submit as array. You have 500ms.${pipelineHint}`;
|
|
126
127
|
return {
|
|
127
128
|
id,
|
|
128
129
|
problems,
|
|
@@ -956,8 +957,8 @@ export async function generateHybridChallenge(kv, clientTimestamp, app_id) {
|
|
|
956
957
|
hybridChallenges.set(id, hybrid);
|
|
957
958
|
}
|
|
958
959
|
const instructions = speedChallenge.rttInfo
|
|
959
|
-
? `Solve ALL speed problems (SHA256) in <${speedChallenge.timeLimit}ms (RTT-adjusted) AND answer ALL reasoning questions. Submit both together.`
|
|
960
|
-
: 'Solve ALL speed problems (SHA256) in <500ms AND answer ALL reasoning questions. Submit both together.';
|
|
960
|
+
? `Solve ALL speed problems (SHA256) in <${speedChallenge.timeLimit}ms (RTT-adjusted) AND answer ALL reasoning questions. Submit both together. Tip: compute all hashes in-process and submit in a single HTTP request.`
|
|
961
|
+
: 'Solve ALL speed problems (SHA256) in <500ms AND answer ALL reasoning questions. Submit both together. Tip: compute all hashes in-process and submit in a single HTTP request.';
|
|
961
962
|
return {
|
|
962
963
|
id,
|
|
963
964
|
speed: {
|