Update files
This commit is contained in:
parent
d6fcfc5c17
commit
d2361b0e10
|
|
@ -36,20 +36,27 @@ const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный
|
||||||
|
|
||||||
router.post('/signin', strictAuthLimiter, async (req, res) => {
|
router.post('/signin', strictAuthLimiter, async (req, res) => {
|
||||||
try {
|
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 обязателен' });
|
return res.status(400).json({ error: 'initData обязателен' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let telegramUser;
|
let payload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({ telegramUser } = validateAndParseInitData(initData, req));
|
payload = validateAndParseInitData(initDataRaw);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logSecurityEvent('INVALID_INITDATA', req, { reason: error.message });
|
||||||
return res.status(401).json({ error: error.message });
|
return res.status(401).json({ error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const telegramUser = payload.user;
|
||||||
|
|
||||||
if (!validateTelegramId(telegramUser.id)) {
|
if (!validateTelegramId(telegramUser.id)) {
|
||||||
logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id });
|
logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id });
|
||||||
return res.status(400).json({ error: 'Неверный ID пользователя' });
|
return res.status(400).json({ error: 'Неверный ID пользователя' });
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,48 @@
|
||||||
const crypto = require('crypto');
|
const { parse, isValid } = require('@telegram-apps/init-data-node');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
const MAX_AUTH_AGE_SECONDS = 5 * 60;
|
const MAX_AUTH_AGE_SECONDS = 5 * 60;
|
||||||
|
|
||||||
function validateAndParseInitData(initData, req) {
|
function validateAndParseInitData(initDataRaw) {
|
||||||
if (!config.telegramBotToken) {
|
if (!config.telegramBotToken) {
|
||||||
throw new Error('TELEGRAM_BOT_TOKEN не настроен');
|
throw new Error('TELEGRAM_BOT_TOKEN не настроен');
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams(initData);
|
if (!initDataRaw || typeof initDataRaw !== 'string') {
|
||||||
const hash = params.get('hash');
|
throw new Error('initData не передан');
|
||||||
const authDate = Number(params.get('auth_date'));
|
|
||||||
|
|
||||||
if (!hash) {
|
|
||||||
throw new Error('Отсутствует hash в 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) {
|
if (!authDate) {
|
||||||
throw new Error('Отсутствует auth_date в initData');
|
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);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) {
|
if (Math.abs(now - authDate) > MAX_AUTH_AGE_SECONDS) {
|
||||||
throw new Error('Данные авторизации устарели');
|
throw new Error('Данные авторизации устарели');
|
||||||
}
|
}
|
||||||
|
|
||||||
const userParam = params.get('user');
|
return payload;
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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 })
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<title>Nakama Moderation</title>
|
<title>Nakama Moderation</title>
|
||||||
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@ const api = axios.create({
|
||||||
withCredentials: true
|
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) =>
|
export const signInWithTelegram = (initData) =>
|
||||||
api.post('/auth/signin', { initData }).then((res) => res.data.user)
|
api.post('/auth/signin', { initData }).then((res) => res.data.user)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@
|
||||||
"xss-clean": "^0.1.4",
|
"xss-clean": "^0.1.4",
|
||||||
"hpp": "^0.2.3",
|
"hpp": "^0.2.3",
|
||||||
"validator": "^13.11.0",
|
"validator": "^13.11.0",
|
||||||
"cookie-parser": "^1.4.6"
|
"cookie-parser": "^1.4.6",
|
||||||
|
"@telegram-apps/init-data-node": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue