Migração para PostgreSQL multi-driver + correções de segurança

- Camada de banco unificada (src/database.js): drivers Postgres/Firebird,
  tradutor de SQL, suporte a schema e pool de conexões
- Conexões: novo_local (Postgres externo) e firebird_local (legado)
- Tela de rotas da API redesenhada (auth, params, exemplos de body)
- Correções de segurança (críticos/altos/médios/baixos): XSS no chat,
  escalonamento de privilégio, mídia autenticada, SQL restrito a gerente,
  JWT sem fallback + issuer, IDOR em conversas, CORS por allowlist,
  rate-limit no login, limites de corpo por rota
- Deploy alinhado: install.sh grava .env com PG_*, migracoes.js driver-aware

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 10:02:59 -03:00
commit ae629d1dc2
50 changed files with 17137 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
const express = require('express');
const cors = require('cors');
const path = require('path');
const routes = require('./routes');
const authenticateToken = require('./middlewares/auth');
const app = express();
// Middlewares
// 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 Firebird - Chatc2',
version: '2.0.0',
description: 'API multi-banco 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;