diff --git a/backend/routes/auth.js b/backend/routes/auth.js index ff827ac..af71c19 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -36,20 +36,27 @@ const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный router.post('/signin', strictAuthLimiter, async (req, res) => { try { - const { initData } = req.body || {}; + const authHeader = req.headers.authorization || ''; + const headerInitData = authHeader.startsWith('tma ') ? authHeader.slice(4).trim() : null; + const bodyInitData = typeof req.body?.initData === 'string' ? req.body.initData : null; - if (!initData || typeof initData !== 'string') { + const initDataRaw = headerInitData || bodyInitData; + + if (!initDataRaw) { return res.status(400).json({ error: 'initData обязателен' }); } - let telegramUser; + let payload; try { - ({ telegramUser } = validateAndParseInitData(initData, req)); + payload = validateAndParseInitData(initDataRaw); } catch (error) { + logSecurityEvent('INVALID_INITDATA', req, { reason: error.message }); return res.status(401).json({ error: error.message }); } + const telegramUser = payload.user; + if (!validateTelegramId(telegramUser.id)) { logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id }); return res.status(400).json({ error: 'Неверный ID пользователя' }); diff --git a/backend/utils/telegram.js b/backend/utils/telegram.js index c2dc7fd..79b559d 100644 --- a/backend/utils/telegram.js +++ b/backend/utils/telegram.js @@ -1,64 +1,48 @@ -const crypto = require('crypto'); +const { parse, isValid } = require('@telegram-apps/init-data-node'); const config = require('../config'); const MAX_AUTH_AGE_SECONDS = 5 * 60; -function validateAndParseInitData(initData, req) { +function validateAndParseInitData(initDataRaw) { if (!config.telegramBotToken) { throw new Error('TELEGRAM_BOT_TOKEN не настроен'); } - const params = new URLSearchParams(initData); - const hash = params.get('hash'); - const authDate = Number(params.get('auth_date')); - - if (!hash) { - throw new Error('Отсутствует hash в initData'); + if (!initDataRaw || typeof initDataRaw !== 'string') { + throw new Error('initData не передан'); } + const trimmed = initDataRaw.trim(); + + if (!trimmed.length) { + throw new Error('initData пуст'); + } + + const valid = isValid(trimmed, config.telegramBotToken); + + if (!valid) { + throw new Error('Неверная подпись initData'); + } + + const payload = parse(trimmed); + + if (!payload || !payload.user) { + throw new Error('Отсутствует пользователь в initData'); + } + + const authDate = Number(payload.auth_date); + if (!authDate) { throw new Error('Отсутствует auth_date в initData'); } - const dataCheck = []; - for (const [key, value] of params.entries()) { - if (key === 'hash') continue; - dataCheck.push(`${key}=${value}`); - } - - dataCheck.sort((a, b) => a.localeCompare(b)); - const dataCheckString = dataCheck.join('\n'); - - const secretKey = crypto.createHmac('sha256', 'WebAppData').update(config.telegramBotToken).digest(); - const calculatedHash = crypto.createHmac('sha256', secretKey).update(dataCheckString).digest('hex'); - - if (calculatedHash !== hash) { - throw new Error('Неверная подпись initData'); - } - const now = Math.floor(Date.now() / 1000); + if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) { throw new Error('Данные авторизации устарели'); } - const userParam = params.get('user'); - - if (!userParam) { - throw new Error('Отсутствует пользователь в initData'); - } - - let telegramUser; - try { - telegramUser = JSON.parse(userParam); - } catch (error) { - throw new Error('Некорректный формат user в initData'); - } - - if (!telegramUser || !telegramUser.id) { - throw new Error('Отсутствует ID пользователя в initData'); - } - - return { params, telegramUser }; + return payload; } module.exports = { diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 120f621..54c14b9 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -16,6 +16,17 @@ const api = axios.create({ } }) +api.interceptors.request.use((config) => { + const initData = window.Telegram?.WebApp?.initData; + if (initData) { + config.headers = config.headers || {}; + if (!config.headers.Authorization) { + config.headers.Authorization = `tma ${initData}`; + } + } + return config; +}); + // Auth API export const signInWithTelegram = async (initData) => { const response = await api.post('/auth/signin', { initData }) diff --git a/moderation/frontend/index.html b/moderation/frontend/index.html index 1444e27..6f21784 100644 --- a/moderation/frontend/index.html +++ b/moderation/frontend/index.html @@ -5,6 +5,7 @@ Nakama Moderation +
diff --git a/moderation/frontend/src/utils/api.js b/moderation/frontend/src/utils/api.js index 785a3d7..4d47b13 100644 --- a/moderation/frontend/src/utils/api.js +++ b/moderation/frontend/src/utils/api.js @@ -9,6 +9,17 @@ const api = axios.create({ withCredentials: true }) +api.interceptors.request.use((config) => { + const initData = window.Telegram?.WebApp?.initData; + if (initData) { + config.headers = config.headers || {}; + if (!config.headers.Authorization) { + config.headers.Authorization = `tma ${initData}`; + } + } + return config; +}); + export const signInWithTelegram = (initData) => api.post('/auth/signin', { initData }).then((res) => res.data.user) diff --git a/package.json b/package.json index 3a9c86a..e914654 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "xss-clean": "^0.1.4", "hpp": "^0.2.3", "validator": "^13.11.0", - "cookie-parser": "^1.4.6" + "cookie-parser": "^1.4.6", + "@telegram-apps/init-data-node": "^1.0.0" }, "devDependencies": { "nodemon": "^3.0.1",