From 51530709a6688d6ccbf9343789bca6723d6f2c5d Mon Sep 17 00:00:00 2001
From: glpshchn <464976@niuitmo.ru>
Date: Wed, 5 Nov 2025 00:51:05 +0300
Subject: [PATCH] Update files
---
SETUP_TELEGRAM_BOT.md | 119 +++++++
backend/bot.js | 6 +-
backend/check-env.js | 61 ++++
backend/config/index.js | 1 +
backend/middleware/auth.js | 31 +-
backend/middleware/errorHandler.js | 76 +++++
backend/middleware/logger.js | 99 ++++++
backend/middleware/security.js | 100 ++++++
backend/middleware/validator.js | 156 ++++++++++
backend/models/Post.js | 3 +
backend/routes/auth.js | 124 +++++++-
backend/routes/posts.js | 199 +++++++++++-
backend/routes/search.js | 345 +++++++++++++--------
backend/scripts/backup.js | 104 +++++++
backend/server.js | 78 ++++-
frontend/index.html | 2 +-
frontend/src/App.jsx | 65 +++-
frontend/src/components/PostCard.css | 39 ++-
frontend/src/components/PostCard.jsx | 46 ++-
frontend/src/components/TelegramLogin.css | 35 +++
frontend/src/components/TelegramLogin.jsx | 55 ++++
frontend/src/pages/CommentsPage.css | 71 +++++
frontend/src/pages/CommentsPage.jsx | 125 ++++++--
frontend/src/pages/Feed.css | 18 ++
frontend/src/pages/Feed.jsx | 55 +++-
frontend/src/pages/Notifications.css | 26 ++
frontend/src/pages/Notifications.jsx | 4 +-
frontend/src/pages/PostMenuPage.css | 100 ++++++
frontend/src/pages/PostMenuPage.jsx | 125 +++++++-
frontend/src/pages/Search.css | 27 ++
frontend/src/pages/Search.jsx | 145 ++++++---
frontend/src/pages/UserProfile.jsx | 8 +-
frontend/src/utils/api.js | 47 ++-
frontend/src/utils/telegram.js | 17 +-
🔒_БЕЗОПАСНОСТЬ.txt | 128 ++++++++
🔧_TELEGRAM_OAUTH.txt | 97 ++++++
🔧_TG_BOT_TOKEN.txt | 82 +++++
🔧_ОБРАБОТКА_ОШИБОК.txt | 81 +++++
🔧_ПРОВЕРКА_ENV.txt | 104 +++++++
🔧_СТОРОННИЕ_КЛИЕНТЫ.txt | 79 +++++
40 files changed, 2827 insertions(+), 256 deletions(-)
create mode 100644 SETUP_TELEGRAM_BOT.md
create mode 100644 backend/check-env.js
create mode 100644 backend/middleware/errorHandler.js
create mode 100644 backend/middleware/logger.js
create mode 100644 backend/middleware/security.js
create mode 100644 backend/middleware/validator.js
create mode 100644 backend/scripts/backup.js
create mode 100644 frontend/src/components/TelegramLogin.css
create mode 100644 frontend/src/components/TelegramLogin.jsx
create mode 100644 🔒_БЕЗОПАСНОСТЬ.txt
create mode 100644 🔧_TELEGRAM_OAUTH.txt
create mode 100644 🔧_TG_BOT_TOKEN.txt
create mode 100644 🔧_ОБРАБОТКА_ОШИБОК.txt
create mode 100644 🔧_ПРОВЕРКА_ENV.txt
create mode 100644 🔧_СТОРОННИЕ_КЛИЕНТЫ.txt
diff --git a/SETUP_TELEGRAM_BOT.md b/SETUP_TELEGRAM_BOT.md
new file mode 100644
index 0000000..ec84bf9
--- /dev/null
+++ b/SETUP_TELEGRAM_BOT.md
@@ -0,0 +1,119 @@
+# 🔧 Установка Telegram Bot Token
+
+## Проблема
+
+Ошибка: `TELEGRAM_BOT_TOKEN не установлен`
+
+## Решение
+
+### 1. Получить токен от BotFather
+
+1. Откройте Telegram
+2. Найдите бота [@BotFather](https://t.me/BotFather)
+3. Отправьте команду `/newbot`
+4. Следуйте инструкциям:
+ - Введите имя бота (например: `My Nakama Bot`)
+ - Введите username бота (например: `my_nakama_bot`)
+5. Получите токен (формат: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`)
+
+### 2. Установить токен на сервере
+
+#### Вариант A: Через .env файл (Рекомендуется)
+
+```bash
+ssh root@ваш_IP
+cd /var/www/nakama/backend
+
+# Создать .env файл если его нет
+nano .env
+
+# Добавить строку:
+TELEGRAM_BOT_TOKEN=ваш_токен_от_BotFather
+
+# Сохранить: Ctrl+O, Enter, Ctrl+X
+```
+
+#### Вариант B: Через PM2 ecosystem
+
+```bash
+ssh root@ваш_IP
+cd /var/www/nakama
+
+# Создать ecosystem.config.js
+nano ecosystem.config.js
+```
+
+Добавьте:
+```javascript
+module.exports = {
+ apps: [{
+ name: 'nakama-backend',
+ script: './backend/server.js',
+ env: {
+ NODE_ENV: 'production',
+ TELEGRAM_BOT_TOKEN: 'ваш_токен_от_BotFather',
+ // ... другие переменные
+ }
+ }]
+};
+```
+
+#### Вариант C: Через export (Временное решение)
+
+```bash
+ssh root@ваш_IP
+export TELEGRAM_BOT_TOKEN="ваш_токен_от_BotFather"
+pm2 restart nakama-backend --update-env
+```
+
+### 3. Перезапустить backend
+
+```bash
+pm2 restart nakama-backend
+```
+
+### 4. Проверить логи
+
+```bash
+pm2 logs nakama-backend --lines 20
+```
+
+Должно быть:
+```
+✅ Telegram Bot инициализирован
+```
+
+**Не должно быть:**
+```
+⚠️ TELEGRAM_BOT_TOKEN не установлен!
+```
+
+## Проверка работы
+
+После установки токена:
+1. Откройте приложение
+2. Попробуйте отправить изображение в Telegram из поиска
+3. Изображение должно прийти в личные сообщения с ботом
+
+## Важно
+
+- Токен должен быть в формате: `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`
+- **НЕ** добавляйте кавычки в .env файле!
+- **НЕ** делитесь токеном публично!
+- Токен должен быть установлен до запуска бота
+
+## Пример .env файла
+
+```env
+NODE_ENV=production
+PORT=3000
+MONGODB_URI=mongodb://localhost:27017/nakama
+TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
+FRONTEND_URL=https://nakama.glpshchn.ru
+```
+
+## Дополнительная информация
+
+- [Telegram Bot API](https://core.telegram.org/bots/api)
+- [BotFather](https://t.me/BotFather)
+
diff --git a/backend/bot.js b/backend/bot.js
index c8f0c88..03fc3c9 100644
--- a/backend/bot.js
+++ b/backend/bot.js
@@ -130,7 +130,7 @@ async function sendPhotosToUser(userId, photos) {
media.push({
type: 'photo',
media: photoUrl,
- caption: index === 0 ? `Из NakamaSpace\n${batch.length} фото` : undefined,
+ caption: index === 0 ? `Из NakamaHost\n${batch.length} фото` : undefined,
parse_mode: 'HTML'
});
} else {
@@ -140,7 +140,7 @@ async function sendPhotosToUser(userId, photos) {
media.push({
type: 'photo',
media: photoUrl,
- caption: index === 0 ? `Из NakamaSpace\n${batch.length} фото` : undefined,
+ caption: index === 0 ? `Из NakamaHost\n${batch.length} фото` : undefined,
parse_mode: 'HTML'
});
}
@@ -167,7 +167,7 @@ async function handleWebAppData(userId, dataString) {
const data = JSON.parse(dataString);
if (data.action === 'send_image') {
- const caption = `Из NakamaSpace\n\n${data.caption || ''}`;
+ const caption = `Из NakamaHost\n\n${data.caption || ''}`;
await sendPhotoToUser(userId, data.url, caption);
return { success: true, message: 'Изображение отправлено!' };
}
diff --git a/backend/check-env.js b/backend/check-env.js
new file mode 100644
index 0000000..036ade2
--- /dev/null
+++ b/backend/check-env.js
@@ -0,0 +1,61 @@
+// Скрипт для проверки переменных окружения
+const dotenv = require('dotenv');
+const path = require('path');
+const fs = require('fs');
+
+console.log('🔍 Проверка переменных окружения...\n');
+
+// Проверить наличие .env файла
+const envPath = path.join(__dirname, '.env');
+console.log(`📁 Путь к .env: ${envPath}`);
+
+if (fs.existsSync(envPath)) {
+ console.log('✅ Файл .env найден\n');
+
+ // Загрузить .env
+ dotenv.config({ path: envPath });
+
+ // Проверить TELEGRAM_BOT_TOKEN
+ const token = process.env.TELEGRAM_BOT_TOKEN;
+ if (token) {
+ console.log('✅ TELEGRAM_BOT_TOKEN установлен');
+ console.log(` Токен: ${token.substring(0, 10)}...${token.substring(token.length - 4)}`);
+ console.log(` Длина: ${token.length} символов`);
+ } else {
+ console.log('❌ TELEGRAM_BOT_TOKEN НЕ установлен!');
+ console.log('\n📝 Проверьте .env файл:');
+ console.log(' Должна быть строка: TELEGRAM_BOT_TOKEN=ваш_токен');
+ console.log(' Без кавычек и пробелов вокруг =');
+ }
+
+ // Показать все переменные из .env
+ console.log('\n📋 Все переменные из .env:');
+ const envContent = fs.readFileSync(envPath, 'utf-8');
+ const lines = envContent.split('\n').filter(line => line.trim() && !line.startsWith('#'));
+ lines.forEach(line => {
+ const key = line.split('=')[0].trim();
+ const value = line.split('=').slice(1).join('=').trim();
+ if (key === 'TELEGRAM_BOT_TOKEN') {
+ console.log(` ${key}=${value.substring(0, 10)}...${value.substring(value.length - 4)}`);
+ } else {
+ console.log(` ${key}=${value.substring(0, 20)}...`);
+ }
+ });
+} else {
+ console.log('❌ Файл .env НЕ найден!');
+ console.log(`\n📝 Создайте файл .env в: ${envPath}`);
+ console.log(' Добавьте строку: TELEGRAM_BOT_TOKEN=ваш_токен');
+}
+
+console.log('\n🔍 Проверка переменных окружения системы:');
+const systemToken = process.env.TELEGRAM_BOT_TOKEN;
+if (systemToken) {
+ console.log('✅ TELEGRAM_BOT_TOKEN найден в системных переменных');
+} else {
+ console.log('⚠️ TELEGRAM_BOT_TOKEN не найден в системных переменных');
+}
+
+console.log('\n💡 Для PM2 нужно использовать:');
+console.log(' pm2 restart nakama-backend --update-env');
+console.log(' или добавить в ecosystem.config.js');
+
diff --git a/backend/config/index.js b/backend/config/index.js
index 6a47a58..7a627b7 100644
--- a/backend/config/index.js
+++ b/backend/config/index.js
@@ -1,4 +1,5 @@
// Централизованная конфигурация приложения
+// Важно: dotenv.config() должен быть вызван ДО этого файла
module.exports = {
// Сервер
diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js
index 9acef9c..c4bad73 100644
--- a/backend/middleware/auth.js
+++ b/backend/middleware/auth.js
@@ -1,5 +1,8 @@
const crypto = require('crypto');
const User = require('../models/User');
+const { validateTelegramId } = require('./validator');
+const { logSecurityEvent } = require('./logger');
+const config = require('../config');
// Проверка Telegram Init Data
function validateTelegramWebAppData(initData, botToken) {
@@ -84,17 +87,29 @@ const authenticate = async (req, res, next) => {
req.telegramUser = telegramUser;
- // Проверка подписи Telegram (только в production и если есть токен)
- if (process.env.NODE_ENV === 'production' && process.env.TELEGRAM_BOT_TOKEN) {
- const isValid = validateTelegramWebAppData(initData, process.env.TELEGRAM_BOT_TOKEN);
+ // Валидация Telegram ID
+ if (!validateTelegramId(telegramUser.id)) {
+ logSecurityEvent('INVALID_TELEGRAM_ID', req, { telegramId: telegramUser.id });
+ return res.status(401).json({ error: 'Неверный ID пользователя' });
+ }
+
+ // Проверка подписи Telegram (строгая проверка в production)
+ if (config.telegramBotToken) {
+ const isValid = validateTelegramWebAppData(initData, config.telegramBotToken);
if (!isValid) {
- console.warn('⚠️ Неверная подпись Telegram Init Data для пользователя:', telegramUser.id);
- // В production можно либо отклонить, либо пропустить с предупреждением
- // Для строгой проверки раскомментируйте:
- // return res.status(401).json({ error: 'Неверные данные авторизации' });
+ logSecurityEvent('INVALID_TELEGRAM_SIGNATURE', req, {
+ telegramId: telegramUser.id,
+ hasToken: !!config.telegramBotToken
+ });
+
+ // В production строгая проверка
+ if (config.isProduction()) {
+ return res.status(401).json({ error: 'Неверные данные авторизации' });
+ }
}
- } else if (process.env.NODE_ENV === 'production') {
+ } else if (config.isProduction()) {
+ logSecurityEvent('MISSING_BOT_TOKEN', req);
console.warn('⚠️ TELEGRAM_BOT_TOKEN не установлен, проверка подписи пропущена');
}
diff --git a/backend/middleware/errorHandler.js b/backend/middleware/errorHandler.js
new file mode 100644
index 0000000..04bfbd4
--- /dev/null
+++ b/backend/middleware/errorHandler.js
@@ -0,0 +1,76 @@
+const config = require('../config');
+
+// Централизованная обработка ошибок
+const errorHandler = (err, req, res, next) => {
+ // Логирование ошибки
+ console.error('❌ Ошибка:', {
+ message: err.message,
+ stack: config.isDevelopment() ? err.stack : undefined,
+ path: req.path,
+ method: req.method,
+ ip: req.ip,
+ user: req.user?.id || 'anonymous'
+ });
+
+ // Определение типа ошибки и статус кода
+ let statusCode = err.statusCode || err.status || 500;
+ let message = err.message || 'Внутренняя ошибка сервера';
+
+ // Обработка специфических ошибок
+ if (err.name === 'ValidationError') {
+ statusCode = 400;
+ message = 'Ошибка валидации данных';
+ } else if (err.name === 'CastError') {
+ statusCode = 400;
+ message = 'Неверный формат данных';
+ } else if (err.name === 'MongoError' && err.code === 11000) {
+ statusCode = 409;
+ message = 'Дубликат записи';
+ } else if (err.name === 'MulterError') {
+ statusCode = 400;
+ if (err.code === 'LIMIT_FILE_SIZE') {
+ message = 'Файл слишком большой';
+ } else if (err.code === 'LIMIT_FILE_COUNT') {
+ message = 'Слишком много файлов';
+ } else {
+ message = 'Ошибка загрузки файла';
+ }
+ } else if (err.name === 'AxiosError') {
+ statusCode = 502;
+ message = 'Ошибка внешнего сервиса';
+ }
+
+ // Отправка ответа
+ res.status(statusCode).json({
+ success: false,
+ error: message,
+ ...(config.isDevelopment() && { stack: err.stack })
+ });
+};
+
+// Обработка 404
+const notFoundHandler = (req, res, next) => {
+ res.status(404).json({
+ success: false,
+ error: 'Маршрут не найден'
+ });
+};
+
+// Обработка необработанных промисов
+process.on('unhandledRejection', (reason, promise) => {
+ console.error('❌ Unhandled Rejection:', reason);
+ // В production можно отправить уведомление
+});
+
+// Обработка необработанных исключений
+process.on('uncaughtException', (error) => {
+ console.error('❌ Uncaught Exception:', error);
+ // Graceful shutdown
+ process.exit(1);
+});
+
+module.exports = {
+ errorHandler,
+ notFoundHandler
+};
+
diff --git a/backend/middleware/logger.js b/backend/middleware/logger.js
new file mode 100644
index 0000000..1d723e1
--- /dev/null
+++ b/backend/middleware/logger.js
@@ -0,0 +1,99 @@
+const fs = require('fs');
+const path = require('path');
+const config = require('../config');
+
+// Создать директорию для логов если её нет
+const logsDir = path.join(__dirname, '../logs');
+if (!fs.existsSync(logsDir)) {
+ fs.mkdirSync(logsDir, { recursive: true });
+}
+
+// Функция для логирования
+const log = (level, message, data = {}) => {
+ const timestamp = new Date().toISOString();
+ const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
+
+ // Логирование в консоль
+ if (level === 'error') {
+ console.error(logMessage, data);
+ } else if (level === 'warn') {
+ console.warn(logMessage, data);
+ } else {
+ console.log(logMessage, data);
+ }
+
+ // Логирование в файл (только в production)
+ if (config.isProduction()) {
+ const logFile = path.join(logsDir, `${level}.log`);
+ const fileMessage = `${logMessage} ${JSON.stringify(data)}\n`;
+
+ fs.appendFile(logFile, fileMessage, (err) => {
+ if (err) {
+ console.error('Ошибка записи в лог файл:', err);
+ }
+ });
+ }
+};
+
+// Middleware для логирования запросов
+const requestLogger = (req, res, next) => {
+ const start = Date.now();
+
+ // Логировать после завершения запроса
+ res.on('finish', () => {
+ const duration = Date.now() - start;
+ const logData = {
+ method: req.method,
+ path: req.path,
+ status: res.statusCode,
+ duration: `${duration}ms`,
+ ip: req.ip,
+ userAgent: req.get('user-agent'),
+ userId: req.user?.id || 'anonymous'
+ };
+
+ if (res.statusCode >= 400) {
+ log('error', 'Request failed', logData);
+ } else if (res.statusCode >= 300) {
+ log('warn', 'Request redirect', logData);
+ } else {
+ log('info', 'Request completed', logData);
+ }
+ });
+
+ next();
+};
+
+// Логирование подозрительной активности
+const logSecurityEvent = (type, req, details = {}) => {
+ const securityData = {
+ type,
+ ip: req.ip,
+ userAgent: req.get('user-agent'),
+ path: req.path,
+ method: req.method,
+ userId: req.user?.id || 'anonymous',
+ ...details
+ };
+
+ log('warn', 'Security event', securityData);
+
+ // В production можно отправить уведомление
+ if (config.isProduction()) {
+ const securityLogFile = path.join(logsDir, 'security.log');
+ const message = `[${new Date().toISOString()}] [SECURITY] ${type}: ${JSON.stringify(securityData)}\n`;
+
+ fs.appendFile(securityLogFile, message, (err) => {
+ if (err) {
+ console.error('Ошибка записи в security лог:', err);
+ }
+ });
+ }
+};
+
+module.exports = {
+ log,
+ requestLogger,
+ logSecurityEvent
+};
+
diff --git a/backend/middleware/security.js b/backend/middleware/security.js
new file mode 100644
index 0000000..b080a83
--- /dev/null
+++ b/backend/middleware/security.js
@@ -0,0 +1,100 @@
+const helmet = require('helmet');
+const mongoSanitize = require('express-mongo-sanitize');
+const xss = require('xss-clean');
+const hpp = require('hpp');
+const rateLimit = require('express-rate-limit');
+const config = require('../config');
+
+// Настройка Helmet для безопасности headers
+const helmetConfig = helmet({
+ contentSecurityPolicy: {
+ directives: {
+ defaultSrc: ["'self'"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ scriptSrc: ["'self'", "https://telegram.org", "'unsafe-inline'"],
+ imgSrc: ["'self'", "data:", "https:", "blob:"],
+ connectSrc: ["'self'", "https://api.telegram.org", "https://e621.net", "https://gelbooru.com"],
+ fontSrc: ["'self'", "data:"],
+ objectSrc: ["'none'"],
+ // Запретить использование консоли и eval
+ scriptSrcAttr: ["'none'"],
+ baseUri: ["'self'"],
+ formAction: ["'self'"],
+ frameAncestors: ["'none'"],
+ upgradeInsecureRequests: config.isProduction() ? [] : null,
+ },
+ },
+ crossOriginEmbedderPolicy: false,
+ crossOriginResourcePolicy: { policy: "cross-origin" },
+ // Запретить использование консоли
+ noSniff: true,
+ xssFilter: true
+});
+
+// Sanitize MongoDB операторы (защита от NoSQL injection)
+const sanitizeMongo = mongoSanitize({
+ replaceWith: '_',
+ onSanitize: ({ req, key }) => {
+ console.warn(`⚠️ Sanitized MongoDB operator in ${req.path} at key: ${key}`);
+ }
+});
+
+// XSS защита
+const xssProtection = xss();
+
+// Защита от HTTP Parameter Pollution
+const hppProtection = hpp();
+
+// Строгий rate limit для авторизации
+const strictAuthLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000, // 15 минут
+ max: 5, // 5 попыток
+ message: 'Слишком много попыток авторизации, попробуйте позже',
+ standardHeaders: true,
+ legacyHeaders: false,
+ skipSuccessfulRequests: true,
+});
+
+// Rate limit для создания постов (защита от спама)
+const strictPostLimiter = rateLimit({
+ windowMs: 60 * 60 * 1000, // 1 час
+ max: 10, // 10 постов
+ message: 'Превышен лимит создания постов, попробуйте позже',
+ standardHeaders: true,
+ legacyHeaders: false,
+ skipSuccessfulRequests: true,
+});
+
+// Rate limit для файлов
+const fileUploadLimiter = rateLimit({
+ windowMs: 60 * 60 * 1000, // 1 час
+ max: 50, // 50 загрузок
+ message: 'Превышен лимит загрузки файлов',
+ standardHeaders: true,
+ legacyHeaders: false,
+});
+
+// DDoS защита - агрессивный rate limit
+const ddosProtection = rateLimit({
+ windowMs: 60 * 1000, // 1 минута
+ max: 100, // 100 запросов в минуту
+ message: 'Слишком много запросов, попробуйте позже',
+ standardHeaders: true,
+ legacyHeaders: false,
+ skip: (req) => {
+ // Пропускать health check
+ return req.path === '/health';
+ }
+});
+
+module.exports = {
+ helmetConfig,
+ sanitizeMongo,
+ xssProtection,
+ hppProtection,
+ strictAuthLimiter,
+ strictPostLimiter,
+ fileUploadLimiter,
+ ddosProtection
+};
+
diff --git a/backend/middleware/validator.js b/backend/middleware/validator.js
new file mode 100644
index 0000000..0c6e2fb
--- /dev/null
+++ b/backend/middleware/validator.js
@@ -0,0 +1,156 @@
+const validator = require('validator');
+
+// Валидация и санитизация входных данных
+const sanitizeInput = (req, res, next) => {
+ // Рекурсивная функция для очистки объекта
+ const sanitizeObject = (obj) => {
+ if (typeof obj !== 'object' || obj === null) {
+ return typeof obj === 'string' ? validator.escape(obj) : obj;
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map(item => sanitizeObject(item));
+ }
+
+ const sanitized = {};
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+ if (typeof value === 'string') {
+ sanitized[key] = validator.escape(validator.trim(value));
+ } else {
+ sanitized[key] = sanitizeObject(value);
+ }
+ }
+ }
+ return sanitized;
+ };
+
+ // Очистка body
+ if (req.body) {
+ req.body = sanitizeObject(req.body);
+ }
+
+ // Очистка query
+ if (req.query) {
+ req.query = sanitizeObject(req.query);
+ }
+
+ // Очистка params (только строковые значения)
+ if (req.params) {
+ for (const key in req.params) {
+ if (typeof req.params[key] === 'string') {
+ req.params[key] = validator.escape(req.params[key]);
+ }
+ }
+ }
+
+ next();
+};
+
+// Валидация URL
+const validateUrl = (url) => {
+ if (!url || typeof url !== 'string') {
+ return false;
+ }
+
+ // Проверка на path traversal
+ if (url.includes('..') || url.includes('./') || url.includes('../')) {
+ return false;
+ }
+
+ // Проверка на валидный URL
+ return validator.isURL(url, {
+ protocols: ['http', 'https'],
+ require_protocol: true,
+ require_valid_protocol: true
+ });
+};
+
+// Валидация Telegram User ID
+const validateTelegramId = (id) => {
+ if (!id || typeof id !== 'number' && typeof id !== 'string') {
+ return false;
+ }
+
+ const numId = typeof id === 'string' ? parseInt(id, 10) : id;
+ return !isNaN(numId) && numId > 0 && numId < Number.MAX_SAFE_INTEGER;
+};
+
+// Валидация контента поста
+const validatePostContent = (content) => {
+ if (!content || typeof content !== 'string') {
+ return false;
+ }
+
+ // Максимальная длина
+ if (content.length > 5000) {
+ return false;
+ }
+
+ // Проверка на опасные паттерны
+ const dangerousPatterns = [
+ /