diff --git a/package.json b/package.json index d6cd3ce..a1662e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.150", + "version": "1.0.151", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/hooks/useSupabase.ts b/src/hooks/useSupabase.ts index fc24761..27ec8a6 100644 --- a/src/hooks/useSupabase.ts +++ b/src/hooks/useSupabase.ts @@ -144,7 +144,7 @@ export function useAnnouncements() { }); } -export function useAnnouncementReaction(sessionToken: string | null) { +export function useAnnouncementReaction() { const queryClient = useQueryClient(); return useMutation({ @@ -155,11 +155,12 @@ export function useAnnouncementReaction(sessionToken: string | null) { announcementId: string; reaction: 'like' | 'dislike'; }) => { - if (!sessionToken) throw new Error('Not authenticated'); + const initData = window.Telegram?.WebApp?.initData; + if (!initData) throw new Error('Telegram not available'); - // Call Edge Function for secure reaction handling + // Call Edge Function with initData for validation const { data, error } = await supabase.functions.invoke('announcement-reaction', { - body: { sessionToken, announcementId, reaction }, + body: { initData, announcementId, reaction }, }); if (error) { diff --git a/src/sections/Announcements.tsx b/src/sections/Announcements.tsx index c413b2d..ca324bd 100644 --- a/src/sections/Announcements.tsx +++ b/src/sections/Announcements.tsx @@ -10,46 +10,27 @@ import { import { cn, formatDate, formatNumber } from '@/lib/utils'; import { useTelegram } from '@/hooks/useTelegram'; import { useAnnouncements, useAnnouncementReaction } from '@/hooks/useSupabase'; -import { useAuth } from '@/contexts/AuthContext'; export function AnnouncementsSection() { const { hapticImpact, hapticNotification, openLink } = useTelegram(); - const { - isAuthenticated, - sessionToken, - user, - authError, - signIn, - isLoading: authLoading, - } = useAuth(); - const { data: announcements, isLoading, refetch, isRefetching } = useAnnouncements(); - const reactionMutation = useAnnouncementReaction(sessionToken); - - // Debug: Log auth state - console.warn('[Announcements] Auth state:', { - isAuthenticated, - hasSessionToken: !!sessionToken, - user: user?.first_name, - authError, - }); + const reactionMutation = useAnnouncementReaction(); const handleReaction = (id: string, reaction: 'like' | 'dislike') => { - if (!isAuthenticated) { + if (!window.Telegram?.WebApp?.initData) { hapticNotification('error'); - // Show alert or toast here if UI library allows, for now using browser alert for clarity in dev - // In production better to use a Toast component if (window.Telegram?.WebApp) { - window.Telegram.WebApp.showAlert('Ji bo dengdanê divê tu têketî bî'); - } else { - window.alert('Ji bo dengdanê divê tu têketî bî'); + window.Telegram.WebApp.showAlert('Ji bo dengdanê divê tu di Telegramê de bî'); } return; } hapticImpact('light'); reactionMutation.mutate( { announcementId: id, reaction }, - { onSuccess: () => hapticNotification('success') } + { + onSuccess: () => hapticNotification('success'), + onError: () => hapticNotification('error'), + } ); }; @@ -81,34 +62,6 @@ export function AnnouncementsSection() { - {/* Debug Banner - Remove after fixing */} -
-
- Auth: {isAuthenticated ? 'YES' : 'NO'} | Token: {sessionToken ? 'YES' : 'NO'} | User:{' '} - {user?.first_name || 'null'} -
-
Err: {authError || 'none'}
-
- TG: {window.Telegram?.WebApp ? 'YES' : 'NO'} | initData:{' '} - {window.Telegram?.WebApp?.initData - ? window.Telegram.WebApp.initData.length + ' chars' - : 'EMPTY'} -
-
- Platform: {window.Telegram?.WebApp?.platform || 'unknown'} | Ver:{' '} - {window.Telegram?.WebApp?.version || '?'} -
- {!isAuthenticated && ( - - )} -
- {/* Content */}
{isLoading ? ( diff --git a/src/version.json b/src/version.json index 9e6a553..d060bda 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.150", - "buildTime": "2026-02-07T02:53:36.684Z", - "buildNumber": 1770432816685 + "version": "1.0.151", + "buildTime": "2026-02-07T03:12:47.597Z", + "buildNumber": 1770433967598 } diff --git a/supabase/functions/announcement-reaction/index.ts b/supabase/functions/announcement-reaction/index.ts index 1bbfff8..31f21d3 100644 --- a/supabase/functions/announcement-reaction/index.ts +++ b/supabase/functions/announcement-reaction/index.ts @@ -14,42 +14,54 @@ function getCorsHeaders(): Record { }; } -// Session token secret (must match telegram-auth function) -function getSessionSecret(botToken: string): Uint8Array { - return createHmac('sha256', 'SessionTokenSecret').update(botToken).digest(); +interface TelegramUser { + id: number; + first_name: string; + last_name?: string; + username?: string; } -// Verify HMAC-signed session token -function verifySessionToken(token: string, botToken: string): number | null { +// Validate Telegram WebApp initData and extract user +function validateInitData(initData: string, botToken: string): TelegramUser | null { try { - const parts = token.split('.'); - if (parts.length !== 2) { + const params = new URLSearchParams(initData); + const hash = params.get('hash'); + 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('[announcement-reaction] Hash mismatch'); 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('[announcement-reaction] Invalid signature'); + // 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; } - // Parse payload - const payload = JSON.parse(atob(payloadB64)); + // Parse user data + const userStr = params.get('user'); + if (!userStr) return null; - // Check expiration - if (Date.now() > payload.exp) { - console.error('[announcement-reaction] Token expired'); - return null; - } - - return payload.tgId; + return JSON.parse(userStr) as TelegramUser; } catch (e) { - console.error('[announcement-reaction] Token verification error:', e); + console.error('[announcement-reaction] Validation error:', e); return null; } } @@ -64,21 +76,16 @@ serve(async (req) => { try { const body = await req.json(); - const { sessionToken, announcementId, reaction } = body; + const { initData, announcementId, reaction } = body; console.log('[announcement-reaction] Request received:', { - hasSessionToken: !!sessionToken, + hasInitData: !!initData, announcementId, reaction, }); // Validate input - if (!sessionToken || !announcementId || !reaction) { - console.error('[announcement-reaction] Missing fields:', { - sessionToken: !!sessionToken, - announcementId, - reaction, - }); + if (!initData || !announcementId || !reaction) { return new Response(JSON.stringify({ error: 'Missing required fields' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, @@ -105,17 +112,18 @@ serve(async (req) => { }); } - console.log('[announcement-reaction] Bot token available, verifying session...'); - - // Verify session token - const telegramId = verifySessionToken(sessionToken, botToken); - if (!telegramId) { - return new Response(JSON.stringify({ error: 'Invalid or expired session' }), { + // Validate initData and get Telegram user + const telegramUser = validateInitData(initData, botToken); + if (!telegramUser) { + return new Response(JSON.stringify({ error: 'Invalid Telegram data' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' }, }); } + 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 },