From 08ab0002902cf21cfc6a5e6b9e73b205032eaa56 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Tue, 11 Nov 2025 02:16:43 +0300 Subject: [PATCH] Update files --- backend/utils/telegram.js | 24 +++-- frontend/src/App.jsx | 8 ++ frontend/src/utils/api.js | 30 ++++++ frontend/src/utils/initDataChecker.js | 130 ++++++++++++++++++++++++++ moderation/frontend/src/utils/api.js | 30 ++++++ 5 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 frontend/src/utils/initDataChecker.js diff --git a/backend/utils/telegram.js b/backend/utils/telegram.js index 637be12..4a2eb0a 100644 --- a/backend/utils/telegram.js +++ b/backend/utils/telegram.js @@ -2,7 +2,7 @@ const { parse, validate } = require('@telegram-apps/init-data-node'); const crypto = require('crypto'); const config = require('../config'); -const MAX_AUTH_AGE_SECONDS = 5 * 60; +const MAX_AUTH_AGE_SECONDS = 60 * 60; // 1 час /** * Manual validation with base64 padding fix @@ -101,7 +101,8 @@ function validateAndParseInitData(initDataRaw, botToken = null) { console.log('[Telegram] Parsed payload:', { hasUser: !!payload?.user, userId: payload?.user?.id, - authDate: payload?.auth_date, + auth_date: payload?.auth_date, + authDate: payload?.authDate, allKeys: Object.keys(payload) }); @@ -109,18 +110,27 @@ function validateAndParseInitData(initDataRaw, botToken = null) { throw new Error('Отсутствует пользователь в initData'); } - // Check for auth_date - it might be authDate in parsed payload - const authDate = Number(payload.auth_date || payload.authDate); + // Check for authDate (camelCase from library) or auth_date (snake_case) + const authDate = Number(payload.authDate || payload.auth_date); if (!authDate) { - console.error('[Telegram] Missing auth_date in payload:', payload); + console.error('[Telegram] Missing authDate in payload:', payload); throw new Error('Отсутствует auth_date в initData'); } const now = Math.floor(Date.now() / 1000); + const age = Math.abs(now - authDate); - if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) { - throw new Error('Данные авторизации устарели'); + console.log('[Telegram] Auth date check:', { + authDate, + now, + age, + maxAge: MAX_AUTH_AGE_SECONDS, + expired: age > MAX_AUTH_AGE_SECONDS + }); + + if (age > MAX_AUTH_AGE_SECONDS) { + throw new Error(`Данные авторизации устарели (возраст: ${age}с, макс: ${MAX_AUTH_AGE_SECONDS}с)`); } console.log('[Telegram] initData validation complete'); diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9d01581..c1b7f90 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from 'react' import { initTelegramApp } from './utils/telegram' import { verifyAuth } from './utils/api' import { initTheme } from './utils/theme' +import { startInitDataChecker, stopInitDataChecker } from './utils/initDataChecker' import Layout from './components/Layout' import Feed from './pages/Feed' import Search from './pages/Search' @@ -28,6 +29,13 @@ function AppContent() { initAppCalled.current = true initApp() } + + // Запустить проверку initData + startInitDataChecker() + + return () => { + stopInitDataChecker() + } }, []) const waitForInitData = async () => { diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index c0b908a..89e2646 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -42,6 +42,36 @@ api.interceptors.request.use((config) => { return config; }); +// Response interceptor для обработки устаревших токенов +api.interceptors.response.use( + (response) => response, + (error) => { + const status = error?.response?.status; + const errorMessage = error?.response?.data?.error || ''; + + // Если токен устарел или невалиден - перезагрузить приложение + if (status === 401 && ( + errorMessage.includes('устарели') || + errorMessage.includes('expired') || + errorMessage.includes('Неверная подпись') + )) { + console.warn('[API] Auth token expired or invalid, reloading app...'); + + // Показать уведомление пользователю + const tg = window.Telegram?.WebApp; + if (tg?.showAlert) { + tg.showAlert('Сессия устарела. Перезагрузка...', () => { + window.location.reload(); + }); + } else { + setTimeout(() => window.location.reload(), 1000); + } + } + + return Promise.reject(error); + } +); + // Auth API export const signInWithTelegram = async (initData) => { const response = await api.post('/auth/signin', { initData }) diff --git a/frontend/src/utils/initDataChecker.js b/frontend/src/utils/initDataChecker.js new file mode 100644 index 0000000..1663de4 --- /dev/null +++ b/frontend/src/utils/initDataChecker.js @@ -0,0 +1,130 @@ +/** + * Утилита для проверки свежести initData + * Предотвращает запросы с устаревшими токенами + */ + +const MAX_INIT_DATA_AGE = 55 * 60 * 1000; // 55 минут (до истечения часа на бекенде) +const CHECK_INTERVAL = 5 * 60 * 1000; // Проверять каждые 5 минут + +let checkTimer = null; +let lastReloadTime = Date.now(); + +/** + * Извлекает auth_date из initData + */ +function extractAuthDate(initData) { + try { + const params = new URLSearchParams(initData); + const authDateStr = params.get('auth_date'); + return authDateStr ? parseInt(authDateStr, 10) : null; + } catch (error) { + console.error('[InitDataChecker] Failed to parse auth_date:', error); + return null; + } +} + +/** + * Проверяет, устарел ли initData + */ +function isInitDataExpired(initData) { + const authDate = extractAuthDate(initData); + if (!authDate) return false; + + const authTime = authDate * 1000; // Конвертируем в миллисекунды + const age = Date.now() - authTime; + + return age > MAX_INIT_DATA_AGE; +} + +/** + * Перезагружает приложение, если initData устарел + */ +function checkAndReloadIfNeeded() { + const tg = window.Telegram?.WebApp; + const initData = tg?.initData; + + if (!initData) { + console.warn('[InitDataChecker] No initData available'); + return; + } + + if (isInitDataExpired(initData)) { + console.warn('[InitDataChecker] InitData expired, reloading...'); + + // Предотвращаем частые перезагрузки (не чаще раза в минуту) + const timeSinceLastReload = Date.now() - lastReloadTime; + if (timeSinceLastReload < 60 * 1000) { + console.warn('[InitDataChecker] Recent reload detected, skipping...'); + return; + } + + lastReloadTime = Date.now(); + + if (tg?.showAlert) { + tg.showAlert('Обновление сессии...', () => { + window.location.reload(); + }); + } else { + window.location.reload(); + } + } +} + +/** + * Запускает периодическую проверку initData + */ +export function startInitDataChecker() { + // Проверить сразу + checkAndReloadIfNeeded(); + + // Проверять периодически + if (checkTimer) { + clearInterval(checkTimer); + } + + checkTimer = setInterval(checkAndReloadIfNeeded, CHECK_INTERVAL); + + console.log('[InitDataChecker] Started with interval:', CHECK_INTERVAL / 1000, 'seconds'); +} + +/** + * Останавливает проверку + */ +export function stopInitDataChecker() { + if (checkTimer) { + clearInterval(checkTimer); + checkTimer = null; + console.log('[InitDataChecker] Stopped'); + } +} + +/** + * Получает информацию о текущем состоянии initData + */ +export function getInitDataInfo() { + const tg = window.Telegram?.WebApp; + const initData = tg?.initData; + + if (!initData) { + return { available: false }; + } + + const authDate = extractAuthDate(initData); + if (!authDate) { + return { available: true, valid: false }; + } + + const authTime = authDate * 1000; + const age = Date.now() - authTime; + const expired = age > MAX_INIT_DATA_AGE; + + return { + available: true, + valid: true, + authDate: new Date(authTime), + age: Math.floor(age / 1000), // в секундах + expired, + remainingTime: expired ? 0 : Math.floor((MAX_INIT_DATA_AGE - age) / 1000) // в секундах + }; +} + diff --git a/moderation/frontend/src/utils/api.js b/moderation/frontend/src/utils/api.js index 015ed93..93dbd36 100644 --- a/moderation/frontend/src/utils/api.js +++ b/moderation/frontend/src/utils/api.js @@ -23,6 +23,36 @@ api.interceptors.request.use((config) => { return config; }); +// Response interceptor для обработки устаревших токенов +api.interceptors.response.use( + (response) => response, + (error) => { + const status = error?.response?.status; + const errorMessage = error?.response?.data?.error || ''; + + // Если токен устарел или невалиден - перезагрузить приложение + if (status === 401 && ( + errorMessage.includes('устарели') || + errorMessage.includes('expired') || + errorMessage.includes('Неверная подпись') + )) { + console.warn('[Moderation API] Auth token expired or invalid, reloading app...'); + + // Показать уведомление пользователю + const tg = window.Telegram?.WebApp; + if (tg?.showAlert) { + tg.showAlert('Сессия устарела. Перезагрузка...', () => { + window.location.reload(); + }); + } else { + setTimeout(() => window.location.reload(), 1000); + } + } + + return Promise.reject(error); + } +); + export const verifyAuth = () => api.post('/mod-app/auth/verify').then((res) => res.data.user) export const fetchUsers = (params = {}) =>