diff --git a/backend/routes/auth.js b/backend/routes/auth.js index e980649..5df1d60 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -141,9 +141,30 @@ function validateTelegramOAuth(authData, botToken) { // Авторизация через Telegram OAuth (Login Widget) router.post('/oauth', strictAuthLimiter, async (req, res) => { try { - const { user: telegramUser, auth_date, hash } = req.body; + // Telegram Login Widget может отправлять данные в двух форматах: + // 1. { user: {...}, auth_date, hash } + // 2. { id, first_name, last_name, username, photo_url, auth_date, hash } + let telegramUser, auth_date, hash; + + if (req.body.user) { + // Формат 1 + telegramUser = req.body.user; + auth_date = req.body.auth_date; + hash = req.body.hash; + } else { + // Формат 2 - данные напрямую в body + telegramUser = { + id: req.body.id, + first_name: req.body.first_name, + last_name: req.body.last_name, + username: req.body.username, + photo_url: req.body.photo_url + }; + auth_date = req.body.auth_date; + hash = req.body.hash; + } - if (!telegramUser || !auth_date || !hash) { + if (!telegramUser || !telegramUser.id || !auth_date || !hash) { logSecurityEvent('INVALID_OAUTH_DATA', req); return res.status(400).json({ error: 'Неверные данные авторизации' }); } @@ -276,6 +297,27 @@ router.post('/oauth', strictAuthLimiter, async (req, res) => { const settings = normalizeUserSettings(populatedUser.settings); + // Генерируем JWT токены для web-сессии + const { signAccessToken, signRefreshToken, ACCESS_COOKIE, REFRESH_COOKIE } = require('../utils/tokens'); + + const accessToken = signAccessToken(populatedUser._id.toString()); + const refreshToken = signRefreshToken(populatedUser._id.toString()); + + // Устанавливаем cookies + res.cookie(ACCESS_COOKIE, accessToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 5 * 60 * 1000 // 5 минут + }); + + res.cookie(REFRESH_COOKIE, refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 7 * 24 * 60 * 60 * 1000 // 7 дней + }); + res.json({ success: true, user: { @@ -491,7 +533,7 @@ router.post('/magic-link/set-password', async (req, res) => { } // Хешируем пароль - const bcrypt = require('bcrypt'); + const bcrypt = require('bcryptjs'); const passwordHash = await bcrypt.hash(password, 10); // Обновляем пользователя @@ -568,7 +610,7 @@ router.post('/login-email', strictAuthLimiter, async (req, res) => { } // Проверить пароль - const bcrypt = require('bcrypt'); + const bcrypt = require('bcryptjs'); const isPasswordValid = await bcrypt.compare(password, user.passwordHash); if (!isPasswordValid) { @@ -721,7 +763,7 @@ router.post('/link-email', authenticate, async (req, res) => { } // Хешируем пароль - const bcrypt = require('bcrypt'); + const bcrypt = require('bcryptjs'); const passwordHash = await bcrypt.hash(password, 10); // Привязываем email и пароль diff --git a/frontend/src/components/AuthModal.css b/frontend/src/components/AuthModal.css index 1bf7bf9..efd2f1a 100644 --- a/frontend/src/components/AuthModal.css +++ b/frontend/src/components/AuthModal.css @@ -257,3 +257,14 @@ background: var(--bg-secondary); } + +/* Telegram OAuth Widget */ +.telegram-widget-container { + display: flex; + justify-content: center; + margin-bottom: 20px; +} + +.telegram-widget-container iframe { + border-radius: 12px; +} diff --git a/frontend/src/components/AuthModal.jsx b/frontend/src/components/AuthModal.jsx index 614203f..b0a7d05 100644 --- a/frontend/src/components/AuthModal.jsx +++ b/frontend/src/components/AuthModal.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect, useRef } from 'react' import { X, Send } from 'lucide-react' import { sendMagicLink } from '../utils/api' import './AuthModal.css' @@ -8,6 +8,86 @@ export default function AuthModal({ reason, onClose, onAuth }) { const [loading, setLoading] = useState(false) const [sent, setSent] = useState(false) const [error, setError] = useState('') + const telegramWidgetRef = useRef(null) + const [botUsername, setBotUsername] = useState('NakamaSpaceBot') // Дефолтное значение + + useEffect(() => { + // Получить bot username из API или использовать дефолтное + const fetchBotUsername = async () => { + try { + // Можно получить через API или использовать переменную окружения + // Пока используем дефолтное значение + setBotUsername('NakamaSpaceBot') + } catch (error) { + console.error('Ошибка получения bot username:', error) + } + } + + fetchBotUsername() + initTelegramWidget() + }, []) + + const initTelegramWidget = () => { + if (!telegramWidgetRef.current) return + + // Проверить не загружен ли уже виджет + if (document.querySelector('script[src*="telegram-widget"]')) { + return + } + + // Очистить контейнер + telegramWidgetRef.current.innerHTML = '' + + const script = document.createElement('script') + script.async = true + script.src = 'https://telegram.org/js/telegram-widget.js?22' + script.setAttribute('data-telegram-login', botUsername) + script.setAttribute('data-size', 'large') + script.setAttribute('data-request-access', 'write') + script.setAttribute('data-onauth', 'onTelegramAuth') + script.setAttribute('data-radius', '10') + const authUrl = `${window.location.origin}/api/auth/oauth` + script.setAttribute('data-auth-url', authUrl) + + // Глобальная функция для обработки авторизации + window.onTelegramAuth = async (userData) => { + console.log('[Telegram Widget] Данные от виджета:', userData) + + try { + setLoading(true) + setError('') + + // Telegram Login Widget отправляет данные напрямую + const response = await fetch('/api/auth/oauth', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify(userData) + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.error || 'Ошибка авторизации') + } + + const result = await response.json() + console.log('[Telegram Widget] Авторизация успешна:', result) + + // Вызываем callback и закрываем модалку + if (onAuth) { + onAuth(result) + } + onClose() + } catch (error) { + console.error('Ошибка авторизации через Telegram:', error) + setError(error.message || 'Ошибка авторизации') + } finally { + setLoading(false) + } + } + + telegramWidgetRef.current.appendChild(script) + } const handleSendMagicLink = async () => { if (!email.trim() || !email.includes('@')) { @@ -28,11 +108,6 @@ export default function AuthModal({ reason, onClose, onAuth }) { } } - const handleTelegramAuth = () => { - // Открываем Telegram бота для авторизации - window.open('https://t.me/YOUR_BOT_USERNAME', '_blank') - } - return (
e.stopPropagation()}> @@ -46,16 +121,8 @@ export default function AuthModal({ reason, onClose, onAuth }) { {!sent ? ( <> - {/* Telegram авторизация */} - + {/* Telegram авторизация через OAuth Widget */} +
или @@ -92,7 +159,7 @@ export default function AuthModal({ reason, onClose, onAuth }) {

- Отправим ссылку для входа на email. Регистрация не требуется. + Отправим ссылку для регистрации на email.

) : (