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 },