Update files

This commit is contained in:
glpshchn 2025-11-11 02:16:43 +03:00
parent c3f2746723
commit 08ab000290
5 changed files with 215 additions and 7 deletions

View File

@ -2,7 +2,7 @@ const { parse, validate } = require('@telegram-apps/init-data-node');
const crypto = require('crypto'); const crypto = require('crypto');
const config = require('../config'); 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 * Manual validation with base64 padding fix
@ -101,7 +101,8 @@ function validateAndParseInitData(initDataRaw, botToken = null) {
console.log('[Telegram] Parsed payload:', { console.log('[Telegram] Parsed payload:', {
hasUser: !!payload?.user, hasUser: !!payload?.user,
userId: payload?.user?.id, userId: payload?.user?.id,
authDate: payload?.auth_date, auth_date: payload?.auth_date,
authDate: payload?.authDate,
allKeys: Object.keys(payload) allKeys: Object.keys(payload)
}); });
@ -109,18 +110,27 @@ function validateAndParseInitData(initDataRaw, botToken = null) {
throw new Error('Отсутствует пользователь в initData'); throw new Error('Отсутствует пользователь в initData');
} }
// Check for auth_date - it might be authDate in parsed payload // Check for authDate (camelCase from library) or auth_date (snake_case)
const authDate = Number(payload.auth_date || payload.authDate); const authDate = Number(payload.authDate || payload.auth_date);
if (!authDate) { 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'); throw new Error('Отсутствует auth_date в initData');
} }
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const age = Math.abs(now - authDate);
if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) { console.log('[Telegram] Auth date check:', {
throw new Error('Данные авторизации устарели'); 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'); console.log('[Telegram] initData validation complete');

View File

@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from 'react'
import { initTelegramApp } from './utils/telegram' import { initTelegramApp } from './utils/telegram'
import { verifyAuth } from './utils/api' import { verifyAuth } from './utils/api'
import { initTheme } from './utils/theme' import { initTheme } from './utils/theme'
import { startInitDataChecker, stopInitDataChecker } from './utils/initDataChecker'
import Layout from './components/Layout' import Layout from './components/Layout'
import Feed from './pages/Feed' import Feed from './pages/Feed'
import Search from './pages/Search' import Search from './pages/Search'
@ -28,6 +29,13 @@ function AppContent() {
initAppCalled.current = true initAppCalled.current = true
initApp() initApp()
} }
// Запустить проверку initData
startInitDataChecker()
return () => {
stopInitDataChecker()
}
}, []) }, [])
const waitForInitData = async () => { const waitForInitData = async () => {

View File

@ -42,6 +42,36 @@ api.interceptors.request.use((config) => {
return 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 // Auth API
export const signInWithTelegram = async (initData) => { export const signInWithTelegram = async (initData) => {
const response = await api.post('/auth/signin', { initData }) const response = await api.post('/auth/signin', { initData })

View File

@ -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) // в секундах
};
}

View File

@ -23,6 +23,36 @@ api.interceptors.request.use((config) => {
return 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 verifyAuth = () => api.post('/mod-app/auth/verify').then((res) => res.data.user)
export const fetchUsers = (params = {}) => export const fetchUsers = (params = {}) =>