Update files

This commit is contained in:
glpshchn 2025-12-09 03:51:07 +03:00
parent d6ed268c4a
commit a93c95158f
11 changed files with 437 additions and 48 deletions

View File

@ -21,29 +21,40 @@ const OWNER_USERNAMES = new Set(config.moderationOwnerUsernames || []);
// Проверка доступа к модерации (только для модераторов и админов) // Проверка доступа к модерации (только для модераторов и админов)
const requireModerationAccess = async (req, res, next) => { const requireModerationAccess = async (req, res, next) => {
// Проверить роль пользователя if (!req.user) {
if (!req.user || !['moderator', 'admin'].includes(req.user.role)) {
return res.status(403).json({ error: 'Недостаточно прав для модерации' }); return res.status(403).json({ error: 'Недостаточно прав для модерации' });
} }
// Для JWT авторизации (без Telegram) - достаточно проверки роли // Для JWT авторизации (без Telegram) - проверяем роль
if (!req.user.telegramId) { if (!req.user.telegramId) {
if (!['moderator', 'admin'].includes(req.user.role)) {
return res.status(403).json({ error: 'Недостаточно прав для модерации' });
}
req.isModerationAdmin = true; req.isModerationAdmin = true;
req.isOwner = req.user.role === 'admin'; req.isOwner = req.user.role === 'admin';
return next(); return next();
} }
// Для Telegram авторизации - проверяем через isModerationAdmin
const username = normalizeUsername(req.user?.username); const username = normalizeUsername(req.user?.username);
const telegramId = req.user?.telegramId; const telegramId = req.user?.telegramId;
// Владелец или админ по роли - всегда доступ
if (OWNER_USERNAMES.has(username) || req.user.role === 'admin') { if (OWNER_USERNAMES.has(username) || req.user.role === 'admin') {
req.isModerationAdmin = true; req.isModerationAdmin = true;
req.isOwner = true; req.isOwner = true;
return next(); return next();
} }
// Проверить, является ли пользователь модератором через ModerationAdmin
const allowed = await isModerationAdmin({ telegramId, username }); const allowed = await isModerationAdmin({ telegramId, username });
if (!allowed) { if (!allowed) {
// Если не модератор по базе, но роль moderator или admin - разрешить
if (['moderator', 'admin'].includes(req.user.role)) {
req.isModerationAdmin = true;
req.isOwner = req.user.role === 'admin';
return next();
}
return res.status(403).json({ error: 'Недостаточно прав для модерации' }); return res.status(403).json({ error: 'Недостаточно прав для модерации' });
} }

View File

@ -309,8 +309,11 @@ router.post('/telegram-widget', authLimiter, async (req, res) => {
router.post('/telegram', authLimiter, authenticateModeration, async (req, res) => { router.post('/telegram', authLimiter, authenticateModeration, async (req, res) => {
try { try {
const user = req.user; const user = req.user;
const { isModerationAdmin } = require('../services/moderationAdmin');
const { normalizeUsername } = require('../services/moderationAdmin');
const config = require('../config');
if (!user || !['moderator', 'admin'].includes(user.role)) { if (!user) {
return res.status(403).json({ error: 'Доступ запрещен' }); return res.status(403).json({ error: 'Доступ запрещен' });
} }
@ -318,6 +321,21 @@ router.post('/telegram', authLimiter, authenticateModeration, async (req, res) =
return res.status(403).json({ error: 'Аккаунт заблокирован' }); return res.status(403).json({ error: 'Аккаунт заблокирован' });
} }
// Проверить доступ: роль admin/moderator ИЛИ является модератором через ModerationAdmin
const username = normalizeUsername(user.username);
const telegramId = user.telegramId;
const OWNER_USERNAMES = new Set(config.moderationOwnerUsernames || []);
const isOwner = OWNER_USERNAMES.has(username);
const isAdminByRole = ['moderator', 'admin'].includes(user.role);
const isAdminByDB = await isModerationAdmin({ telegramId, username });
if (!isOwner && !isAdminByRole && !isAdminByDB) {
return res.status(403).json({
error: 'Доступ запрещен. У вас нет прав модератора. Обратитесь к администратору.'
});
}
// Обновить время последней активности // Обновить время последней активности
user.lastActiveAt = new Date(); user.lastActiveAt = new Date();
await user.save(); await user.save();

View File

@ -0,0 +1,58 @@
#!/bin/bash
echo "🔍 Проверка состояния бэкенда модерации..."
echo ""
# Проверка 1: Процесс запущен ли на порту 3001
echo "1⃣ Проверка процесса на порту 3001:"
if lsof -i :3001 > /dev/null 2>&1; then
echo " ✅ Порт 3001 занят процессом:"
lsof -i :3001 | grep LISTEN
else
echo " ❌ Порт 3001 свободен - бэкенд не запущен!"
fi
echo ""
# Проверка 2: HTTP запрос к бэкенду
echo "2⃣ Проверка доступности бэкенда:"
if curl -s http://localhost:3001/health > /dev/null 2>&1; then
echo " ✅ Бэкенд отвечает на /health:"
curl -s http://localhost:3001/health | jq . 2>/dev/null || curl -s http://localhost:3001/health
else
echo " ❌ Бэкенд недоступен по http://localhost:3001/health"
fi
echo ""
# Проверка 3: Через Nginx
echo "3⃣ Проверка через Nginx:"
if curl -s https://moderation.nkm.guru/api/health > /dev/null 2>&1; then
echo " ✅ Nginx проксирует запросы:"
curl -s https://moderation.nkm.guru/api/health | jq . 2>/dev/null || curl -s https://moderation.nkm.guru/api/health
else
echo " ❌ Nginx не может достучаться до бэкенда"
fi
echo ""
# Проверка 4: Логи Nginx
echo "4⃣ Последние ошибки из Nginx:"
if [ -f /var/log/nginx/moderation-error.log ]; then
echo " Последние 5 строк:"
tail -5 /var/log/nginx/moderation-error.log
else
echo " ⚠️ Файл логов не найден: /var/log/nginx/moderation-error.log"
fi
echo ""
echo "📋 РЕШЕНИЕ:"
echo "Если бэкенд не запущен, выполните:"
echo ""
echo "cd /Users/glpshchn/Desktop/nakama/moderation/backend"
echo "NODE_ENV=production PORT=3001 MODERATION_PORT=3001 node server.js"
echo ""
echo "Или используйте PM2:"
echo "pm2 start moderation/backend/server.js --name moderation-backend --env production"
echo "pm2 save"

View File

@ -68,8 +68,8 @@ services:
dockerfile: Dockerfile.moderation-backend dockerfile: Dockerfile.moderation-backend
container_name: nakama-moderation-backend container_name: nakama-moderation-backend
restart: unless-stopped restart: unless-stopped
expose: ports:
- "3001" - "127.0.0.1:3001:3001" # Проброс порта на хост для Nginx
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3001 - PORT=3001

View File

@ -0,0 +1,46 @@
#!/bin/bash
echo "🔧 Исправление проблемы с портом бэкенда модерации..."
echo ""
# Остановить и удалить контейнер
echo "1⃣ Остановка контейнера..."
docker stop nakama-moderation-backend 2>/dev/null || true
docker rm nakama-moderation-backend 2>/dev/null || true
echo ""
# Пересобрать и перезапустить
echo "2⃣ Пересборка и запуск контейнера с проброшенным портом..."
docker-compose up -d moderation-backend
echo ""
# Проверка
echo "3⃣ Проверка доступности порта..."
sleep 2
if lsof -i :3001 > /dev/null 2>&1; then
echo " ✅ Порт 3001 доступен:"
lsof -i :3001 | grep LISTEN
else
echo " ⚠️ Порт 3001 еще не доступен, подождите..."
fi
echo ""
# Проверка здоровья
echo "4⃣ Проверка здоровья бэкенда..."
sleep 3
if curl -s http://localhost:3001/health > /dev/null 2>&1; then
echo " ✅ Бэкенд отвечает:"
curl -s http://localhost:3001/health | jq . 2>/dev/null || curl -s http://localhost:3001/health
else
echo " ⚠️ Бэкенд еще не готов, проверьте логи:"
echo " docker logs nakama-moderation-backend"
fi
echo ""
echo "✅ Готово! Теперь Nginx должен иметь доступ к бэкенду на порту 3001"

34
fix-nginx-conflict.sh Normal file
View File

@ -0,0 +1,34 @@
#!/bin/bash
echo "🔍 Проверка конфликтов в nginx конфигурации..."
# Проверить существующие конфигурации
echo "📋 Существующие конфигурации:"
ls -la /etc/nginx/sites-enabled/
echo ""
echo "🔍 Поиск moderation.nkm.guru в конфигурациях:"
grep -r "moderation.nkm.guru" /etc/nginx/sites-enabled/ 2>/dev/null || echo "Не найдено в sites-enabled"
echo ""
echo "📝 Содержимое /etc/nginx/sites-enabled/nakama (если существует):"
if [ -f "/etc/nginx/sites-enabled/nakama" ]; then
echo "--- Начало файла nakama ---"
head -100 /etc/nginx/sites-enabled/nakama
echo "--- Конец ---"
else
echo "Файл не найден"
fi
echo ""
echo "⚠️ РЕШЕНИЕ:"
echo "1. Если moderation.nkm.guru уже настроен в /etc/nginx/sites-enabled/nakama:"
echo " - Удалите настройки moderation.nkm.guru из этого файла"
echo " - Или удалите весь файл если он не нужен"
echo ""
echo "2. Затем установите новую конфигурацию:"
echo " sudo cp nginx-moderation-production.conf /etc/nginx/sites-available/moderation.nkm.guru"
echo " sudo ln -sf /etc/nginx/sites-available/moderation.nkm.guru /etc/nginx/sites-enabled/"
echo " sudo nginx -t"
echo " sudo systemctl reload nginx"

View File

@ -0,0 +1,40 @@
#!/bin/bash
# Скрипт для установки конфигурации nginx для модерации
echo "🔧 Установка конфигурации Nginx для moderation.nkm.guru"
# Создать директорию если не существует
sudo mkdir -p /etc/nginx/sites-available
sudo mkdir -p /etc/nginx/sites-enabled
# Путь к файлу конфигурации
CONFIG_FILE="nginx-moderation-production.conf"
# Проверить существование файла
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ Ошибка: Файл $CONFIG_FILE не найден!"
echo "Убедитесь что вы в директории проекта"
exit 1
fi
# Скопировать конфигурацию
echo "📋 Копирование конфигурации..."
sudo cp "$CONFIG_FILE" /etc/nginx/sites-available/moderation.nkm.guru
# Создать симлинк
echo "🔗 Создание симлинка..."
sudo ln -sf /etc/nginx/sites-available/moderation.nkm.guru /etc/nginx/sites-enabled/moderation.nkm.guru
# Проверить конфигурацию
echo "✔️ Проверка конфигурации nginx..."
if sudo nginx -t; then
echo "✅ Конфигурация корректна!"
echo "🔄 Перезагрузка nginx..."
sudo systemctl reload nginx
echo "✅ Готово! Конфигурация применена."
else
echo "❌ Ошибка в конфигурации nginx!"
exit 1
fi

View File

@ -48,6 +48,78 @@ EMAIL_FROM=noreply@nakama.guru
4. Пользователь вводит код, username и пароль 4. Пользователь вводит код, username и пароль
5. Аккаунт активируется 5. Аккаунт активируется
## Деплой БЕЗ Docker (локально)
### 1. Настройка переменных окружения
Добавьте в `.env` в корне проекта:
```bash
# Модерация
MODERATION_PORT=3001
MODERATION_CORS_ORIGIN=https://moderation.nkm.guru
VITE_MODERATION_API_URL=https://moderation.nkm.guru/api # ВАЖНО: обязательно HTTPS!
# Email для кодов подтверждения админа
OWNER_EMAIL=aaem9848@gmail.com
# Email настройки (выберите один вариант выше)
EMAIL_PROVIDER=aws
# ... остальные настройки email
# MongoDB (та же база что и основной бэкенд)
MONGODB_URI=mongodb://103.80.87.247:27017/nakama
# JWT (те же ключи что и основной бэкенд)
JWT_SECRET=your_jwt_secret
JWT_ACCESS_SECRET=your_access_secret
JWT_REFRESH_SECRET=your_refresh_secret
```
### 2. Запуск бэкенда модерации
```bash
cd /Users/glpshchn/Desktop/nakama/moderation/backend
NODE_ENV=production PORT=3001 MODERATION_PORT=3001 node server.js
```
Или с PM2 (рекомендуется):
```bash
cd /Users/glpshchn/Desktop/nakama
pm2 start moderation/backend/server.js --name moderation-backend --env production -- \
NODE_ENV=production PORT=3001 MODERATION_PORT=3001
pm2 save
```
### 3. Проверка работы бэкенда
```bash
# Проверка бэкенда
curl http://localhost:3001/health
# Проверка через nginx
curl https://moderation.nkm.guru/api/health
```
### 4. Настройка Nginx
```bash
# Скопировать конфигурацию
sudo cp nginx-moderation-production.conf /etc/nginx/sites-available/moderation.nkm.guru
# Активировать конфигурацию
sudo ln -sf /etc/nginx/sites-available/moderation.nkm.guru /etc/nginx/sites-enabled/
# Проверить конфигурацию
sudo nginx -t
# Перезагрузить nginx
sudo systemctl reload nginx
```
**⚠️ ВАЖНО:** Убедитесь, что в `/etc/nginx/sites-enabled/nakama` нет дублирующих настроек для `moderation.nkm.guru`. Если есть - удалите их, иначе будет конфликт.
## Деплой с Docker ## Деплой с Docker
### 1. Настройка переменных окружения ### 1. Настройка переменных окружения
@ -126,10 +198,56 @@ docker-compose restart nginx-moderation
## Troubleshooting ## Troubleshooting
### 502 Bad Gateway
Это означает, что Nginx не может подключиться к бэкенду. Проверьте:
1. **Бэкенд запущен?**
```bash
curl http://localhost:3001/health
# Должен вернуть: {"status":"ok","service":"moderation",...}
```
2. **Порт 3001 свободен?**
```bash
lsof -i :3001
# Должен показать процесс Node.js
```
3. **Логи Nginx:**
```bash
sudo tail -f /var/log/nginx/moderation-error.log
```
4. **Запустите бэкенд:**
```bash
cd /Users/glpshchn/Desktop/nakama/moderation/backend
NODE_ENV=production PORT=3001 MODERATION_PORT=3001 node server.js
```
### Конфликт конфигурации Nginx
Если видите ошибки типа "conflicting server name" или "protocol options redefined":
1. Проверьте дубликаты в `/etc/nginx/sites-enabled/`:
```bash
grep -r "moderation.nkm.guru" /etc/nginx/sites-enabled/
```
2. Удалите дублирующие настройки из `/etc/nginx/sites-enabled/nakama` (если есть)
3. Перезагрузите nginx:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
### Email не отправляется ### Email не отправляется
1. Проверьте настройки EMAIL_PROVIDER в .env 1. Проверьте настройки EMAIL_PROVIDER в .env
2. Проверьте логи: `docker-compose logs moderation-backend` 2. Проверьте логи бэкенда:
- Если через PM2: `pm2 logs moderation-backend`
- Если напрямую: логи в консоли
3. Убедитесь что credentials правильные 3. Убедитесь что credentials правильные
### Не работает авторизация ### Не работает авторизация

View File

@ -8,8 +8,6 @@
<title>Nakama Moderation</title> <title>Nakama Moderation</title>
<!-- Telegram Web App SDK --> <!-- Telegram Web App SDK -->
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script>
<!-- Telegram Login Widget SDK -->
<script async src="https://telegram.org/js/telegram-widget.js?22" data-telegram-login="NakamaSpaceBot" data-size="large" data-request-access="write"></script>
<style> <style>
* { * {
margin: 0; margin: 0;

View File

@ -154,12 +154,18 @@ export default function App() {
const result = await loginTelegram(); const result = await loginTelegram();
if (cancelled) return; if (cancelled) return;
setUser(result.user); if (result && result.user) {
setError(null); setUser(result.user);
setError(null);
setLoading(false);
return;
}
} catch (err) {
console.error('Telegram авторизация не удалась:', err);
// В MiniApp при ошибке показываем ошибку, а не форму входа
setError(err?.response?.data?.error || 'Ошибка авторизации. Убедитесь, что у вас есть права модератора.');
setLoading(false); setLoading(false);
return; return;
} catch (err) {
console.warn('Telegram авторизация не удалась, пробуем JWT:', err);
} }
} }
@ -211,7 +217,7 @@ export default function App() {
const showLoginForm = error === 'login_required' || (!user && !loading); const showLoginForm = error === 'login_required' || (!user && !loading);
// Инициализировать виджет только если нет WebApp initData и показана форма входа // Инициализировать виджет только если нет WebApp initData и показана форма входа
if (!telegramApp?.initData && showLoginForm) { if (!telegramApp?.initData && showLoginForm && telegramWidgetRef.current) {
// Глобальная функция для обработки авторизации через виджет // Глобальная функция для обработки авторизации через виджет
window.onTelegramAuth = async (userData) => { window.onTelegramAuth = async (userData) => {
console.log('Telegram Login Widget данные:', userData); console.log('Telegram Login Widget данные:', userData);
@ -256,14 +262,21 @@ export default function App() {
const initWidget = () => { const initWidget = () => {
// Проверить не загружен ли уже виджет // Проверить не загружен ли уже виджет
if (document.querySelector('script[src*="telegram-widget"]')) { if (document.querySelector('script[src*="telegram-widget"]')) {
console.log('[Telegram Widget] Виджет уже загружен');
return; return;
} }
const widgetContainer = telegramWidgetRef.current; const widgetContainer = telegramWidgetRef.current;
if (!widgetContainer) { if (!widgetContainer) {
console.warn('[Telegram Widget] Контейнер не найден');
return; return;
} }
console.log('[Telegram Widget] Инициализация виджета...');
// Очистить контейнер перед добавлением скрипта
widgetContainer.innerHTML = '';
const script = document.createElement('script'); const script = document.createElement('script');
script.async = true; script.async = true;
script.src = 'https://telegram.org/js/telegram-widget.js?22'; script.src = 'https://telegram.org/js/telegram-widget.js?22';
@ -271,12 +284,21 @@ export default function App() {
script.setAttribute('data-size', 'large'); script.setAttribute('data-size', 'large');
script.setAttribute('data-request-access', 'write'); script.setAttribute('data-request-access', 'write');
script.setAttribute('data-onauth', 'onTelegramAuth'); script.setAttribute('data-onauth', 'onTelegramAuth');
script.setAttribute('data-radius', '10');
script.onload = () => {
console.log('[Telegram Widget] Скрипт загружен');
};
script.onerror = () => {
console.error('[Telegram Widget] Ошибка загрузки скрипта');
};
widgetContainer.appendChild(script); widgetContainer.appendChild(script);
}; };
// Подождать немного чтобы контейнер был готов // Подождать немного чтобы контейнер был готов
const timer = setTimeout(initWidget, 500); const timer = setTimeout(initWidget, 100);
return () => { return () => {
clearTimeout(timer); clearTimeout(timer);
if (window.onTelegramAuth) { if (window.onTelegramAuth) {
@ -1305,43 +1327,20 @@ export default function App() {
); );
} }
// Показать форму входа // Показать форму входа ТОЛЬКО если это НЕ MiniApp
if (error === 'login_required' || (!user && !loading)) { const telegramApp = window.Telegram?.WebApp;
const telegramApp = window.Telegram?.WebApp; const isMiniApp = telegramApp && telegramApp.initData;
const canUseTelegram = telegramApp && telegramApp.initData;
if (error === 'login_required' || (!user && !loading && !isMiniApp)) {
return ( return (
<div className="fullscreen-center"> <div className="fullscreen-center">
<div className="login-card"> <div className="login-card">
<h1 style={{ marginTop: 0, marginBottom: '24px' }}>Вход в модерацию</h1> <h1 style={{ marginTop: 0, marginBottom: '24px' }}>Вход в модерацию</h1>
{/* Telegram авторизация */} {/* Telegram авторизация в MiniApp - не показываем, авторизация автоматическая */}
{canUseTelegram && (
<>
<button
className="btn primary"
onClick={handleTelegramLogin}
disabled={authLoading}
style={{ width: '100%', justifyContent: 'center', marginBottom: '16px' }}
>
{authLoading ? <Loader2 className="spin" size={18} /> : '🔐 Войти через Telegram'}
</button>
<div style={{
textAlign: 'center',
marginBottom: '24px',
padding: '16px',
background: 'rgba(255, 255, 255, 0.05)',
borderRadius: '8px'
}}>
<p style={{ margin: 0, fontSize: '14px', color: 'var(--text-secondary)' }}>
или
</p>
</div>
</>
)}
{/* Telegram Login Widget для обычного браузера */} {/* Telegram Login Widget для обычного браузера */}
{!canUseTelegram && ( {!isMiniApp && (
<> <>
<div <div
id="telegram-login-widget" id="telegram-login-widget"
@ -1560,16 +1559,21 @@ export default function App() {
); );
} }
// Показать ошибку (особенно важно для MiniApp)
if (error && error !== 'login_required') { if (error && error !== 'login_required') {
const telegramApp = window.Telegram?.WebApp;
const isMiniApp = telegramApp && telegramApp.initData;
return ( return (
<div className="fullscreen-center"> <div className="fullscreen-center">
<ShieldCheck size={48} /> <ShieldCheck size={48} />
<p>{error}</p> <p style={{ marginTop: '16px', textAlign: 'center', maxWidth: '400px' }}>{error}</p>
{error.includes('доступ') && ( {!isMiniApp && error.includes('доступ') && (
<button <button
className="btn" className="btn"
onClick={() => { onClick={() => {
localStorage.removeItem('moderation_token'); localStorage.removeItem('moderation_token');
localStorage.removeItem('moderation_jwt_token');
setError('login_required'); setError('login_required');
}} }}
style={{ marginTop: '16px' }} style={{ marginTop: '16px' }}
@ -1577,6 +1581,27 @@ export default function App() {
Войти заново Войти заново
</button> </button>
)} )}
{isMiniApp && (
<button
className="btn"
onClick={async () => {
setLoading(true);
setError(null);
try {
const result = await loginTelegram();
setUser(result.user);
setError(null);
} catch (err) {
setError(err?.response?.data?.error || 'Ошибка авторизации');
} finally {
setLoading(false);
}
}}
style={{ marginTop: '16px' }}
>
Попробовать снова
</button>
)}
</div> </div>
); );
} }

View File

@ -0,0 +1,41 @@
#!/bin/bash
# Скрипт для запуска бэкенда модерации
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"
echo "🚀 Запуск бэкенда модерации..."
echo ""
# Проверка существования .env
if [ ! -f ".env" ]; then
echo "⚠️ Файл .env не найден в корне проекта!"
echo " Создайте его на основе ENV_EXAMPLE.txt"
exit 1
fi
# Проверка занятости порта
if lsof -i :3001 > /dev/null 2>&1; then
echo "⚠️ Порт 3001 уже занят!"
echo " Остановите существующий процесс или измените порт"
lsof -i :3001
exit 1
fi
# Проверка установки зависимостей
if [ ! -d "node_modules" ]; then
echo "📦 Установка зависимостей..."
npm install
fi
echo "✅ Запуск сервера модерации на порту 3001..."
echo ""
# Запуск с переменными окружения
cd moderation/backend
NODE_ENV=production \
PORT=3001 \
MODERATION_PORT=3001 \
node server.js