🔒 Implementação de segurança: Login Web fixo e proteção de API
This commit is contained in:
@@ -457,9 +457,58 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Login Overlay */
|
||||
#login-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
backdrop-filter: blur(20px);
|
||||
transition: opacity 0.5s ease, visibility 0.5s;
|
||||
}
|
||||
|
||||
#login-overlay.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
padding: 2.5rem;
|
||||
text-align: center;
|
||||
border: 1px solid var(--accent);
|
||||
box-shadow: 0 0 30px var(--accent-glow);
|
||||
}
|
||||
|
||||
.login-card h2 {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.5rem;
|
||||
background: linear-gradient(135deg, var(--accent), #8b5cf6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-overlay">
|
||||
<div class="card login-card">
|
||||
<div style="font-size: 3rem; margin-bottom: 1rem;">🔒</div>
|
||||
<h2>Acesso Restrito</h2>
|
||||
<p style="color:var(--text-muted); margin-bottom:1.5rem; font-size:0.85rem;">Esta VPS está protegida. Insira a senha mestra para gerenciar o Agente.</p>
|
||||
<input type="password" id="web-pass-input" class="form-input" placeholder="Senha da VPS" style="margin-bottom:1rem; text-align:center; font-size: 1.1rem; letter-spacing: 0.2rem;" onkeypress="if(event.key==='Enter') attemptLogin()">
|
||||
<button class="btn btn-primary" style="width:100%; padding: 0.8rem;" onclick="attemptLogin()">Entrar no Dashboard</button>
|
||||
<div id="login-error" style="color:var(--danger); font-size: 0.75rem; margin-top: 1rem; display: none;">Senha incorreta. Tente novamente.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="logo">
|
||||
@@ -590,6 +639,53 @@
|
||||
<div id="toast">Ação executada!</div>
|
||||
|
||||
<script>
|
||||
let webPassword = localStorage.getItem('vps_web_password') || '';
|
||||
|
||||
// Helper para chamadas de API com autenticação
|
||||
async function apiFetch(url, options = {}) {
|
||||
if (!options.headers) options.headers = {};
|
||||
options.headers['X-Web-Password'] = webPassword;
|
||||
|
||||
const res = await fetch(url, options);
|
||||
if (res.status === 401) {
|
||||
showLoginOverlay();
|
||||
throw new Error("Não autorizado");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function showLoginOverlay() {
|
||||
document.getElementById('login-overlay').classList.remove('hidden');
|
||||
document.getElementById('login-error').style.display = 'none';
|
||||
}
|
||||
|
||||
async function attemptLogin() {
|
||||
const input = document.getElementById('web-pass-input');
|
||||
const pwd = input.value.trim();
|
||||
if (!pwd) return;
|
||||
|
||||
const res = await fetch('/api/login', {
|
||||
headers: { 'X-Web-Password': pwd }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
webPassword = pwd;
|
||||
localStorage.setItem('vps_web_password', pwd);
|
||||
document.getElementById('login-overlay').classList.add('hidden');
|
||||
initDashboard();
|
||||
} else {
|
||||
document.getElementById('login-error').style.display = 'block';
|
||||
input.value = '';
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function initDashboard() {
|
||||
fetchStats();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
// Theme management
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const isDark = html.dataset.theme !== 'light';
|
||||
@@ -609,10 +705,10 @@
|
||||
updateThemeIcon(savedTheme === 'light');
|
||||
}
|
||||
|
||||
// Stats
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const res = await fetch('/api/status');
|
||||
if (!res.ok) throw new Error();
|
||||
const res = await apiFetch('/api/status');
|
||||
const data = await res.json();
|
||||
|
||||
const cpuVal = document.getElementById('cpu-val');
|
||||
@@ -650,12 +746,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(fetchStats, 3000);
|
||||
fetchStats();
|
||||
setInterval(() => { if(!document.getElementById('login-overlay').classList.contains('hidden')) return; fetchStats(); }, 3000);
|
||||
|
||||
// Actions
|
||||
async function executeAction(type) {
|
||||
const messages = {
|
||||
reboot_vps: '⚠️ Confirma reboot da VPS?',
|
||||
reboot_vps: '⚠️ Confirma reboot CRÍTICO da VPS?',
|
||||
clear_cache: 'Limpar cache do servidor?',
|
||||
restart_bot: 'Reiniciar o agente AI?'
|
||||
};
|
||||
@@ -663,7 +759,7 @@
|
||||
if (messages[type] && !confirm(messages[type])) return;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/action', {
|
||||
const res = await apiFetch('/api/action', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type })
|
||||
@@ -677,6 +773,7 @@
|
||||
|
||||
function showToast(msg, isError = false) {
|
||||
const toast = document.getElementById('toast');
|
||||
if(!toast) return;
|
||||
toast.textContent = msg;
|
||||
toast.className = isError ? 'error show' : 'show';
|
||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||
@@ -691,7 +788,7 @@
|
||||
input.value = '';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/chat', {
|
||||
const res = await apiFetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text })
|
||||
@@ -709,6 +806,7 @@
|
||||
|
||||
function addBubble(text, sender) {
|
||||
const box = document.getElementById('chat-box');
|
||||
if(!box) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'chat-bubble bubble-' + sender;
|
||||
div.textContent = text;
|
||||
@@ -718,7 +816,7 @@
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const res = await fetch('/api/config');
|
||||
const res = await apiFetch('/api/config');
|
||||
const data = await res.json();
|
||||
const provider = document.getElementById('active_provider');
|
||||
const key = document.getElementById('gemini_api_key');
|
||||
@@ -732,7 +830,7 @@
|
||||
const key = document.getElementById('gemini_api_key').value.trim();
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/config', {
|
||||
const res = await apiFetch('/api/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ active_provider: provider, gemini_api_key: key })
|
||||
@@ -748,8 +846,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
window.saveConfiguration = saveConfig;
|
||||
window.sendMessage = sendChat;
|
||||
async function testLLMSpeed() {
|
||||
const btn = document.getElementById('btn-test-llm');
|
||||
const originalContent = btn.innerHTML;
|
||||
@@ -757,7 +853,7 @@
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/test_llm');
|
||||
const res = await apiFetch('/api/test_llm');
|
||||
const data = await res.json();
|
||||
if(data.status === 'success') {
|
||||
showToast(`✅ LLM Online! Resposta em ${data.latency}s`);
|
||||
@@ -778,10 +874,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
window.saveConfiguration = saveConfig;
|
||||
window.sendMessage = sendChat;
|
||||
window.executeAction = executeAction;
|
||||
window.testLLMSpeed = testLLMSpeed;
|
||||
window.attemptLogin = attemptLogin;
|
||||
|
||||
loadConfig();
|
||||
// Auto-login se já tiver senha salva
|
||||
if (webPassword) {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/login', { headers: { 'X-Web-Password': webPassword } });
|
||||
if (res.ok) {
|
||||
document.getElementById('login-overlay').classList.add('hidden');
|
||||
initDashboard();
|
||||
} else {
|
||||
showLoginOverlay();
|
||||
}
|
||||
} catch(e) { showLoginOverlay(); }
|
||||
})();
|
||||
} else {
|
||||
showLoginOverlay();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user