Login finalizado, admin funcionando corretamente e sendo gerado oQRcode na raiz do projeto

This commit is contained in:
LS
2025-03-27 14:34:16 -03:00
parent 1367389619
commit bae6b1ae14
6 changed files with 153 additions and 26 deletions

View File

@@ -1,5 +1,12 @@
install:
pip install -r requirements.txt
run:
clean:
rm -rf ~/.local/share/controles/database.db
rm -f admin_qr.png
run: clean
python app.py
reset-admin: clean
python create_admin.py

BIN
admin_qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

105
app.py
View File

@@ -1,4 +1,4 @@
from flask import Flask, request, render_template, redirect, url_for, flash, session
from flask import Flask, request, render_template, redirect, url_for, flash, session, jsonify
from functions.database import (
Base,
Militante,
@@ -17,12 +17,14 @@ from functions.database import (
)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from datetime import datetime
from datetime import datetime, timedelta
from flask_bootstrap import Bootstrap5
from routes.cota import cota_bp
from functions.validations import validar_cpf
from functools import wraps
from pathlib import Path
from time import time
from create_admin import generate_qr_code
app = Flask(__name__)
app.secret_key = 'sua_chave_secreta_aqui' # Necessário para sessões do Flask
@@ -41,6 +43,39 @@ def login_required(f):
return f(*args, **kwargs)
return decorated_function
# Decorator para verificar se a sessão expirou
def session_timeout(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return redirect(url_for('login'))
# Verificar se existe timestamp do último acesso
if 'last_activity' in session:
last_activity = datetime.fromtimestamp(session['last_activity'])
now = datetime.now()
# Se passaram mais de 2 horas
if now - last_activity > timedelta(hours=2):
# Registrar o logout por timeout
try:
user = db_session.query(Usuario).get(session['user_id'])
if user:
user.ultimo_logout = datetime.now()
user.motivo_logout = "Timeout de sessão"
db_session.commit()
except Exception as e:
print(f"Erro ao registrar logout por timeout: {e}")
session.clear()
flash('Sua sessão expirou. Por favor, faça login novamente.', 'warning')
return redirect(url_for('login'))
# Atualizar timestamp de último acesso
session['last_activity'] = time()
return f(*args, **kwargs)
return decorated_function
# Rota raiz - redireciona para login se não estiver autenticado
@app.route("/")
def index():
@@ -85,6 +120,12 @@ def login():
print(f"Login bem sucedido para o usuário '{username}'")
session['user_id'] = user.id
session['is_admin'] = user.is_admin
session['last_activity'] = time() # Inicializar timestamp
# Registrar horário do login
user.ultimo_login = datetime.now()
db_session.commit()
next_page = request.args.get('next')
flash('Login realizado com sucesso!', 'success')
return redirect(next_page or url_for('home'))
@@ -101,6 +142,7 @@ def logout():
# Rota home (protegida)
@app.route("/home")
@login_required
@session_timeout
def home():
"""Página inicial do sistema"""
try:
@@ -137,6 +179,7 @@ def home():
# Rota para criar um novo militante
@app.route("/militantes/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def novo_militante():
if request.method == "POST":
cpf = request.form["cpf"]
@@ -172,6 +215,7 @@ def novo_militante():
# Rota para listar militantes
@app.route("/militantes")
@login_required
@session_timeout
def listar_militantes():
militantes = db_session.query(Militante).all()
return render_template("listar_militantes.html", militantes=militantes)
@@ -179,6 +223,7 @@ def listar_militantes():
# Rota para criar uma nova cota mensal
@app.route("/cotas/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def nova_cota():
if request.method == "POST":
cotas_mensais = CotaMensal(
@@ -203,6 +248,7 @@ def nova_cota():
# Rota para listar cotas mensais
@app.route("/cotas")
@login_required
@session_timeout
def listar_cotas():
cotas = db_session.query(CotaMensal).all()
return render_template("listar_cotas.html", cotas=cotas)
@@ -210,6 +256,7 @@ def listar_cotas():
# Rota para criar um novo pagamento
@app.route("/pagamentos/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def novo_pagamento():
if request.method == "POST":
pagamentos = Pagamento(
@@ -234,6 +281,7 @@ def novo_pagamento():
# Rota para listar pagamentos
@app.route("/pagamentos")
@login_required
@session_timeout
def listar_pagamentos():
pagamentos = db_session.query(Pagamento).all()
return render_template("listar_pagamentos.html", pagamentos=pagamentos)
@@ -241,6 +289,7 @@ def listar_pagamentos():
# Rota para criar um novo material vendido
@app.route("/materiais/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def novo_material():
if request.method == "POST":
materiais_vendidos = MaterialVendido(
@@ -266,6 +315,7 @@ def novo_material():
# Rota para listar materiais vendidos
@app.route("/materiais")
@login_required
@session_timeout
def listar_materiais():
materiais = db_session.query(MaterialVendido).all()
return render_template("listar_materiais.html", materiais=materiais)
@@ -273,6 +323,7 @@ def listar_materiais():
# Rota para criar uma nova venda de jornais avulsos
@app.route("/jornais/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def nova_venda_jornal():
if request.method == "POST":
vendas_jornais_avulsos = VendaJornalAvulso(
@@ -297,6 +348,7 @@ def nova_venda_jornal():
# Rota para listar vendas de jornais avulsos
@app.route("/jornais")
@login_required
@session_timeout
def listar_vendas_jornal():
vendas = db_session.query(VendaJornalAvulso).all()
return render_template("listar_vendas_jornal.html", vendas=vendas)
@@ -304,6 +356,7 @@ def listar_vendas_jornal():
# Rota para criar uma nova assinatura anual
@app.route("/assinaturas/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def nova_assinatura():
if request.method == "POST":
assinaturas_anuais = AssinaturaAnual(
@@ -330,6 +383,7 @@ def nova_assinatura():
# Rota para listar assinaturas anuais
@app.route("/assinaturas")
@login_required
@session_timeout
def listar_assinaturas():
assinaturas = db_session.query(AssinaturaAnual).all()
return render_template("listar_assinaturas.html", assinaturas=assinaturas)
@@ -337,6 +391,7 @@ def listar_assinaturas():
# Rota para criar um novo relatório de cotas mensais
@app.route("/relatorios/cotas/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def novo_relatorio_cotas():
if request.method == "POST":
relatorio_cotas_mensais = RelatorioCotasMensais(
@@ -361,6 +416,7 @@ def novo_relatorio_cotas():
# Rota para listar relatórios de cotas mensais
@app.route("/relatorios/cotas")
@login_required
@session_timeout
def listar_relatorios_cotas():
relatorios = db_session.query(RelatorioCotasMensais).all()
return render_template("listar_relatorios_cotas.html", relatorios=relatorios)
@@ -368,6 +424,7 @@ def listar_relatorios_cotas():
# Rota para criar um novo relatório de vendas de materiais
@app.route("/relatorios/vendas/novo", methods=["GET", "POST"])
@login_required
@session_timeout
def novo_relatorio_vendas():
if request.method == "POST":
relatorio_vendas_materiais = RelatorioVendasMateriais(
@@ -392,12 +449,14 @@ def novo_relatorio_vendas():
# Rota para listar relatórios de vendas de materiais
@app.route("/relatorios/vendas")
@login_required
@session_timeout
def listar_relatorios_vendas():
relatorios = db_session.query(RelatorioVendasMateriais).all()
return render_template("listar_relatorios_vendas.html", relatorios=relatorios)
@app.route("/militantes/editar/<int:id>", methods=["GET", "POST"])
@login_required
@session_timeout
def editar_militante(id):
militante = db_session.query(Militante).get(id)
if not militante:
@@ -469,17 +528,11 @@ def novo_usuario():
db_session.add(novo_usuario)
db_session.commit()
# Gerar QR code para configuração do OTP
# Gerar QR code usando a função do create_admin.py
qr_uri = novo_usuario.get_otp_uri()
qr_path = generate_qr_code(novo_usuario)
flash('Usuário criado com sucesso!', 'success')
# Modificação alternativa para salvar no diretório atual
qr_path = Path('admin_qr.png')
img = qrcode.QRCode(version=1, box_size=10, border=5)
img.add_data(qr_uri)
img.make(fit=True)
img.save(str(qr_path))
return render_template('mostrar_qr_code.html', qr_uri=qr_uri)
except Exception as e:
@@ -490,6 +543,30 @@ def novo_usuario():
return render_template('novo_usuario.html')
@app.route('/check_session')
def check_session():
if 'last_activity' not in session:
return jsonify({'expired': True})
last_activity = datetime.fromtimestamp(session['last_activity'])
now = datetime.now()
if now - last_activity > timedelta(hours=2):
# Registrar o logout por timeout
try:
user = db_session.query(Usuario).get(session.get('user_id'))
if user:
user.ultimo_logout = datetime.now()
user.motivo_logout = "Timeout de sessão"
db_session.commit()
except Exception as e:
print(f"Erro ao registrar logout por timeout: {e}")
session.clear()
return jsonify({'expired': True})
return jsonify({'expired': False})
def create_app():
app = Flask(__name__)
# ... existing code ...
@@ -501,4 +578,10 @@ def create_app():
# Iniciar o servidor Flask
if __name__ == "__main__":
# Verificar se existe usuário admin e gerar QR code
admin = db_session.query(Usuario).filter_by(username="admin").first()
if admin:
print("\n=== Gerando QR Code para usuário admin existente ===")
generate_qr_code(admin)
app.run(debug=True)

View File

@@ -4,6 +4,36 @@ import os
from pathlib import Path
import pyotp
def generate_qr_code(user):
"""
Gera o QR code para um usuário específico
Args:
user: Instância do modelo Usuario
Returns:
Path: Caminho do arquivo QR code gerado
"""
import qrcode
# Gerar QR Code apenas na raiz do projeto
qr_path = Path('admin_qr.png')
# Remover arquivo antigo se existir
if qr_path.exists():
os.remove(str(qr_path))
# Gerar e salvar QR Code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(user.get_otp_uri())
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(str(qr_path))
print(f"\nQR Code gerado em: {os.path.abspath(qr_path)}")
return qr_path
def create_admin_user():
try:
# Inicializar o banco de dados
@@ -17,6 +47,8 @@ def create_admin_user():
if admin:
print("\n=== Usuário Admin Encontrado ===")
# Atualizar QR code mesmo se o usuário já existir
qr_path = generate_qr_code(admin)
else:
print("\n=== Criando Novo Usuário Admin ===")
# Criar usuário admin com novo segredo OTP
@@ -45,21 +77,11 @@ def create_admin_user():
totp = pyotp.TOTP(admin.otp_secret)
otp_uri = totp.provisioning_uri(
name=admin.username,
issuer_name="Sistema de Gestão"
issuer_name="Sistema de Controles"
)
# Configurar diretório para o QR Code
home = Path.home()
qr_dir = home / '.local' / 'share' / 'controles' / 'qrcodes'
qr_dir.mkdir(parents=True, exist_ok=True)
# Gerar e salvar QR Code
qr_path = qr_dir / 'admin_qr.png'
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(otp_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(str(qr_path))
# Usar a função extraída para gerar o QR code
qr_path = generate_qr_code(admin)
print("\n=== QR Code Gerado ===")
print(f"QR Code salvo em: {qr_path}")

View File

@@ -235,7 +235,7 @@ class Usuario(Base):
totp = pyotp.TOTP(self.otp_secret)
return totp.provisioning_uri(
name=self.username,
issuer_name="Sistema de Gestão"
issuer_name="Sistema de Controles"
)
class Role(Base):

View File

@@ -48,5 +48,20 @@
{{ bootstrap.load_js() }}
<!-- Adicionar Font Awesome para o ícone de Sair -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
{% if 'user_id' in session %}
<script>
// Verificar a sessão a cada minuto
setInterval(function() {
fetch('/check_session')
.then(response => response.json())
.then(data => {
if (data.expired) {
window.location.href = '/login';
}
});
}, 60000);
</script>
{% endif %}
</body>
</html>