Update files
This commit is contained in:
parent
e446691b3d
commit
d6ed268c4a
|
|
@ -28,7 +28,7 @@ const signAuthTokens = (user) => ({
|
||||||
|
|
||||||
const getCookieBaseOptions = () => ({
|
const getCookieBaseOptions = () => ({
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: config.isProduction(),
|
secure: config.isProduction(), // HTTPS только в production
|
||||||
sameSite: config.isProduction() ? 'lax' : 'lax',
|
sameSite: config.isProduction() ? 'lax' : 'lax',
|
||||||
path: '/'
|
path: '/'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ EMAIL_FROM=noreply@nakama.guru
|
||||||
# Модерация
|
# Модерация
|
||||||
MODERATION_PORT=3001
|
MODERATION_PORT=3001
|
||||||
MODERATION_CORS_ORIGIN=https://moderation.nkm.guru
|
MODERATION_CORS_ORIGIN=https://moderation.nkm.guru
|
||||||
VITE_MODERATION_API_URL=https://moderation.nkm.guru/api
|
VITE_MODERATION_API_URL=https://moderation.nkm.guru/api # ВАЖНО: обязательно HTTPS!
|
||||||
|
|
||||||
# Email для кодов подтверждения админа
|
# Email для кодов подтверждения админа
|
||||||
OWNER_EMAIL=aaem9848@gmail.com
|
OWNER_EMAIL=aaem9848@gmail.com
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,9 @@ const { generalLimiter } = require('../../backend/middleware/rateLimiter');
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
// Trust proxy для правильного IP
|
// Trust proxy для правильного IP и HTTPS
|
||||||
if (config.isProduction()) {
|
// В production доверяем прокси (nginx), чтобы правильно определять HTTPS
|
||||||
app.set('trust proxy', 1);
|
app.set('trust proxy', config.isProduction() ? 1 : false);
|
||||||
}
|
|
||||||
|
|
||||||
// Security headers
|
// Security headers
|
||||||
app.use(helmetConfig);
|
app.use(helmetConfig);
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ export default function App() {
|
||||||
const [chatInput, setChatInput] = useState('');
|
const [chatInput, setChatInput] = useState('');
|
||||||
const chatSocketRef = useRef(null);
|
const chatSocketRef = useRef(null);
|
||||||
const chatListRef = useRef(null);
|
const chatListRef = useRef(null);
|
||||||
|
const telegramWidgetRef = useRef(null);
|
||||||
|
|
||||||
// Comments modal
|
// Comments modal
|
||||||
const [commentsModal, setCommentsModal] = useState(null); // { postId, comments: [] }
|
const [commentsModal, setCommentsModal] = useState(null); // { postId, comments: [] }
|
||||||
|
|
@ -139,65 +140,6 @@ export default function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
// Инициализация Telegram Login Widget для обычного браузера
|
|
||||||
const initTelegramWidget = () => {
|
|
||||||
// Глобальная функция для обработки авторизации через виджет
|
|
||||||
window.onTelegramAuth = async (userData) => {
|
|
||||||
console.log('Telegram Login Widget данные:', userData);
|
|
||||||
|
|
||||||
try {
|
|
||||||
setAuthLoading(true);
|
|
||||||
|
|
||||||
// Отправить данные виджета на сервер для создания сессии
|
|
||||||
const API_URL = getApiUrl();
|
|
||||||
const response = await fetch(`${API_URL}/moderation-auth/telegram-widget`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
credentials: 'include',
|
|
||||||
body: JSON.stringify(userData)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
throw new Error(errorData.error || 'Ошибка авторизации');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.accessToken) {
|
|
||||||
localStorage.setItem('moderation_jwt_token', result.accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result?.user) {
|
|
||||||
setUser(result.user);
|
|
||||||
setError(null);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Ошибка авторизации через виджет:', err);
|
|
||||||
setError(err.message || 'Ошибка авторизации через Telegram');
|
|
||||||
} finally {
|
|
||||||
setAuthLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загрузить виджет скрипт если его нет и есть контейнер
|
|
||||||
if (!document.querySelector('script[src*="telegram-widget"]')) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const widgetContainer = telegramWidgetRef.current;
|
|
||||||
if (!widgetContainer) return;
|
|
||||||
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.async = true;
|
|
||||||
script.src = 'https://telegram.org/js/telegram-widget.js?22';
|
|
||||||
script.setAttribute('data-telegram-login', 'NakamaSpaceBot');
|
|
||||||
script.setAttribute('data-size', 'large');
|
|
||||||
script.setAttribute('data-request-access', 'write');
|
|
||||||
script.setAttribute('data-onauth', 'onTelegramAuth');
|
|
||||||
|
|
||||||
widgetContainer.appendChild(script);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -221,10 +163,7 @@ export default function App() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализировать виджет для обычного браузера
|
// Инициализация виджета будет в отдельном useEffect после монтирования компонента
|
||||||
if (!telegramApp?.initData) {
|
|
||||||
initTelegramWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверить JWT токен
|
// Проверить JWT токен
|
||||||
const jwtToken = localStorage.getItem('moderation_jwt_token');
|
const jwtToken = localStorage.getItem('moderation_jwt_token');
|
||||||
|
|
@ -266,6 +205,93 @@ export default function App() {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Отдельный useEffect для инициализации виджета после монтирования
|
||||||
|
useEffect(() => {
|
||||||
|
const telegramApp = window.Telegram?.WebApp;
|
||||||
|
const showLoginForm = error === 'login_required' || (!user && !loading);
|
||||||
|
|
||||||
|
// Инициализировать виджет только если нет WebApp initData и показана форма входа
|
||||||
|
if (!telegramApp?.initData && showLoginForm) {
|
||||||
|
// Глобальная функция для обработки авторизации через виджет
|
||||||
|
window.onTelegramAuth = async (userData) => {
|
||||||
|
console.log('Telegram Login Widget данные:', userData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setAuthLoading(true);
|
||||||
|
|
||||||
|
// Отправить данные виджета на сервер для создания сессии
|
||||||
|
const API_URL = getApiUrl();
|
||||||
|
// В production используем относительный путь (HTTPS через nginx)
|
||||||
|
const fullApiUrl = API_URL.startsWith('http') ? API_URL : `${window.location.origin}${API_URL}`;
|
||||||
|
const response = await fetch(`${fullApiUrl}/moderation-auth/telegram-widget`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(userData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error || 'Ошибка авторизации');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.accessToken) {
|
||||||
|
localStorage.setItem('moderation_jwt_token', result.accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result?.user) {
|
||||||
|
setUser(result.user);
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка авторизации через виджет:', err);
|
||||||
|
setError(err.message || 'Ошибка авторизации через Telegram');
|
||||||
|
} finally {
|
||||||
|
setAuthLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWidget = () => {
|
||||||
|
// Проверить не загружен ли уже виджет
|
||||||
|
if (document.querySelector('script[src*="telegram-widget"]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetContainer = telegramWidgetRef.current;
|
||||||
|
if (!widgetContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.async = true;
|
||||||
|
script.src = 'https://telegram.org/js/telegram-widget.js?22';
|
||||||
|
script.setAttribute('data-telegram-login', 'NakamaSpaceBot');
|
||||||
|
script.setAttribute('data-size', 'large');
|
||||||
|
script.setAttribute('data-request-access', 'write');
|
||||||
|
script.setAttribute('data-onauth', 'onTelegramAuth');
|
||||||
|
|
||||||
|
widgetContainer.appendChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Подождать немного чтобы контейнер был готов
|
||||||
|
const timer = setTimeout(initWidget, 500);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (window.onTelegramAuth) {
|
||||||
|
delete window.onTelegramAuth;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (window.onTelegramAuth) {
|
||||||
|
delete window.onTelegramAuth;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [error, user, loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tab === 'users') {
|
if (tab === 'users') {
|
||||||
loadUsers();
|
loadUsers();
|
||||||
|
|
@ -403,16 +429,27 @@ export default function App() {
|
||||||
if (import.meta.env.VITE_API_URL) {
|
if (import.meta.env.VITE_API_URL) {
|
||||||
return import.meta.env.VITE_API_URL;
|
return import.meta.env.VITE_API_URL;
|
||||||
}
|
}
|
||||||
|
// В production используем относительный путь (HTTPS)
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
return '/api';
|
return '/api';
|
||||||
}
|
}
|
||||||
|
// В development используем localhost (только для dev)
|
||||||
return 'http://localhost:3001/api';
|
return 'http://localhost:3001/api';
|
||||||
};
|
};
|
||||||
|
|
||||||
const API_URL = getApiUrl();
|
const API_URL = getApiUrl();
|
||||||
|
|
||||||
// Для WebSocket убираем "/api" из base URL, т.к. socket.io слушает на корне
|
// Для WebSocket убираем "/api" из base URL, т.к. socket.io слушает на корне
|
||||||
const socketBase = API_URL.replace(/\/?api\/?$/, '');
|
// В production используем текущий origin через wss:// (HTTPS)
|
||||||
|
let socketBase = API_URL.replace(/\/?api\/?$/, '');
|
||||||
|
if (!socketBase || socketBase === '/api') {
|
||||||
|
// В production используем текущий origin (HTTPS)
|
||||||
|
socketBase = window.location.origin;
|
||||||
|
}
|
||||||
|
// Заменить http:// на https:// для безопасности (если не localhost)
|
||||||
|
if (socketBase.startsWith('http://') && !socketBase.includes('localhost')) {
|
||||||
|
socketBase = socketBase.replace('http://', 'https://');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[Chat] Инициализация чата');
|
console.log('[Chat] Инициализация чата');
|
||||||
console.log('[Chat] WS base URL:', socketBase);
|
console.log('[Chat] WS base URL:', socketBase);
|
||||||
|
|
@ -910,9 +947,11 @@ export default function App() {
|
||||||
if (import.meta.env.VITE_API_URL) {
|
if (import.meta.env.VITE_API_URL) {
|
||||||
return import.meta.env.VITE_API_URL.replace('/api', '');
|
return import.meta.env.VITE_API_URL.replace('/api', '');
|
||||||
}
|
}
|
||||||
|
// В production используем текущий origin (HTTPS через nginx)
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
return window.location.origin;
|
return window.location.origin;
|
||||||
}
|
}
|
||||||
|
// Только для development
|
||||||
return 'http://localhost:3000';
|
return 'http://localhost:3000';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ export const getApiUrl = () => {
|
||||||
if (import.meta.env.VITE_API_URL) {
|
if (import.meta.env.VITE_API_URL) {
|
||||||
return import.meta.env.VITE_API_URL;
|
return import.meta.env.VITE_API_URL;
|
||||||
}
|
}
|
||||||
// В production используем относительный путь
|
// В production используем относительный путь (HTTPS через nginx)
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
return '/api';
|
return '/api';
|
||||||
}
|
}
|
||||||
// В development используем порт модерации
|
// В development используем порт модерации (только для dev)
|
||||||
return 'http://localhost:3001/api';
|
return 'http://localhost:3001/api';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ server {
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
# Логи
|
# Логи
|
||||||
access_log /var/log/nginx/moderation-access.log;
|
access_log /var/log/nginx/moderation-access.log;
|
||||||
|
|
@ -44,6 +45,10 @@ server {
|
||||||
# Максимальный размер загружаемых файлов
|
# Максимальный размер загружаемых файлов
|
||||||
client_max_body_size 20M;
|
client_max_body_size 20M;
|
||||||
|
|
||||||
|
# Корневая директория фронтенда
|
||||||
|
root /var/www/nakama/moderation/frontend/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
# Gzip compression
|
# Gzip compression
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
|
|
@ -56,15 +61,17 @@ server {
|
||||||
application/vnd.ms-fontobject image/svg+xml;
|
application/vnd.ms-fontobject image/svg+xml;
|
||||||
|
|
||||||
# Проксирование API запросов к бэкенду модерации
|
# Проксирование API запросов к бэкенду модерации
|
||||||
|
# ИЗМЕНИТЕ localhost:3001 на ваш реальный адрес бэкенда
|
||||||
location /api {
|
location /api {
|
||||||
proxy_pass http://nakama-moderation-backend:3001;
|
proxy_pass http://127.0.0.1:3001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto https; # Всегда HTTPS
|
||||||
proxy_set_header X-Forwarded-Host $host;
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port 443;
|
||||||
|
|
||||||
# WebSocket поддержка
|
# WebSocket поддержка
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
@ -74,11 +81,14 @@ server {
|
||||||
proxy_connect_timeout 60s;
|
proxy_connect_timeout 60s;
|
||||||
proxy_send_timeout 60s;
|
proxy_send_timeout 60s;
|
||||||
proxy_read_timeout 60s;
|
proxy_read_timeout 60s;
|
||||||
|
|
||||||
|
# Отключить буферизацию для реального времени
|
||||||
|
proxy_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# WebSocket для чата модераторов
|
# WebSocket для чата модераторов
|
||||||
location /mod-chat {
|
location /mod-chat {
|
||||||
proxy_pass http://nakama-moderation-backend:3001;
|
proxy_pass http://127.0.0.1:3001;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|
@ -86,27 +96,33 @@ server {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto https; # Всегда HTTPS
|
||||||
|
proxy_set_header X-Forwarded-Port 443;
|
||||||
|
|
||||||
# Таймауты для WebSocket
|
# Таймауты для WebSocket
|
||||||
proxy_connect_timeout 7d;
|
proxy_connect_timeout 7d;
|
||||||
proxy_send_timeout 7d;
|
proxy_send_timeout 7d;
|
||||||
proxy_read_timeout 7d;
|
proxy_read_timeout 7d;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Статические файлы фронтенда (из Docker контейнера)
|
# Статические файлы фронтенда
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://nakama-moderation-frontend:80;
|
try_files $uri $uri/ /index.html;
|
||||||
proxy_http_version 1.1;
|
}
|
||||||
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
# Кэширование статических файлов
|
# Кэширование статических файлов
|
||||||
proxy_cache_valid 200 1y;
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot|map)$ {
|
||||||
proxy_cache_valid 404 1h;
|
expires 1y;
|
||||||
}
|
add_header Cache-Control "public, immutable";
|
||||||
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Без кэша для index.html (для обновлений)
|
||||||
|
location = /index.html {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue