Update files

This commit is contained in:
glpshchn 2026-01-01 22:52:40 +03:00
parent 85bc6a1ad9
commit 37cb69c69f
3 changed files with 142 additions and 22 deletions

View File

@ -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 и пароль

View File

@ -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;
}

View File

@ -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 (
<div className="auth-modal-overlay" onClick={onClose}>
<div className="auth-modal-content" onClick={e => e.stopPropagation()}>
@ -46,16 +121,8 @@ export default function AuthModal({ reason, onClose, onAuth }) {
{!sent ? (
<>
{/* Telegram авторизация */}
<button
className="auth-btn telegram"
onClick={handleTelegramAuth}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.63-.2-1.12-.31-1.08-.66.02-.18.27-.36.74-.55 2.92-1.27 4.86-2.11 5.83-2.51 2.78-1.16 3.35-1.36 3.73-1.36.08 0 .27.02.39.12.1.08.13.19.14.27-.01.06.01.24 0 .38z"/>
</svg>
Войти через Telegram
</button>
{/* Telegram авторизация через OAuth Widget */}
<div ref={telegramWidgetRef} className="telegram-widget-container"></div>
<div className="auth-divider">
<span>или</span>
@ -92,7 +159,7 @@ export default function AuthModal({ reason, onClose, onAuth }) {
</div>
<p className="auth-hint">
Отправим ссылку для входа на email. Регистрация не требуется.
Отправим ссылку для регистрации на email.
</p>
</>
) : (