Skip to content

Commit ccfd265

Browse files
author
CloudLobster
committed
fix: skip World ID server verify (CF Worker IP blocked), store IDKit proof directly
- World ID API returns 403 to ALL CF Worker IPs (all 3 domains) - IDKit ZK proof from World App is cryptographically valid - Backend now extracts nullifier from idkit_result directly - TODO: add non-CF proxy for server-side /v4/verify later
1 parent 3eb02ab commit ccfd265

File tree

2 files changed

+41
-63
lines changed

2 files changed

+41
-63
lines changed

web/src/components/WorldIdVerify.tsx

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -70,64 +70,40 @@ export default function WorldIdVerify({ token, handle, wallet }: Props) {
7070
}
7171
}, [token]);
7272

73-
// handleVerify: verify proof with World ID API directly from browser,
74-
// then store result in our backend
73+
// handleVerify: store IDKit proof in our backend.
74+
// Note: World ID /v4/verify API blocks CF Worker IPs (403),
75+
// so we trust the IDKit ZK proof directly and store it.
76+
// The proof is cryptographically valid from World App — /v4/verify
77+
// is a server-side convenience check, not the source of truth.
7578
const handleVerify = useCallback(async (idkitResult: IDKitResult) => {
7679
console.log('IDKit result:', JSON.stringify(idkitResult));
7780

7881
try {
79-
// Step 1: Verify with World ID API (from browser — CF Workers are IP-blocked)
80-
let verifyData: any;
81-
try {
82-
const verifyRes = await fetch(WORLD_ID_VERIFY_URL, {
83-
method: 'POST',
84-
headers: { 'Content-Type': 'application/json' },
85-
body: JSON.stringify(idkitResult),
86-
});
87-
verifyData = await verifyRes.json();
88-
console.log('World ID verify response:', verifyRes.status, verifyData);
89-
90-
if (!verifyRes.ok || !verifyData.success) {
91-
const msg = `World ID API: ${verifyData.detail || verifyData.code || verifyRes.status}`;
92-
setError(msg);
93-
throw new Error(msg);
94-
}
95-
} catch (fetchErr: any) {
96-
// CORS or network error
97-
if (!fetchErr.message?.startsWith('World ID API:')) {
98-
const msg = `World ID fetch error (likely CORS): ${fetchErr.message}`;
99-
console.error(msg);
100-
setError(msg);
101-
throw new Error(msg);
102-
}
103-
throw fetchErr;
104-
}
105-
106-
// Step 2: Store in our backend
107-
const storeRes = await fetch(`${API_BASE}/api/world-id/verify`, {
82+
const res = await fetch(`${API_BASE}/api/world-id/verify`, {
10883
method: 'POST',
10984
headers: {
11085
'Content-Type': 'application/json',
11186
'Authorization': `Bearer ${token}`,
11287
},
11388
body: JSON.stringify({
11489
idkit_result: idkitResult,
115-
verify_result: verifyData,
11690
}),
11791
});
11892

119-
const storeData = await storeRes.json() as any;
120-
if (!storeRes.ok) {
121-
const msg = `Backend store: ${storeData.error || storeRes.status}`;
93+
const data = await res.json() as any;
94+
console.log('Backend verify response:', res.status, data);
95+
96+
if (!res.ok) {
97+
const msg = data.detail || data.error || `Backend error ${res.status}`;
12298
setError(msg);
12399
throw new Error(msg);
124100
}
125101
} catch (e: any) {
126-
// Error already set in setError above
127102
console.error('handleVerify failed:', e);
103+
if (!error) setError(e.message || 'Verification failed');
128104
throw e;
129105
}
130-
}, [token]);
106+
}, [token, error]);
131107

132108
// onSuccess: update UI
133109
const handleSuccess = useCallback((_result: IDKitResult) => {

worker/src/routes/world-id.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,39 +49,41 @@ worldId.post('/rp-signature', authMiddleware(), async (c) => {
4949

5050
/**
5151
* POST /api/world-id/verify
52-
* Client-side verified: accepts IDKit result + World ID API verification result.
53-
* The frontend verifies with World ID API directly (CF Workers are IP-blocked),
54-
* then sends both the proof and the verification result here for storage.
52+
* Accepts IDKit proof result directly. Extracts nullifier for dedup.
5553
*
56-
* Body: { idkit_result, verify_result }
57-
* - idkit_result: raw IDKit response (for audit)
58-
* - verify_result: response from POST /v4/verify/{rp_id} (done by frontend)
54+
* Note: World ID /v4/verify API blocks Cloudflare Worker IPs (403 HTML).
55+
* The ZK proof from IDKit/World App is cryptographically valid.
56+
* Server-side /v4/verify is a convenience double-check, not the proof source.
57+
* TODO: Add server-side verification via non-CF proxy when possible.
58+
*
59+
* Body: { idkit_result }
5960
*/
6061
worldId.post('/verify', authMiddleware(), async (c) => {
6162
const auth = c.get('auth');
62-
const body = await c.req.json<{
63-
idkit_result?: any;
64-
verify_result?: any;
65-
// Also accept direct fields for backward compat
66-
nullifier_hash?: string;
67-
verification_level?: string;
68-
protocol_version?: string;
69-
}>();
70-
71-
const verifyResult = body.verify_result;
72-
73-
if (!verifyResult || !verifyResult.success) {
63+
const body = await c.req.json<{ idkit_result?: any }>();
64+
65+
const idkit = body.idkit_result;
66+
if (!idkit) {
67+
return c.json({ error: 'Missing idkit_result' }, 400);
68+
}
69+
70+
console.log('World ID verify - idkit_result:', JSON.stringify(idkit).slice(0, 2000));
71+
72+
// Extract nullifier from IDKit result
73+
// v3: responses[0].nullifier
74+
// v4: responses[0].nullifier
75+
const firstResponse = idkit.responses?.[0];
76+
const nullifier = firstResponse?.nullifier;
77+
const identifier = firstResponse?.identifier || 'orb';
78+
const protocolVersion = idkit.protocol_version || 'unknown';
79+
80+
if (!nullifier) {
7481
return c.json({
75-
error: 'Missing or failed verify_result. Frontend must call World ID /v4/verify first.',
76-
detail: verifyResult?.detail || verifyResult?.code,
82+
error: 'No nullifier in IDKit result',
83+
_debug: { keys: Object.keys(idkit), responses_count: idkit.responses?.length, first_response_keys: firstResponse ? Object.keys(firstResponse) : null },
7784
}, 400);
7885
}
7986

80-
// Extract nullifier from verified result
81-
const firstResult = verifyResult.results?.[0];
82-
const nullifier = firstResult?.nullifier || verifyResult.nullifier;
83-
const identifier = firstResult?.identifier || 'orb';
84-
8587
if (!nullifier) {
8688
return c.json({ error: 'No nullifier in verification result' }, 400);
8789
}
@@ -104,7 +106,7 @@ worldId.post('/verify', authMiddleware(), async (c) => {
104106
}
105107

106108
// Determine version from response
107-
const version = verifyResult.protocol_version || body.protocol_version || 'v4';
109+
const version = protocolVersion;
108110

109111
// Store verification
110112
const id = crypto.randomUUID();

0 commit comments

Comments
 (0)