IOI-RUN / login.py
Roudrigus's picture
Update login.py
44b7086 verified
raw
history blame
9.77 kB
# -*- coding: utf-8 -*-
import os
import streamlit as st
from banco import SessionLocal
from models import Usuario
from utils_seguranca import verificar_senha
from utils_auditoria import registrar_log
# 🔀 Roteador de banco (Produção/Teste/Treinamento) — opcional
# Se não houver db_router.py, usamos fallback suave.
try:
from db_router import (
set_db_choice,
current_db_choice,
list_banks,
bank_label,
get_session_for_current_db, # opcional — se existir, usamos a sessão certa
)
_HAS_ROUTER = True
except Exception:
_HAS_ROUTER = False
def set_db_choice(choice: str):
st.session_state["__db_choice__"] = (choice or "prod").lower()
def current_db_choice() -> str:
return st.session_state.get("__db_choice__", "prod")
def list_banks():
return ["prod", "test"]
def bank_label(choice: str) -> str:
return {"prod": "Banco 1 (📗 Produção)", "test": "Banco 2 (📕 Teste)"}.get(choice, choice)
# -----------------------------------------------------------------------------
# Sessão de banco por ambiente
# -----------------------------------------------------------------------------
def _get_db_session():
"""
Se o router tiver uma fábrica de sessão por banco, usa ela.
Caso contrário, usa SessionLocal() padrão.
"""
try:
if _HAS_ROUTER and callable(get_session_for_current_db): # type: ignore[name-defined]
return get_session_for_current_db() # db per ambiente
except Exception:
pass
return SessionLocal()
# -----------------------------------------------------------------------------
# Efeito de aniversário (visual)
# -----------------------------------------------------------------------------
def _mostrar_efeito_aniversario(nome: str):
"""Exibe imediatamente efeito e mensagem central de aniversário."""
try:
st.balloons()
except Exception:
pass
st.markdown(
f"""
<div style="
display:flex;align-items:center;justify-content:center;
text-align:center;margin:40px 0 20px 0;">
<div style="
font-size: 32px; font-weight: 800; color:#A020F0;
background:linear-gradient(90deg,#FFF0F6,#F0E6FF);
padding:16px 24px;border-radius:16px;box-shadow:0 4px 10px rgba(0,0,0,.08);">
🎉 Feliz Aniversário, {nome}! 🎉
</div>
</div>
""",
unsafe_allow_html=True
)
st.caption("Desejamos a você muitas conquistas e bons embarques ao longo do ano! 💜")
# -----------------------------------------------------------------------------
# Login emergencial / Autologin para testes (via Secrets)
# -----------------------------------------------------------------------------
def _autologin_if_allowed() -> bool:
"""
Se DISABLE_AUTH=1 estiver definido nos Secrets,
entra automático (apenas para teste/homologação).
"""
if os.getenv("DISABLE_AUTH", "0") == "1":
st.session_state.logado = True
st.session_state.usuario = os.getenv("DEMO_USER", "demo")
st.session_state.perfil = os.getenv("DEMO_PERFIL", "admin")
st.session_state.email = os.getenv("DEMO_EMAIL", "demo@example.com")
st.info("🔓 Autologin (DISABLE_AUTH=1) — apenas para teste/homologação.")
return True
return False
def _render_emergency_login():
"""
Login emergencial (opcional), protegido por Secrets:
- ALLOW_EMERGENCY_LOGIN=1 (habilita)
- EMERG_USER (usuário)
- EMERG_PASS_BCRYPT (hash bcrypt da senha) OU EMERG_PASS_PLAIN (apenas para teste)
"""
if os.getenv("ALLOW_EMERGENCY_LOGIN", "0") != "1":
st.info(
"Login temporariamente indisponível.\n\n"
"• Para testar: defina **DISABLE_AUTH=1** em *Settings → Secrets*\n"
"• Para contingência: **ALLOW_EMERGENCY_LOGIN=1**, EMERG_USER e EMERG_PASS_BCRYPT"
)
return
import bcrypt # garantido no requirements
st.markdown("#### Login emergencial")
with st.form("emergency_login"):
u = st.text_input("Usuário", value=os.getenv("EMERG_USER", "admin"))
p = st.text_input("Senha", type="password")
ok = st.form_submit_button("Entrar")
if ok:
user_ok = (u == os.getenv("EMERG_USER", "admin"))
pass_hash = (os.getenv("EMERG_PASS_BCRYPT") or "").strip()
pass_plain= (os.getenv("EMERG_PASS_PLAIN") or "").strip()
pass_ok = False
if pass_hash:
try:
pass_ok = bcrypt.checkpw(p.encode(), pass_hash.encode())
except Exception as e:
st.error(f"Validação bcrypt falhou: {e}")
elif pass_plain:
pass_ok = (p == pass_plain)
st.warning("⚠️ EMERG_PASS_PLAIN em uso. Prefira EMERG_PASS_BCRYPT (mais seguro).")
else:
st.error("Defina EMERG_PASS_BCRYPT (ou EMERG_PASS_PLAIN apenas para teste).")
if user_ok and pass_ok:
st.session_state.logado = True
st.session_state.usuario = u
st.session_state.perfil = "admin"
st.session_state.email = f"{u}@local"
st.success("Login emergencial efetuado.")
st.rerun()
else:
st.error("Usuário e/ou senha inválidos (emergencial).")
# -----------------------------------------------------------------------------
# Tela de Login
# -----------------------------------------------------------------------------
def login():
st.subheader("🔐 Login")
# ✅ Seleção do banco (se houver router)
banks = list_banks()
labels = [bank_label(b) for b in banks]
idx_default = banks.index("prod") if "prod" in banks else 0
banco_label_sel = st.selectbox("Usar banco:", labels, index=idx_default)
db_choice = banks[labels.index(banco_label_sel)]
set_db_choice(db_choice)
# Indicação visual do banco ativo na sidebar
ambiente = current_db_choice()
if ambiente == "prod":
badge = "🟢 Produção"
elif ambiente == "test":
badge = "🔴 Teste"
elif ambiente == "treinamento":
badge = "🔵 Treinamento"
else:
badge = ambiente
st.sidebar.caption(f"🗄️ Banco ativo: {badge}")
# 1) Autologin (debug) — sai daqui logado se DISABLE_AUTH=1
if _autologin_if_allowed():
return
# Campos de credencial
usuario_input = st.text_input("Usuário")
senha_input = st.text_input("Senha", type="password")
# 2) Login normal (DB)
if st.button("Entrar", type="primary"):
db = _get_db_session()
try:
usuario_db = (
db.query(Usuario)
.filter(Usuario.usuario == usuario_input, Usuario.ativo == True)
.first()
)
if not usuario_db or not verificar_senha(senha_input, getattr(usuario_db, "senha", "")):
st.error("❌ Usuário ou senha inválidos.")
# Auditoria — tentativa inválida
try:
registrar_log(
usuario=usuario_input or "(vazio)",
acao="Tentativa de login inválida",
tabela="usuarios",
ambiente=current_db_choice()
)
except Exception:
pass
return
# ✅ LOGIN OK — marca sessão
st.session_state.logado = True
st.session_state.usuario = usuario_db.usuario
st.session_state.perfil = getattr(usuario_db, "perfil", "usuario") or "usuario"
st.session_state.email = getattr(usuario_db, "email", None)
st.session_state.nome = getattr(usuario_db, "nome", None)
# 🔁 Força revalidação do quiz
st.session_state.quiz_verificado = False
# Auditoria de sucesso
try:
registrar_log(
usuario=usuario_db.usuario,
acao="Login realizado com sucesso",
tabela="usuarios",
registro_id=getattr(usuario_db, "id", None),
ambiente=current_db_choice()
)
except Exception:
pass
# 🎂 Checagem de aniversário (mês/dia)
try:
from datetime import date as _date
def _to_date_safe(val):
if not val:
return None
if isinstance(val, _date):
return val
try:
yy, mm, dd = map(int, str(val).split("-"))
return _date(yy, mm, dd)
except Exception:
return None
dn = _to_date_safe(getattr(usuario_db, "data_aniversario", None))
hoje = _date.today()
if dn and dn.month == hoje.month and dn.day == hoje.day:
nome_exibir = st.session_state.get("nome") or st.session_state.get("usuario") or "Usuário"
_mostrar_efeito_aniversario(nome_exibir)
st.session_state["__show_birthday__"] = True
except Exception:
pass
st.success("✅ Login realizado com sucesso!")
st.rerun()
finally:
try:
db.close()
except Exception:
pass
# 3) Login emergencial (opcional) se o normal não passou
if not st.session_state.get("logado"):
_render_emergency_login()
``