From b81521043ed3dc23e92b7b2c2c2708001bab8a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PontualTech=20/=20Karl=C3=A3o?= Date: Wed, 29 Apr 2026 08:06:11 -0300 Subject: [PATCH] feat(api): GET /api/bms/diag-log e /:file pra ler logs do servidor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Endpoints novos só backend (não muda APK): - GET /api/bms/diag-log: lista files do user (name, size, mtime, sorted desc) - GET /api/bms/diag-log/:file: retorna conteúdo plain text - Path traversal protegido (regex sanitiza nome do arquivo) - Filtra por user_id (não vê logs de outros users) Co-Authored-By: Claude Opus 4.7 (1M context) --- server/src/index.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/server/src/index.js b/server/src/index.js index ce885aa..c148002 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -135,7 +135,7 @@ app.post('/api/bms/diag-log', requireAuth, (req, res) => { const ts = new Date().toISOString().replace(/[:.]/g, '-'); const file = path.join(dir, `${req.user.id}-${ts}.txt`); try { - fs.writeFileSync(file, log.slice(0, 50000)); // cap em 50KB + fs.writeFileSync(file, log.slice(0, 50000)); db.audit(req.user.id, 'bms_diag_log', 'bluetooth', null, { bytes: log.length, file: path.basename(file) }, req.ip); res.json({ ok: true, file: path.basename(file) }); } catch (e) { @@ -143,6 +143,38 @@ app.post('/api/bms/diag-log', requireAuth, (req, res) => { } }); +// Lista logs disponíveis (debug) +app.get('/api/bms/diag-log', requireAuth, (req, res) => { + const dir = path.join(db.dataDir, 'diag-logs'); + try { + if (!fs.existsSync(dir)) return res.json({ files: [] }); + const files = fs.readdirSync(dir) + .filter(f => f.startsWith(`${req.user.id}-`)) + .map(f => { + const stat = fs.statSync(path.join(dir, f)); + return { name: f, size: stat.size, mtime: stat.mtime }; + }) + .sort((a, b) => b.mtime - a.mtime); + res.json({ files }); + } catch (e) { + res.status(500).json({ error: e.message }); + } +}); + +// Lê conteúdo de um log específico +app.get('/api/bms/diag-log/:file', requireAuth, (req, res) => { + const file = req.params.file.replace(/[^a-zA-Z0-9._-]/g, ''); + if (!file.startsWith(`${req.user.id}-`)) return res.status(403).json({ error: 'forbidden' }); + const fullPath = path.join(db.dataDir, 'diag-logs', file); + try { + if (!fs.existsSync(fullPath)) return res.status(404).json({ error: 'not found' }); + const content = fs.readFileSync(fullPath, 'utf8'); + res.type('text/plain').send(content); + } catch (e) { + res.status(500).json({ error: e.message }); + } +}); + // ===== Google Login via OAuth redirect (pra apps Capacitor onde GSI popup não funciona) ===== // In-memory store: session_id → { tokens, createdAt }. Auto-expira em 10min. const pendingGoogleSessions = new Map();