diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 8858c8a..76123b5 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -1,16 +1,7 @@ const User = require('../models/User'); const { validateTelegramId } = require('./validator'); const { logSecurityEvent } = require('./logger'); -const config = require('../config'); -const { - ACCESS_COOKIE, - REFRESH_COOKIE, - signAuthTokens, - setAuthCookies, - clearAuthCookies, - verifyAccessToken, - verifyRefreshToken -} = require('../utils/tokens'); +const { validateAndParseInitData } = require('../utils/telegram'); const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot'; const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; @@ -56,72 +47,68 @@ const ensureUserSettings = async (user) => { } }; -// Middleware для проверки авторизации const authenticate = async (req, res, next) => { try { - const accessToken = req.cookies[ACCESS_COOKIE]; - const refreshToken = req.cookies[REFRESH_COOKIE]; + const authHeader = req.headers.authorization || ''; - let tokenPayload = null; - - if (accessToken) { - try { - tokenPayload = verifyAccessToken(accessToken); - } catch (error) { - if (error.name !== 'TokenExpiredError') { - logSecurityEvent('INVALID_ACCESS_TOKEN', req, { error: error.message }); - clearAuthCookies(res); - return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); - } - } - } - - if (!tokenPayload && refreshToken) { - try { - const refreshPayload = verifyRefreshToken(refreshToken); - const userForRefresh = await User.findById(refreshPayload.userId); - - if (!userForRefresh) { - clearAuthCookies(res); - return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); - } - - const tokens = signAuthTokens(userForRefresh); - setAuthCookies(res, tokens); - tokenPayload = verifyAccessToken(tokens.accessToken); - } catch (error) { - logSecurityEvent('INVALID_REFRESH_TOKEN', req, { error: error.message }); - clearAuthCookies(res); - return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); - } - } - - if (!tokenPayload) { + if (!authHeader.startsWith('tma ')) { logSecurityEvent('AUTH_TOKEN_MISSING', req); return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); } - if (!validateTelegramId(tokenPayload.telegramId)) { - logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: tokenPayload.telegramId }); - clearAuthCookies(res); - return res.status(401).json({ error: 'Неверный ID пользователя' }); - } + const initDataRaw = authHeader.slice(4).trim(); - let user = await User.findOne({ telegramId: tokenPayload.telegramId.toString() }); - if (!user) { - clearAuthCookies(res); + if (!initDataRaw) { + logSecurityEvent('EMPTY_INITDATA', req); return res.status(401).json({ error: OFFICIAL_CLIENT_MESSAGE }); } + let payload; + + try { + payload = validateAndParseInitData(initDataRaw); + } catch (error) { + logSecurityEvent('INVALID_INITDATA', req, { reason: error.message }); + return res.status(401).json({ error: `${error.message}. ${OFFICIAL_CLIENT_MESSAGE}` }); + } + + const telegramUser = payload.user; + + if (!validateTelegramId(telegramUser.id)) { + logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id }); + return res.status(401).json({ error: 'Неверный ID пользователя' }); + } + + let user = await User.findOne({ telegramId: telegramUser.id.toString() }); + + if (!user) { + user = new User({ + telegramId: telegramUser.id.toString(), + username: telegramUser.username || telegramUser.first_name, + firstName: telegramUser.first_name, + lastName: telegramUser.last_name, + photoUrl: telegramUser.photo_url + }); + await user.save(); + } else { + user.username = telegramUser.username || telegramUser.first_name; + user.firstName = telegramUser.first_name; + user.lastName = telegramUser.last_name; + if (telegramUser.photo_url) { + user.photoUrl = telegramUser.photo_url; + } + await user.save(); + } + if (user.banned) { - clearAuthCookies(res); return res.status(403).json({ error: 'Пользователь заблокирован' }); } await ensureUserSettings(user); await touchUserActivity(user); + req.user = user; - req.telegramUser = { id: user.telegramId }; + req.telegramUser = telegramUser; next(); } catch (error) { console.error('❌ Ошибка авторизации:', error); diff --git a/backend/routes/auth.js b/backend/routes/auth.js index af71c19..b8f77ff 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -6,9 +6,7 @@ const config = require('../config'); const { validateTelegramId } = require('../middleware/validator'); const { logSecurityEvent } = require('../middleware/logger'); const { strictAuthLimiter } = require('../middleware/security'); -const { validateAndParseInitData } = require('../utils/telegram'); -const { signAuthTokens, setAuthCookies, clearAuthCookies } = require('../utils/tokens'); -const { touchUserActivity, ensureUserSettings } = require('../middleware/auth'); +const { authenticate, ensureUserSettings, touchUserActivity } = require('../middleware/auth'); const ALLOWED_SEARCH_PREFERENCES = ['furry', 'anime']; @@ -34,97 +32,45 @@ const normalizeUserSettings = (settings = {}) => { const OFFICIAL_CLIENT_MESSAGE = 'Используйте официальный клиент. Сообщите об ошибке в https://t.me/NakamaReportbot'; -router.post('/signin', strictAuthLimiter, async (req, res) => { +const respondWithUser = async (user, res) => { + const populatedUser = await user.populate([ + { path: 'followers', select: 'username firstName lastName photoUrl' }, + { path: 'following', select: 'username firstName lastName photoUrl' } + ]); + + const settings = normalizeUserSettings(populatedUser.settings); + + return res.json({ + success: true, + user: { + id: populatedUser._id, + telegramId: populatedUser.telegramId, + username: populatedUser.username, + firstName: populatedUser.firstName, + lastName: populatedUser.lastName, + photoUrl: populatedUser.photoUrl, + bio: populatedUser.bio, + role: populatedUser.role, + followersCount: populatedUser.followers.length, + followingCount: populatedUser.following.length, + settings, + banned: populatedUser.banned + } + }); +}; + +router.post('/signin', strictAuthLimiter, authenticate, async (req, res) => { try { - 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; - - const initDataRaw = headerInitData || bodyInitData; - - if (!initDataRaw) { - return res.status(400).json({ error: 'initData обязателен' }); - } - - let payload; - - try { - 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 пользователя' }); - } - - let user = await User.findOne({ telegramId: telegramUser.id.toString() }); - - if (!user) { - user = new User({ - telegramId: telegramUser.id.toString(), - username: telegramUser.username || telegramUser.first_name, - firstName: telegramUser.first_name, - lastName: telegramUser.last_name, - photoUrl: telegramUser.photo_url - }); - await user.save(); - } else { - user.username = telegramUser.username || telegramUser.first_name; - user.firstName = telegramUser.first_name; - user.lastName = telegramUser.last_name; - if (telegramUser.photo_url) { - user.photoUrl = telegramUser.photo_url; - } - await user.save(); - } - - if (user.banned) { - return res.status(403).json({ error: 'Пользователь заблокирован' }); - } - - await ensureUserSettings(user); - await touchUserActivity(user); - - const tokens = signAuthTokens(user); - setAuthCookies(res, tokens); - - const populatedUser = await user.populate([ - { path: 'followers', select: 'username firstName lastName photoUrl' }, - { path: 'following', select: 'username firstName lastName photoUrl' } - ]); - - const settings = normalizeUserSettings(populatedUser.settings); - - return res.json({ - success: true, - user: { - id: populatedUser._id, - telegramId: populatedUser.telegramId, - username: populatedUser.username, - firstName: populatedUser.firstName, - lastName: populatedUser.lastName, - photoUrl: populatedUser.photoUrl, - bio: populatedUser.bio, - role: populatedUser.role, - followersCount: populatedUser.followers.length, - followingCount: populatedUser.following.length, - settings, - banned: populatedUser.banned - } - }); + await ensureUserSettings(req.user); + await touchUserActivity(req.user); + return respondWithUser(req.user, res); } catch (error) { console.error('Ошибка signin:', error); res.status(500).json({ error: 'Ошибка авторизации' }); } }); -router.post('/logout', (req, res) => { - clearAuthCookies(res); +router.post('/logout', (_req, res) => { res.json({ success: true }); }); diff --git a/backend/server.js b/backend/server.js index fa88590..e7fd724 100644 --- a/backend/server.js +++ b/backend/server.js @@ -4,7 +4,6 @@ const cors = require('cors'); const dotenv = require('dotenv'); const path = require('path'); const http = require('http'); -const cookieParser = require('cookie-parser'); // Загрузить переменные окружения ДО импорта config dotenv.config({ path: path.join(__dirname, '.env') }); @@ -56,7 +55,6 @@ app.use(cors(corsOptions)); // Body parsing с ограничениями app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); -app.use(cookieParser()); // Security middleware app.use(sanitizeMongo); // Защита от NoSQL injection diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 3543b13..9d01581 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,7 +1,7 @@ import { BrowserRouter, Routes, Route, Navigate, useNavigate } from 'react-router-dom' import { useState, useEffect, useRef } from 'react' import { initTelegramApp } from './utils/telegram' -import { signInWithTelegram, verifyAuth } from './utils/api' +import { verifyAuth } from './utils/api' import { initTheme } from './utils/theme' import Layout from './components/Layout' import Feed from './pages/Feed' @@ -18,8 +18,8 @@ function AppContent() { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const navigate = useNavigate() - const startParamProcessed = useRef(false) // Флаг для обработки startParam только один раз - const initAppCalled = useRef(false) // Флаг чтобы initApp вызывался только один раз + const startParamProcessed = useRef(false) + const initAppCalled = useRef(false) useEffect(() => { initTheme() @@ -55,7 +55,7 @@ function AppContent() { tg.ready?.() tg.expand?.() - const userData = await signInWithTelegram(tg.initData) + const userData = await verifyAuth() setUser(userData) setError(null) @@ -68,16 +68,7 @@ function AppContent() { } } catch (err) { console.error('Ошибка инициализации:', err) - - try { - // Попытаться восстановить сессию по токенам - const userData = await verifyAuth() - setUser(userData) - setError(null) - } catch (verifyError) { - console.error('Не удалось восстановить сессию:', verifyError) - setError(err?.response?.data?.error || err.message || 'Ошибка авторизации') - } + setError(err?.response?.data?.error || err.message || 'Ошибка авторизации') } finally { setLoading(false) } diff --git a/moderation/frontend/src/App.jsx b/moderation/frontend/src/App.jsx index 22f0d47..7d6aa12 100644 --- a/moderation/frontend/src/App.jsx +++ b/moderation/frontend/src/App.jsx @@ -1,6 +1,5 @@ import { useEffect, useRef, useState } from 'react'; import { - signInWithTelegram, verifyAuth, fetchUsers, banUser, @@ -130,13 +129,7 @@ export default function App() { const app = await waitForInitData(); if (cancelled) return; - const initData = app.initData; - - if (!initData) { - throw new Error('Telegram не передал initData'); - } - - const userData = await signInWithTelegram(initData); + const userData = await verifyAuth(); if (cancelled) return; setUser(userData); @@ -144,22 +137,11 @@ export default function App() { } catch (err) { if (cancelled) return; console.error('Ошибка инициализации модератора:', err); - try { - const userData = await verifyAuth(); - if (!cancelled) { - setUser(userData); - setError(null); - } - } catch (verifyError) { - if (!cancelled) { - const message = - err?.response?.data?.error || - verifyError?.response?.data?.error || - err?.message || - 'Нет доступа. Убедитесь, что вы добавлены как администратор.'; - setError(message); - } - } + const message = + err?.response?.data?.error || + err?.message || + 'Нет доступа. Убедитесь, что вы добавлены как администратор.'; + setError(message); } finally { if (!cancelled) { setLoading(false); diff --git a/moderation/frontend/src/utils/api.js b/moderation/frontend/src/utils/api.js index 4d47b13..68128a0 100644 --- a/moderation/frontend/src/utils/api.js +++ b/moderation/frontend/src/utils/api.js @@ -20,9 +20,6 @@ api.interceptors.request.use((config) => { return config; }); -export const signInWithTelegram = (initData) => - api.post('/auth/signin', { initData }).then((res) => res.data.user) - export const verifyAuth = () => api.post('/mod-app/auth/verify').then((res) => res.data.user) export const fetchUsers = (params = {}) => diff --git a/package.json b/package.json index e914654..bc325ec 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "xss-clean": "^0.1.4", "hpp": "^0.2.3", "validator": "^13.11.0", - "cookie-parser": "^1.4.6", "@telegram-apps/init-data-node": "^1.0.0" }, "devDependencies": {