// ================================================================ // api/admin-auth.js — V2.1 // Autenticação segura — sem fallback hardcoded // V2.1 — Removida referência à Prefeitura // Sessão expira em 8 horas // Ciclismo Individual 2026 — Turismo de Base Comunitária // © 2026 Ewerson Luiz de Oliveira · Assoc. dos Seringueiros do Vale do Guaporé // ================================================================ // Gera token com timestamp de expiração embutido function gerarToken(usuario, perfil) { const expira = Date.now() + (8 * 60 * 60 * 1000); // 8 horas const payload = `${usuario}:${perfil}:${expira}`; const rand = Math.random().toString(36).slice(2); return Buffer.from(payload).toString('base64') + '.' + rand; } // Valida token — retorna dados ou null se expirado/inválido export function validarToken(token) { try { const [payload] = token.split('.'); const decoded = Buffer.from(payload, 'base64').toString('utf8'); const [usuario, perfil, expira] = decoded.split(':'); if (Date.now() > parseInt(expira)) return null; // expirado return { usuario, perfil }; } catch { return null; } } export default function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ erro: 'Método não permitido' }); } const ADMIN_USER = process.env.ADMIN_USER; const ADMIN_SENHA = process.env.ADMIN_SENHA; // ── Bloqueia se variáveis não configuradas ──────────────────── if (!ADMIN_USER || !ADMIN_SENHA) { console.error('admin-auth: ADMIN_USER ou ADMIN_SENHA não configurados no Vercel'); return res.status(503).json({ ok: false, erro: 'Painel não configurado. Configure ADMIN_USER e ADMIN_SENHA nas variáveis de ambiente do Vercel.' }); } const { usuario, senha, acao, token } = req.body; // ── Validar token existente (checagem de sessão ativa) ──────── if (acao === 'validarSessao') { if (!token) return res.status(401).json({ ok: false, erro: 'Token não informado' }); const dados = validarToken(token); if (!dados) return res.status(401).json({ ok: false, erro: 'Sessão expirada' }); return res.status(200).json({ ok: true, ...dados }); } // ── Verificar permissão de operador ─────────────────────────── if (acao === 'verificarPermissao') { if (!token) return res.status(401).json({ ok: false }); const dados = validarToken(token); if (!dados) return res.status(401).json({ ok: false, erro: 'Sessão expirada' }); if (dados.usuario === ADMIN_USER) { return res.status(200).json({ ok: true, podeExcluir: true }); } for (let i = 1; i <= 20; i++) { const op = process.env[`OPERADOR_${i}`]; if (!op) continue; const [opUser, , , opExcluir] = op.split(':'); if (opUser === dados.usuario) { return res.status(200).json({ ok: true, podeExcluir: opExcluir === 'true' }); } } return res.status(404).json({ ok: false, erro: 'Operador não encontrado' }); } // ── Login ───────────────────────────────────────────────────── if (!usuario || !senha) { return res.status(400).json({ ok: false, erro: 'Usuário e senha obrigatórios' }); } // Admin master if (usuario === ADMIN_USER && senha === ADMIN_SENHA) { return res.status(200).json({ ok: true, token: gerarToken(usuario, 'admin'), perfil: 'admin', nome: 'Administrador', podeExcluir: true, expira_em: Date.now() + (8 * 60 * 60 * 1000) }); } // Operadores (OPERADOR_1, OPERADOR_2... formato: usuario:senha:nome:podeExcluir) for (let i = 1; i <= 20; i++) { const op = process.env[`OPERADOR_${i}`]; if (!op) continue; const partes = op.split(':'); if (partes.length < 3) continue; const [opUser, opSenha, opNome, opExcluir] = partes; if (usuario === opUser && senha === opSenha) { return res.status(200).json({ ok: true, token: gerarToken(usuario, 'operador'), perfil: 'operador', nome: opNome || opUser, podeExcluir: opExcluir === 'true', expira_em: Date.now() + (8 * 60 * 60 * 1000) }); } } // Delay artificial para dificultar brute force return new Promise(resolve => setTimeout(() => { resolve(res.status(401).json({ ok: false, erro: 'Usuário ou senha incorretos' })); }, 800)); }