From 3513def66122e8749c84d1b3527c875c09c8281b Mon Sep 17 00:00:00 2001 From: Alex Nazaruk Date: Fri, 3 Jul 2026 11:37:26 +0200 Subject: [PATCH] fix: repair broken record_winner API handler handleRecordWinner had two bugs that made the record_winner action always return HTTP 500: 1. Signature mismatch: the dispatcher calls handleRecordWinner(request, data) but the function was declared handleRecordWinner(winnerData), so winnerData was bound to the Request object and winnerData.winnerAddress was always undefined. 2. Undefined 'db': the body called db.recordWinner(...), but db is never imported in this module (and db.recordWinner is a browser-only, localStorage-backed function that cannot run server-side), throwing a ReferenceError on every request. Rewrite handleRecordWinner to mirror the working handleRecordShot: correct (request, winnerData) signature, verify the Bearer JWT, confirm the token wallet matches winnerAddress, then call the existing record_winner_secure RPC (already GRANTed to authenticated and it re-verifies the wallet server-side) via the JWT Supabase client. Co-Authored-By: Claude Opus 4.8 --- src/routes/api/shots/+server.js | 94 ++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/src/routes/api/shots/+server.js b/src/routes/api/shots/+server.js index 332fe64..0593995 100644 --- a/src/routes/api/shots/+server.js +++ b/src/routes/api/shots/+server.js @@ -156,31 +156,99 @@ async function handleRecordShot(request, shotData) { } /** - * Handle winner recording + * Handle winner recording with JWT authentication */ -async function handleRecordWinner(winnerData) { +async function handleRecordWinner(request, winnerData) { try { - console.log('🏆 Recording winner via API:', { + // Validate and extract wallet address from JWT + const authHeader = request.headers.get('authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return json( + { success: false, error: 'Authorization header required' }, + { status: 401 } + ); + } + + const token = authHeader.substring(7); + let walletAddress; + + try { + const payload = verifyJWTSecure(token); + walletAddress = payload.walletAddress || payload.wallet_address || payload.sub; + + if (!walletAddress) { + console.error('❌ JWT payload missing wallet address:', payload); + return json( + { success: false, error: 'Invalid token: no wallet address' }, + { status: 401 } + ); + } + } catch (jwtError) { + console.error('❌ JWT verification failed:', jwtError); + return json( + { success: false, error: 'Invalid or expired token' }, + { status: 401 } + ); + } + + // Verify the wallet address matches the winner data + if (!winnerData.winnerAddress || + walletAddress.toLowerCase() !== winnerData.winnerAddress.toLowerCase()) { + return json( + { success: false, error: 'Wallet address mismatch' }, + { status: 403 } + ); + } + + // Check if server-side Supabase is available + if (!isSupabaseServerAvailable()) { + console.error('❌ Server-side Supabase not configured'); + return json( + { + success: false, + error: 'Server configuration error. Please check environment variables.' + }, + { status: 500 } + ); + } + + console.log('🏆 Recording winner via secure API:', { winnerAddress: winnerData.winnerAddress, amount: winnerData.amount, txHash: winnerData.txHash }); - const result = await db.recordWinner({ - winnerAddress: winnerData.winnerAddress, - amount: winnerData.amount, - txHash: winnerData.txHash, - blockNumber: winnerData.blockNumber, - timestamp: winnerData.timestamp || new Date().toISOString(), - cryptoType: winnerData.cryptoType || 'ETH', - contractAddress: winnerData.contractAddress + // Use Supabase client with JWT authentication + the record_winner_secure RPC, + // mirroring handleRecordShot. The RPC re-verifies the wallet address server-side. + const supabase = getSupabaseJWTClient(token); + + const { data, error } = await supabase.rpc('record_winner_secure', { + p_winner_address: winnerData.winnerAddress.toLowerCase(), + p_amount: winnerData.amount, + p_tx_hash: winnerData.txHash, + p_block_number: winnerData.blockNumber, + p_timestamp: winnerData.timestamp || new Date().toISOString(), + p_crypto_type: winnerData.cryptoType || 'ETH', + p_contract_address: winnerData.contractAddress }); - console.log('✅ Winner recorded successfully via API:', result?.id); + if (error) { + console.error('❌ Supabase winner recording error:', error); + return json( + { + success: false, + error: 'Failed to record winner', + message: error.message + }, + { status: 500 } + ); + } + + console.log('✅ Winner recorded successfully via secure API:', data); return json({ success: true, - winner: result, + winner: data, message: 'Winner recorded successfully' });