Update files
This commit is contained in:
parent
85bc6a1ad9
commit
37cb69c69f
|
|
@ -141,9 +141,30 @@ function validateTelegramOAuth(authData, botToken) {
|
||||||
// Авторизация через Telegram OAuth (Login Widget)
|
// Авторизация через Telegram OAuth (Login Widget)
|
||||||
router.post('/oauth', strictAuthLimiter, async (req, res) => {
|
router.post('/oauth', strictAuthLimiter, async (req, res) => {
|
||||||
try {
|
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);
|
logSecurityEvent('INVALID_OAUTH_DATA', req);
|
||||||
return res.status(400).json({ error: 'Неверные данные авторизации' });
|
return res.status(400).json({ error: 'Неверные данные авторизации' });
|
||||||
}
|
}
|
||||||
|
|
@ -276,6 +297,27 @@ router.post('/oauth', strictAuthLimiter, async (req, res) => {
|
||||||
|
|
||||||
const settings = normalizeUserSettings(populatedUser.settings);
|
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({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
user: {
|
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);
|
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);
|
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
|
||||||
|
|
||||||
if (!isPasswordValid) {
|
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);
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
// Привязываем email и пароль
|
// Привязываем email и пароль
|
||||||
|
|
|
||||||
|
|
@ -257,3 +257,14 @@
|
||||||
background: var(--bg-secondary);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { X, Send } from 'lucide-react'
|
import { X, Send } from 'lucide-react'
|
||||||
import { sendMagicLink } from '../utils/api'
|
import { sendMagicLink } from '../utils/api'
|
||||||
import './AuthModal.css'
|
import './AuthModal.css'
|
||||||
|
|
@ -8,6 +8,86 @@ export default function AuthModal({ reason, onClose, onAuth }) {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [sent, setSent] = useState(false)
|
const [sent, setSent] = useState(false)
|
||||||
const [error, setError] = useState('')
|
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 () => {
|
const handleSendMagicLink = async () => {
|
||||||
if (!email.trim() || !email.includes('@')) {
|
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 (
|
return (
|
||||||
<div className="auth-modal-overlay" onClick={onClose}>
|
<div className="auth-modal-overlay" onClick={onClose}>
|
||||||
<div className="auth-modal-content" onClick={e => e.stopPropagation()}>
|
<div className="auth-modal-content" onClick={e => e.stopPropagation()}>
|
||||||
|
|
@ -46,16 +121,8 @@ export default function AuthModal({ reason, onClose, onAuth }) {
|
||||||
|
|
||||||
{!sent ? (
|
{!sent ? (
|
||||||
<>
|
<>
|
||||||
{/* Telegram авторизация */}
|
{/* Telegram авторизация через OAuth Widget */}
|
||||||
<button
|
<div ref={telegramWidgetRef} className="telegram-widget-container"></div>
|
||||||
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>
|
|
||||||
|
|
||||||
<div className="auth-divider">
|
<div className="auth-divider">
|
||||||
<span>или</span>
|
<span>или</span>
|
||||||
|
|
@ -92,7 +159,7 @@ export default function AuthModal({ reason, onClose, onAuth }) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="auth-hint">
|
<p className="auth-hint">
|
||||||
Отправим ссылку для входа на email. Регистрация не требуется.
|
Отправим ссылку для регистрации на email.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue