diff --git a/package.json b/package.json index 8ad74d9..146c520 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.155", + "version": "1.0.156", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/version.json b/src/version.json index dee47f3..ccde3e3 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.155", - "buildTime": "2026-02-07T03:35:00.822Z", - "buildNumber": 1770435300822 + "version": "1.0.156", + "buildTime": "2026-02-07T03:44:51.028Z", + "buildNumber": 1770435891028 } diff --git a/supabase/functions/announcement-reaction/index.ts b/supabase/functions/announcement-reaction/index.ts index 75fd2ec..a5553ff 100644 --- a/supabase/functions/announcement-reaction/index.ts +++ b/supabase/functions/announcement-reaction/index.ts @@ -2,7 +2,6 @@ import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createHmac } from 'https://deno.land/std@0.177.0/node/crypto.ts'; -// CORS - Only allow our Telegram MiniApp domain const ALLOWED_ORIGIN = 'https://telegram.pezkuwichain.io'; function getCorsHeaders(): Record { @@ -21,7 +20,6 @@ interface TelegramUser { username?: string; } -// Validate Telegram WebApp initData and extract user function validateInitData(initData: string, botToken: string): TelegramUser | null { try { const params = new URLSearchParams(initData); @@ -30,38 +28,25 @@ function validateInitData(initData: string, botToken: string): TelegramUser | nu params.delete('hash'); - // Sort parameters alphabetically const sortedParams = Array.from(params.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`) .join('\n'); - // Calculate secret key: HMAC-SHA256("WebAppData", bot_token) const secretKey = createHmac('sha256', 'WebAppData').update(botToken).digest(); - - // Calculate hash: HMAC-SHA256(secret_key, data_check_string) const calculatedHash = createHmac('sha256', secretKey).update(sortedParams).digest('hex'); - if (calculatedHash !== hash) { - console.error('[announcement-reaction] Hash mismatch'); - return null; - } + if (calculatedHash !== hash) return null; - // Check auth_date (allow 24 hours) const authDate = parseInt(params.get('auth_date') || '0'); const now = Math.floor(Date.now() / 1000); - if (now - authDate > 86400) { - console.error('[announcement-reaction] Auth data expired'); - return null; - } + if (now - authDate > 86400) return null; - // Parse user data const userStr = params.get('user'); if (!userStr) return null; return JSON.parse(userStr) as TelegramUser; - } catch (e) { - console.error('[announcement-reaction] Validation error:', e); + } catch { return null; } } @@ -69,7 +54,6 @@ function validateInitData(initData: string, botToken: string): TelegramUser | nu serve(async (req) => { const corsHeaders = getCorsHeaders(); - // Handle CORS preflight if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }); } @@ -78,13 +62,6 @@ serve(async (req) => { const body = await req.json(); const { initData, announcementId, reaction } = body; - console.log('[announcement-reaction] Request received:', { - hasInitData: !!initData, - announcementId, - reaction, - }); - - // Validate input if (!initData || !announcementId || !reaction) { return new Response(JSON.stringify({ error: 'Missing required fields' }), { status: 400, @@ -99,20 +76,17 @@ serve(async (req) => { }); } - // Get environment variables const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const botToken = Deno.env.get('TELEGRAM_BOT_TOKEN'); if (!botToken) { - console.error('[announcement-reaction] TELEGRAM_BOT_TOKEN not set!'); return new Response(JSON.stringify({ error: 'Server configuration error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } - // Validate initData and get Telegram user const telegramUser = validateInitData(initData, botToken); if (!telegramUser) { return new Response(JSON.stringify({ error: 'Invalid Telegram data' }), { @@ -122,14 +96,12 @@ serve(async (req) => { } const telegramId = telegramUser.id; - console.log('[announcement-reaction] User validated:', telegramId); - // Create Supabase admin client const supabase = createClient(supabaseUrl, supabaseServiceKey, { auth: { autoRefreshToken: false, persistSession: false }, }); - // Get or create user by telegram_id + // Get or create user let userId: string; const { data: existingUser } = await supabase .from('tg_users') @@ -140,7 +112,6 @@ serve(async (req) => { if (existingUser) { userId = existingUser.id; } else { - // Create user if not exists const { data: newUser, error: createError } = await supabase .from('tg_users') .insert({ @@ -153,14 +124,12 @@ serve(async (req) => { .single(); if (createError || !newUser) { - console.error('[announcement-reaction] Failed to create user:', createError); return new Response(JSON.stringify({ error: 'Failed to create user' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } userId = newUser.id; - console.log('[announcement-reaction] Created new user:', userId); } // Check existing reaction @@ -178,7 +147,6 @@ serve(async (req) => { // Remove reaction (toggle off) await supabase.from('tg_announcement_reactions').delete().eq('id', existing.id); - // Decrement counter const { data: ann } = await supabase .from('tg_announcements') .select(reaction === 'like' ? 'likes' : 'dislikes') @@ -197,7 +165,6 @@ serve(async (req) => { const oldReaction = existing.reaction; await supabase.from('tg_announcement_reactions').update({ reaction }).eq('id', existing.id); - // Update counters const { data: ann } = await supabase .from('tg_announcements') .select('likes, dislikes') @@ -228,7 +195,6 @@ serve(async (req) => { reaction, }); - // Increment counter const { data: ann } = await supabase .from('tg_announcements') .select(reaction === 'like' ? 'likes' : 'dislikes') @@ -244,14 +210,13 @@ serve(async (req) => { resultAction = 'added'; } - // Get updated announcement data + // Get updated data const { data: updatedAnn } = await supabase .from('tg_announcements') .select('likes, dislikes') .eq('id', announcementId) .single(); - // Get user's current reaction const { data: userReaction } = await supabase .from('tg_announcement_reactions') .select('reaction') @@ -269,8 +234,7 @@ serve(async (req) => { }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); - } catch (error) { - console.error('[announcement-reaction] Error:', error); + } catch { return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, diff --git a/supabase/functions/telegram-auth/index.ts b/supabase/functions/telegram-auth/index.ts index fd7b32f..b528e5d 100644 --- a/supabase/functions/telegram-auth/index.ts +++ b/supabase/functions/telegram-auth/index.ts @@ -2,10 +2,9 @@ import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createHmac } from 'https://deno.land/std@0.177.0/node/crypto.ts'; -// CORS - Only allow our Telegram MiniApp domain const ALLOWED_ORIGIN = 'https://telegram.pezkuwichain.io'; -function getCorsHeaders(origin: string | null): Record { +function getCorsHeaders(): Record { return { 'Access-Control-Allow-Origin': ALLOWED_ORIGIN, 'Access-Control-Allow-Headers': @@ -23,173 +22,102 @@ interface TelegramUser { language_code?: string; } -// Validate Telegram WebApp initData function validateInitData(initData: string, botToken: string): TelegramUser | null { try { const params = new URLSearchParams(initData); const hash = params.get('hash'); - if (!hash) { - console.error('[validateInitData] No hash in initData'); - return null; - } + if (!hash) return null; params.delete('hash'); - // Sort parameters alphabetically const sortedParams = Array.from(params.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}=${value}`) .join('\n'); - // Calculate secret key: HMAC-SHA256("WebAppData", bot_token) const secretKey = createHmac('sha256', 'WebAppData').update(botToken).digest(); - - // Calculate hash: HMAC-SHA256(secret_key, data_check_string) const calculatedHash = createHmac('sha256', secretKey).update(sortedParams).digest('hex'); - if (calculatedHash !== hash) { - console.error('[validateInitData] Hash mismatch'); - console.error('[validateInitData] Expected:', hash); - console.error('[validateInitData] Calculated:', calculatedHash); - return null; - } + if (calculatedHash !== hash) return null; - // Check auth_date (allow 24 hours) const authDate = parseInt(params.get('auth_date') || '0'); const now = Math.floor(Date.now() / 1000); - if (now - authDate > 86400) { - console.error('[validateInitData] Auth data expired. Age:', now - authDate, 'seconds'); - return null; - } + if (now - authDate > 86400) return null; - // Parse user data const userStr = params.get('user'); - if (!userStr) { - console.error('[validateInitData] No user in initData'); - return null; - } + if (!userStr) return null; - const user = JSON.parse(userStr) as TelegramUser; - console.log('[validateInitData] Success for user:', user.id, user.first_name); - return user; - } catch (e) { - console.error('[validateInitData] Error:', e); + return JSON.parse(userStr) as TelegramUser; + } catch { return null; } } -// Session token secret (derived from bot token) function getSessionSecret(botToken: string): Uint8Array { return createHmac('sha256', 'SessionTokenSecret').update(botToken).digest(); } -// Generate HMAC-signed session token function generateSessionToken(telegramId: number, botToken: string): string { const payload = { tgId: telegramId, iat: Date.now(), - exp: Date.now() + 24 * 60 * 60 * 1000, // 24 hours + exp: Date.now() + 24 * 60 * 60 * 1000, jti: crypto.randomUUID(), }; - const payloadStr = JSON.stringify(payload); - const payloadB64 = btoa(payloadStr); - - // Sign with HMAC-SHA256 + const payloadB64 = btoa(JSON.stringify(payload)); const secret = getSessionSecret(botToken); const signature = createHmac('sha256', secret).update(payloadB64).digest('hex'); return `${payloadB64}.${signature}`; } -// Verify HMAC-signed session token function verifySessionToken(token: string, botToken: string): number | null { try { const parts = token.split('.'); - if (parts.length !== 2) { - console.error('[verifySessionToken] Invalid token format'); - return null; - } + if (parts.length !== 2) return null; const [payloadB64, signature] = parts; - - // Verify signature const secret = getSessionSecret(botToken); const expectedSig = createHmac('sha256', secret).update(payloadB64).digest('hex'); - if (signature !== expectedSig) { - console.error('[verifySessionToken] Invalid signature'); - return null; - } + if (signature !== expectedSig) return null; - // Parse payload const payload = JSON.parse(atob(payloadB64)); - - // Check expiration - if (Date.now() > payload.exp) { - console.error('[verifySessionToken] Token expired'); - return null; - } + if (Date.now() > payload.exp) return null; return payload.tgId; - } catch (e) { - console.error('[verifySessionToken] Error:', e); + } catch { return null; } } serve(async (req) => { - const method = req.method; - const origin = req.headers.get('origin'); - console.log('[telegram-auth] ===== REQUEST ====='); - console.log('[telegram-auth] Method:', method, '| Origin:', origin); + const corsHeaders = getCorsHeaders(); - const corsHeaders = getCorsHeaders(origin); - - // Handle CORS preflight - if (method === 'OPTIONS') { - console.log('[telegram-auth] CORS preflight - returning OK'); + if (req.method === 'OPTIONS') { return new Response('ok', { headers: corsHeaders }); } - console.log('[telegram-auth] Processing POST request...'); - try { - console.log('[telegram-auth] Parsing JSON body...'); const body = await req.json(); - const hasInitData = !!body.initData; - const hasSessionToken = !!body.sessionToken; - console.log( - '[telegram-auth] Body parsed - initData:', - hasInitData, - '| sessionToken:', - hasSessionToken - ); - const { initData, sessionToken } = body; - // Get environment variables const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const botToken = Deno.env.get('TELEGRAM_BOT_TOKEN'); if (!botToken) { - console.error('[telegram-auth] TELEGRAM_BOT_TOKEN not set'); return new Response(JSON.stringify({ error: 'Server configuration error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } - // Create Supabase admin client const supabase = createClient(supabaseUrl, supabaseServiceKey); - // ======================================== // Method 1: Session token verification - // ======================================== if (sessionToken) { - console.log('[telegram-auth] Method 1: Session token verification'); - const tgId = verifySessionToken(sessionToken, botToken); if (!tgId) { return new Response(JSON.stringify({ error: 'Invalid or expired session' }), { @@ -198,7 +126,6 @@ serve(async (req) => { }); } - // Get user by telegram_id const { data: userData, error: userError } = await supabase .from('users') .select('*') @@ -206,14 +133,12 @@ serve(async (req) => { .single(); if (userError || !userData) { - console.error('[telegram-auth] User not found for tgId:', tgId); return new Response(JSON.stringify({ error: 'User not found' }), { status: 404, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } - // Return user with refreshed token return new Response( JSON.stringify({ user: userData, @@ -223,21 +148,14 @@ serve(async (req) => { ); } - // ======================================== - // Method 2: Telegram WebApp initData verification - // ======================================== + // Method 2: initData verification if (!initData) { - console.error('[telegram-auth] No initData or sessionToken provided'); return new Response(JSON.stringify({ error: 'Missing authentication data' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } - console.log('[telegram-auth] Method 2: initData verification'); - console.log('[telegram-auth] initData length:', initData.length); - - // Validate Telegram data const telegramUser = validateInitData(initData, botToken); if (!telegramUser) { return new Response(JSON.stringify({ error: 'Invalid Telegram data' }), { @@ -246,7 +164,7 @@ serve(async (req) => { }); } - // Check if user exists + // Get or create user const { data: existingUser } = await supabase .from('users') .select('id') @@ -256,10 +174,7 @@ serve(async (req) => { let userId: string; if (existingUser) { - // Update existing user userId = existingUser.id; - console.log('[telegram-auth] Updating existing user:', userId); - await supabase .from('users') .update({ @@ -272,9 +187,6 @@ serve(async (req) => { }) .eq('id', userId); } else { - // Create new user - console.log('[telegram-auth] Creating new user for telegram_id:', telegramUser.id); - const { data: newUser, error: createError } = await supabase .from('users') .insert({ @@ -289,7 +201,6 @@ serve(async (req) => { .single(); if (createError) { - console.error('[telegram-auth] Error creating user:', createError); return new Response(JSON.stringify({ error: 'Failed to create user' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, @@ -297,10 +208,8 @@ serve(async (req) => { } userId = newUser.id; - console.log('[telegram-auth] New user created:', userId); } - // Get the full user data const { data: userData } = await supabase.from('users').select('*').eq('id', userId).single(); return new Response( @@ -311,8 +220,7 @@ serve(async (req) => { }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); - } catch (error) { - console.error('[telegram-auth] Unexpected error:', error); + } catch { return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },