Login finalizado, admin funcionando corretamente e sendo gerado oQRcode na raiz do projeto
This commit is contained in:
9
Makefile
9
Makefile
@@ -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
BIN
admin_qr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
105
app.py
105
app.py
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user