🔒 Implementação de segurança: Login Web fixo e proteção de API

This commit is contained in:
2026-03-21 19:34:46 +00:00
parent 5e8acefa9a
commit 2d3da03ee6
4 changed files with 180 additions and 42 deletions

64
main.py
View File

@@ -1,11 +1,16 @@
import os
import psutil
from fastapi import FastAPI, Request
import subprocess
import time
import json
from fastapi import FastAPI, Request, Header, Depends, HTTPException, status
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from dotenv import load_dotenv
from starlette.concurrency import run_in_threadpool
from ai_agent import query_agent
from config import get_config, save_config
# Carrega as variáveis do .env
load_dotenv()
@@ -13,22 +18,37 @@ load_dotenv()
app = FastAPI(title="VpsTelegramBot API")
# Configura templates HTML
# Certifique-se de que a pasta 'templates' existe e tem o index.html
templates = Jinja2Templates(directory="templates")
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
return JSONResponse(content={"status": "ok"})
# --- SEGURANÇA ---
async def verify_password(x_web_password: str = Header(None)):
cfg = get_config()
saved_pwd = cfg.get("web_password", "@@Gi05Br;;")
if not x_web_password or x_web_password != saved_pwd:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Senha Web inválida ou ausente."
)
return True
# --- ROTAS PÚBLICAS ---
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
"""Renderiza o Dashboard Web."""
return templates.TemplateResponse("index.html", {"request": request})
from starlette.concurrency import run_in_threadpool
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
"""Favicon dummy para evitar 404."""
return JSONResponse(content={"status": "ok"})
# --- ROTAS PROTEGIDAS (API) ---
@app.get("/api/login")
async def check_login(is_auth: bool = Depends(verify_password)):
return {"status": "success"}
@app.get("/api/status")
async def get_system_status():
async def get_system_status(is_auth: bool = Depends(verify_password)):
"""Retorna o status do sistema (CPU, RAM, Disco) sem travar o loop."""
def get_stats():
cpu_percent = psutil.cpu_percent(interval=0.1)
@@ -47,24 +67,20 @@ async def get_system_status():
"percent": disk.percent
}
}
data = await run_in_threadpool(get_stats)
return JSONResponse(content=data)
import subprocess
from config import get_config, save_config
@app.get("/api/config")
async def read_configuration():
async def read_configuration(is_auth: bool = Depends(verify_password)):
return JSONResponse(content=get_config())
@app.post("/api/config")
async def update_configuration(req: dict):
async def update_configuration(req: dict, is_auth: bool = Depends(verify_password)):
save_config(req)
return JSONResponse(content={"status": "success"})
@app.post("/api/action")
async def execute_smart_action(action: dict):
async def execute_smart_action(action: dict, is_auth: bool = Depends(verify_password)):
"""Executa ações predefinidas no servidor (Smart Actions da Web UI)."""
action_type = action.get("type")
@@ -72,37 +88,31 @@ async def execute_smart_action(action: dict):
return JSONResponse(content={"status": "success", "message": "Pong! Servidor online e responsivo."})
elif action_type == "restart_bot":
# Dá um pequeno delay e depois reinicia o próprio container a partir de fora (pelo host docker)
subprocess.Popen("sleep 1 && docker restart vps-ai-agent", shell=True)
return JSONResponse(content={"status": "success", "message": "Reboot do Agente autorizado. Estará de volta em instantes!"})
return JSONResponse(content={"status": "success", "message": "Reboot do Agente autorizado."})
elif action_type == "clear_cache":
# Roda um docker prune para deletar volumes perdidos e todos containers parados (limpeza profunda)
subprocess.Popen("docker system prune -af --volumes", shell=True)
return JSONResponse(content={"status": "success", "message": "Limpando caches obsoletos em background! Verifique o gráfico de disco em instantes."})
return JSONResponse(content={"status": "success", "message": "Limpando caches obsoletos em background!"})
elif action_type == "reboot_vps":
# Hacker trick: Roda um container hiper-privilegiado descartável pra entrar no espaço do host (PID 1) e emitir comando de REBOOT físico
subprocess.Popen("sleep 2 && docker run --rm --privileged --pid=host alpine nsenter -t 1 -m -u -n -i reboot", shell=True)
return JSONResponse(content={"status": "success", "message": "🚨 O REBOOT CRÍTICO COMEÇOU. A VPS inteira desligará e religará agora."})
return JSONResponse(content={"status": "success", "message": "🚨 O REBOOT CRÍTICO COMEÇOU."})
return JSONResponse(content={"status": "error", "message": "Ação desconhecida."}, status_code=400)
@app.post("/api/chat")
async def web_chat(message: dict):
async def web_chat(message: dict, is_auth: bool = Depends(verify_password)):
"""Endpoint para interagir com a IA via Web UI."""
user_text = message.get("text", "")
if not user_text:
return JSONResponse(content={"reply": "Por favor, digite um comando válido."})
# Executa a IA em uma thread separada para não travar a UI/API de status
reply = await run_in_threadpool(query_agent, prompt=user_text)
return JSONResponse(content={"reply": reply})
import time
@app.get("/api/test_llm")
async def test_llm_speed():
async def test_llm_speed(is_auth: bool = Depends(verify_password)):
"""Mede a velocidade de resposta da IA ativa."""
start_time = time.time()
try:
@@ -116,11 +126,9 @@ async def test_llm_speed():
async def telegram_webhook(request: Request):
"""Recebe as atualizações (mensagens) do Telegram."""
update = await request.json()
# O bot_logic.py lidará com o 'update' no futuro
print("Update recebido do Telegram:", update)
return {"ok": True}
if __name__ == "__main__":
import uvicorn
# Executa o servidor na porta 8000 acessível de qualquer lugar na rede
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)