Files
chatc2/src/app.js
T
2026-06-24 14:13:09 +00:00

118 lines
4.2 KiB
JavaScript

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 <jwt_token>',
usu_token: 'X-Usu-Token: <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;