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:
+109
@@ -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;
|
||||
Reference in New Issue
Block a user