const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const path = require('path'); const routes = require('./routes'); const authenticateToken = require('./middlewares/auth'); const app = express(); // Middlewares // Segurança: headers HTTP protetores (X-Content-Type-Options, X-Frame-Options, etc.) // CSP configurado de forma permissiva para não quebrar inline scripts/styles existentes app.use(helmet({ contentSecurityPolicy: false, // Desabilitado: app usa inline scripts/styles em várias páginas crossOriginEmbedderPolicy: false, // Permite carregar recursos cross-origin (Evolution API, etc.) })); // CORS: restringível via CORS_ORIGINS (lista separada por vírgula no .env). // Requisições same-origin e server-to-server (sem header Origin) sempre passam. const corsOrigins = (process.env.CORS_ORIGINS || [process.env.LOCAL_URL, process.env.EXTERNAL_URL].filter(Boolean).join(',')) .split(',').map(s => s.trim()).filter(Boolean); if (corsOrigins.length) { app.use(cors({ origin: function (origin, cb) { if (!origin || corsOrigins.indexOf(origin) !== -1) return cb(null, true); return cb(null, false); }, })); console.log('🔒 CORS restrito a:', corsOrigins.join(', ')); } else { app.use(cors()); console.log('⚠️ CORS aberto — defina CORS_ORIGINS no .env para restringir.'); } // Limite de corpo por rota (anti-DoS): // - rotas de mídia (envio de mensagem, webhook, evolution) → MAX_FILE_SIZE (grande) // - demais rotas → JSON_LIMIT (pequeno), reduz superfície de ataque const maxFileSize = process.env.MAX_FILE_SIZE || '50mb'; const jsonPadrao = process.env.JSON_LIMIT || '2mb'; console.log('📦 Limite de upload (mídia):', maxFileSize, '| padrão:', jsonPadrao); const bigJson = express.json({ limit: maxFileSize }); const smallJson = express.json({ limit: jsonPadrao }); const bigUrl = express.urlencoded({ extended: true, limit: maxFileSize }); const smallUrl = express.urlencoded({ extended: true, limit: jsonPadrao }); function rotaComMidia(req) { const p = req.path || ''; return p.endsWith('/messages') || p.indexOf('/webhook/') !== -1 || p.indexOf('/evolution/') !== -1; } app.use((req, res, next) => (rotaComMidia(req) ? bigJson : smallJson)(req, res, next)); app.use((req, res, next) => (rotaComMidia(req) ? bigUrl : smallUrl)(req, res, next)); // Static files app.use(express.static(path.join(__dirname, 'public'))); // Log de requisições app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} (${duration}ms)`); }); next(); }); // Rotas (a autenticação é aplicada internamente em cada grupo de rotas) app.use(routes); // Rota raiz app.get('/', (req, res) => { res.json({ name: 'API Chatc2 - Firebird', version: '2.0.0', description: 'API Firebird com sistema de aliases', auth: { loginPage: 'GET /app/:alias/login', login: 'POST /app/:alias/login', myData: 'GET /app/:alias/me', dashboard: 'GET /app/:alias/dashboard', }, api: { aliases: 'GET /api/aliases', health: 'GET /api/:alias/health', tables: 'GET /api/:alias/tables', tableInfo: 'GET /api/:alias/tables/:tableName', query: 'POST /api/:alias/query', execute: 'POST /api/:alias/execute', }, authentication: { jwt: 'Authorization: Bearer ', usu_token: 'X-Usu-Token: ', }, routes: 'GET /app/:alias/routes', obs: 'Substitua :alias pelo nome do banco (ex: lajedo, alfenas, ...)', }); }); // Middleware de erro global — loga o detalhe internamente e responde genérico // (evita vazar mensagens de erro do banco/SQL para o cliente) app.use((err, req, res, next) => { console.error('Erro não tratado:', err); res.status(err.status || 500).json({ success: false, error: 'Erro interno do servidor.', }); }); // Middleware para rotas não encontradas app.use((req, res) => { res.status(404).json({ success: false, error: `Rota ${req.method} ${req.originalUrl} não encontrada`, }); }); module.exports = app;