feat: новая функция

This commit is contained in:
2025-10-24 23:53:16 +03:00
parent 1a9be1d4c0
commit 2708a7c1d3
29 changed files with 2668 additions and 487 deletions

View File

@@ -22,8 +22,7 @@ README*
.git/
.gitignore
# Зависимости (будут установлены в контейнере)
node_modules/
# Логи установки
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -16,6 +16,7 @@ const cors = require('cors');
const session = require('express-session');
const sessionConfig = require('./config/session');
const logger = require('./utils/logger');
const rateLimit = require('express-rate-limit');
// const csurf = require('csurf'); // Закомментировано, так как не используется
const errorHandler = require('./middleware/errorHandler');
// const { version } = require('./package.json'); // Закомментировано, так как не используется
@@ -115,14 +116,21 @@ app.set('host', '0.0.0.0');
app.set('port', process.env.PORT || 8000);
// Настройка CORS
const corsOrigins = process.env.NODE_ENV === 'production'
? [
'http://localhost:9000', // Локальный Docker nginx
'https://localhost:9443', // Локальный Docker nginx HTTPS
// Добавьте ваш продакшн домен здесь для VDS
// 'https://yourdomain.com',
]
: [
'http://localhost:5173', // Vite dev server
'http://127.0.0.1:5173',
];
app.use(
cors({
origin: [
'http://localhost:5173',
'http://127.0.0.1:5173',
'https://hb3-accelerator.com',
'https://www.hb3-accelerator.com',
],
origin: corsOrigins,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
@@ -171,6 +179,36 @@ app.use((req, res, next) => {
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Определяем режим работы
const isProduction = process.env.NODE_ENV === 'production';
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: isProduction ? 100 : 1000, // 100 запросов в продакшне, 1000 в dev
message: {
error: 'Слишком много запросов, попробуйте позже',
retryAfter: '15 минут'
},
standardHeaders: true,
legacyHeaders: false,
});
// Применяем rate limiting ко всем запросам
app.use(limiter);
// Строгий rate limiting для чувствительных эндпоинтов
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 минут
max: 10, // 10 попыток
message: {
error: 'Превышен лимит попыток, попробуйте позже',
retryAfter: '15 минут'
},
standardHeaders: true,
legacyHeaders: false,
});
// Статическая раздача загруженных файлов (для dev и prod)
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.use('/api/uploads', express.static(path.join(__dirname, 'uploads')));
@@ -178,14 +216,26 @@ app.use('/api/uploads', express.static(path.join(__dirname, 'uploads')));
// Настройка безопасности
app.use(
helmet({
contentSecurityPolicy: false, // Отключаем CSP для разработки
contentSecurityPolicy: isProduction ? {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "ws:", "wss:"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
},
} : false, // Отключаем CSP для разработки
})
);
// Маршруты API
app.use('/api/tables', tablesRoutes); // ДОЛЖНО БЫТЬ ВЫШЕ!
// app.use('/api', identitiesRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/auth', strictLimiter, authRoutes); // Строгий rate limiting для аутентификации
app.use('/api/users', usersRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/admin', adminRoutes);
@@ -195,10 +245,10 @@ app.use('/api/kpp', kppRoutes); // Добавленное использован
app.use('/api/geocoding', geocodingRoutes); // Добавленное использование роута
app.use('/api/dle-v2', dleV2Routes); // Добавляем маршрут DLE v2
app.use('/api/settings', settingsRoutes); // Добавляем маршрут настроек
app.use('/api/settings', strictLimiter, settingsRoutes); // Строгий rate limiting для настроек
app.use('/api/countries', countriesRoutes); // Добавляем маршрут стран
app.use('/api/russian-classifiers', russianClassifiersRoutes); // Добавляем маршрут российских классификаторов
app.use('/api/ollama', ollamaRoutes); // Добавляем маршрут Ollama
app.use('/api/ollama', strictLimiter, ollamaRoutes); // Строгий rate limiting для Ollama
app.use('/api/ai-queue', aiQueueRoutes); // Добавляем маршрут AI Queue
app.use('/api/tags', tagsRoutes); // Добавляем маршрут тегов
app.use('/api/blockchain', blockchainRoutes); // Добавляем маршрут blockchain

View File

@@ -24,6 +24,8 @@ function setPoolChangeCallback(cb) {
let sessionMiddleware = createSessionMiddleware();
function createSessionMiddleware() {
const isProduction = process.env.NODE_ENV === 'production';
return session({
store: new pgSession({
pool: db.getPool(),
@@ -36,8 +38,8 @@ function createSessionMiddleware() {
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: false,
sameSite: 'lax',
secure: false, // false для локального Docker (HTTP)
sameSite: 'lax', // lax для локального Docker
path: '/',
},
});

View File

@@ -787,21 +787,25 @@ router.get('/encryption-key/status', requireAdmin, async (req, res) => {
const exists = fs.existsSync(keyPath);
let key = null;
if (exists) {
try {
key = fs.readFileSync(keyPath, 'utf8').trim();
} catch (error) {
logger.error('Ошибка чтения ключа:', error);
}
// Возвращаем только метаданные без содержимого ключа
let checksum = null;
if (exists) {
try {
const data = fs.readFileSync(keyPath);
// лёгкая хэш-сумма для проверки целостности без раскрытия ключа
const crypto = require('crypto');
checksum = crypto.createHash('sha256').update(data).digest('hex');
} catch (error) {
logger.error('Ошибка чтения ключа для метаданных:', error);
}
res.json({
success: true,
exists,
path: keyPath,
key: key
});
}
res.json({
success: true,
exists,
path: keyPath,
checksum
});
} catch (error) {
logger.error('Ошибка проверки статуса ключа шифрования:', error);
res.status(500).json({ success: false, error: error.message });

View File

@@ -1,48 +1,10 @@
const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');
const https = require('https');
const { promisify } = require('util');
const dns = require('dns');
const resolve4 = promisify(dns.resolve4);
const SSH_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.ssh');
const DEFAULT_KEY_PATH = path.join(SSH_DIR, 'id_rsa');
const DEFAULT_PUB_KEY_PATH = path.join(SSH_DIR, 'id_rsa.pub');
// Helper to read SSH key
const readSshKey = (keyPath) => {
try {
return fs.readFileSync(keyPath, 'utf8');
} catch (error) {
return null;
}
};
// GET /api/ssh-key - Get existing SSH private key
router.get('/ssh-key', (req, res) => {
const privateKey = readSshKey(DEFAULT_KEY_PATH);
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
if (privateKey) {
res.json({ success: true, sshKey: privateKey, publicKey: publicKey, keyType: 'rsa' });
} else {
res.status(404).json({ success: false, message: 'SSH private key not found' });
}
});
// GET /api/ssh-key/public - Get existing SSH public key
router.get('/ssh-key/public', (req, res) => {
const publicKey = readSshKey(DEFAULT_PUB_KEY_PATH);
if (publicKey) {
res.json({ success: true, publicKey: publicKey, keyType: 'rsa' });
} else {
res.status(404).json({ success: false, message: 'SSH public key not found' });
}
});
// Удалено: эндпоинты выдачи приватного/публичного SSH-ключа
// GET /api/dns-check/:domain - Check DNS and get IP address
router.get('/dns-check/:domain', async (req, res) => {

View File

@@ -0,0 +1,279 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
/**
* Единая матрица прав доступа для DLE
* Используется и на backend, и на frontend
*/
// Роли в системе
const ROLES = {
GUEST: 'guest', // Неавторизованный гость
USER: 'user', // Авторизованный гость (0 токенов)
READONLY: 'readonly', // Админ чтение (токены > 0 && < X)
EDITOR: 'editor' // Админ редактор (токены >= X)
};
// Список всех прав в системе
const PERMISSIONS = {
// Публичный доступ
VIEW_HOME: 'view_home',
CHAT_AI: 'chat_ai',
// Получение сообщений
RECEIVE_MESSAGES: 'receive_messages',
// Просмотр данных
VIEW_CRM: 'view_crm',
VIEW_CONTACTS: 'view_contacts',
VIEW_DATA: 'view_data',
// Отправка сообщений
SEND_TO_USERS: 'send_to_users',
CHAT_WITH_ADMINS: 'chat_with_admins',
// AI функции
GENERATE_AI_REPLIES: 'generate_ai_replies',
// Редактирование
EDIT_USER_DATA: 'edit_user_data',
EDIT_CONTACTS: 'edit_contacts',
// Удаление
DELETE_USER_DATA: 'delete_user_data',
DELETE_MESSAGES: 'delete_messages',
// Массовые операции
BROADCAST: 'broadcast',
// Управление тегами
MANAGE_TAGS: 'manage_tags',
// Блокировка пользователей
BLOCK_USERS: 'block_users',
// Управление настройками
MANAGE_SETTINGS: 'manage_settings',
// Контент: юридические документы
VIEW_BASIC_DOCS: 'view_basic_docs', // Базовые документы для пользователей
VIEW_LEGAL_DOCS: 'view_legal_docs', // Юридические документы для читателей
MANAGE_LEGAL_DOCS: 'manage_legal_docs' // Управление документами для редакторов
};
// Матрица: какая роль имеет какие права
const PERMISSIONS_MAP = {
[ROLES.GUEST]: [
PERMISSIONS.VIEW_HOME,
PERMISSIONS.CHAT_AI
],
[ROLES.USER]: [
PERMISSIONS.VIEW_HOME,
PERMISSIONS.CHAT_AI,
PERMISSIONS.RECEIVE_MESSAGES,
PERMISSIONS.VIEW_CONTACTS, // Пользователи могут видеть контакты для выбора
PERMISSIONS.SEND_TO_USERS, // Пользователи могут отправлять сообщения
PERMISSIONS.CHAT_WITH_ADMINS, // Авторизованные пользователи могут видеть личные сообщения
PERMISSIONS.VIEW_BASIC_DOCS // Базовые документы для пользователей
],
[ROLES.READONLY]: [
PERMISSIONS.VIEW_HOME,
PERMISSIONS.CHAT_AI,
PERMISSIONS.RECEIVE_MESSAGES,
PERMISSIONS.VIEW_CRM,
PERMISSIONS.VIEW_CONTACTS,
PERMISSIONS.VIEW_DATA,
PERMISSIONS.SEND_TO_USERS,
PERMISSIONS.CHAT_WITH_ADMINS,
// Базовые документы для пользователей
PERMISSIONS.VIEW_BASIC_DOCS,
// Чтение внутренних юридических документов
PERMISSIONS.VIEW_LEGAL_DOCS
],
[ROLES.EDITOR]: [
PERMISSIONS.VIEW_HOME,
PERMISSIONS.CHAT_AI,
PERMISSIONS.RECEIVE_MESSAGES,
PERMISSIONS.VIEW_CRM,
PERMISSIONS.VIEW_CONTACTS,
PERMISSIONS.VIEW_DATA,
PERMISSIONS.SEND_TO_USERS,
PERMISSIONS.CHAT_WITH_ADMINS,
PERMISSIONS.GENERATE_AI_REPLIES,
PERMISSIONS.EDIT_USER_DATA,
PERMISSIONS.EDIT_CONTACTS,
PERMISSIONS.DELETE_USER_DATA,
PERMISSIONS.DELETE_MESSAGES,
PERMISSIONS.BROADCAST,
PERMISSIONS.MANAGE_TAGS,
PERMISSIONS.BLOCK_USERS,
PERMISSIONS.MANAGE_SETTINGS,
// Базовые документы для пользователей
PERMISSIONS.VIEW_BASIC_DOCS,
// Полный доступ к юридическим документам
PERMISSIONS.VIEW_LEGAL_DOCS,
PERMISSIONS.MANAGE_LEGAL_DOCS
]
};
/**
* Проверяет, имеет ли роль определенное право
* @param {string} role - Роль пользователя
* @param {string} permission - Требуемое право
* @returns {boolean}
*/
function hasPermission(role, permission) {
if (!role || !permission) return false;
return PERMISSIONS_MAP[role]?.includes(permission) || false;
}
/**
* Получает все права для роли
* @param {string} role - Роль пользователя
* @returns {Array<string>}
*/
function getPermissionsForRole(role) {
return PERMISSIONS_MAP[role] || [];
}
/**
* Проверяет, имеет ли роль ХОТЯ БЫ ОДНО из прав
* @param {string} role - Роль пользователя
* @param {Array<string>} permissions - Список прав
* @returns {boolean}
*/
function hasAnyPermission(role, permissions) {
if (!Array.isArray(permissions)) return false;
return permissions.some(p => hasPermission(role, p));
}
/**
* Проверяет, имеет ли роль ВСЕ указанные права
* @param {string} role - Роль пользователя
* @param {Array<string>} permissions - Список прав
* @returns {boolean}
*/
function hasAllPermissions(role, permissions) {
if (!Array.isArray(permissions)) return false;
return permissions.every(p => hasPermission(role, p));
}
/**
* Получает описание роли
* @param {string} role - Роль пользователя
* @returns {string}
*/
function getRoleDescription(role) {
const descriptions = {
[ROLES.GUEST]: 'Неавторизованный гость',
[ROLES.USER]: 'Авторизованный гость',
[ROLES.READONLY]: 'Админ (только чтение)',
[ROLES.EDITOR]: 'Админ (редактор)'
};
return descriptions[role] || 'Неизвестная роль';
}
/**
* Проверить, может ли отправитель отправлять сообщения получателю
* @param {string} senderRole - Роль отправителя
* @param {string} recipientRole - Роль получателя
* @param {number} senderId - ID отправителя
* @param {number} recipientId - ID получателя
* @returns {Object} { canSend: boolean, errorMessage?: string }
*/
function canSendMessage(senderRole, recipientRole, senderId, recipientId) {
// Проверяем базовое право на отправку сообщений
if (!hasPermission(senderRole, PERMISSIONS.SEND_TO_USERS)) {
return {
canSend: false,
errorMessage: 'У вас нет права на отправку сообщений'
};
}
// Собственный чат - всегда разрешен (для ИИ ассистента)
if (senderId === recipientId) {
return { canSend: true };
}
// USER и READONLY могут писать только EDITOR
if ((senderRole === 'user' || senderRole === 'readonly') && recipientRole === 'editor') {
return { canSend: true };
}
// EDITOR может писать всем (USER, READONLY, EDITOR)
if (senderRole === 'editor') {
return { canSend: true };
}
// USER и READONLY НЕ могут писать друг другу
if ((senderRole === 'user' || senderRole === 'readonly') &&
(recipientRole === 'user' || recipientRole === 'readonly')) {
return {
canSend: false,
errorMessage: 'Пользователи и читатели не могут отправлять сообщения друг другу'
};
}
// Остальные случаи запрещены
return {
canSend: false,
errorMessage: `Роль ${senderRole} не может отправлять сообщения роли ${recipientRole}`
};
}
// Экспорты для CommonJS (Node.js)
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
ROLES,
PERMISSIONS,
PERMISSIONS_MAP,
hasPermission,
getPermissionsForRole,
hasAnyPermission,
hasAllPermissions,
getRoleDescription,
canSendMessage
};
}
// ES модули для Frontend
export {
ROLES,
PERMISSIONS,
PERMISSIONS_MAP,
hasPermission,
getPermissionsForRole,
hasAnyPermission,
hasAllPermissions,
getRoleDescription,
canSendMessage
};
// CommonJS для Backend
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
ROLES,
PERMISSIONS,
PERMISSIONS_MAP,
hasPermission,
getPermissionsForRole,
hasAnyPermission,
hasAllPermissions,
getRoleDescription,
canSendMessage
};
}