diff --git a/users.db b/_data/users.db similarity index 96% rename from users.db rename to _data/users.db index f7fe988b..eb64229e 100644 Binary files a/users.db and b/_data/users.db differ diff --git a/pages/samples.md b/pages/samples.md index 9973c6af..67e2505b 100755 --- a/pages/samples.md +++ b/pages/samples.md @@ -302,7 +302,7 @@ document.addEventListener('DOMContentLoaded', async () => { try { console.log("Verificando autenticação..."); // Verifica auth via Proxy Apache - const res = await fetch('/api/check_auth'); + const res = await fetch('/mmpSearch/api/check_auth'); const authData = await res.json(); console.log("Status do usuário:", authData); @@ -715,7 +715,7 @@ document.addEventListener('DOMContentLoaded', async () => { } // URL CORRIGIDA: Usando Proxy reverso (sem porta 33002) - const API_URL = '/api/upload/sample'; + const API_URL = '/mmpSearch/api/upload/sample'; // UI Loading confirmUploadBtn.classList.add('is-loading'); diff --git a/scripts/handler/upload_server.py b/scripts/handler/upload_server.py index 683c07d5..696f1701 100755 --- a/scripts/handler/upload_server.py +++ b/scripts/handler/upload_server.py @@ -13,21 +13,23 @@ from flask import Flask, request, jsonify, send_file from flask_cors import CORS from werkzeug.utils import secure_filename -# --- NOVAS IMPORTAÇÕES DE AUTH --- from flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user +from flask_admin import Admin, AdminIndexView, expose +from flask_admin.contrib.sqla import ModelView + from main import process_single_file, rebuild_indexes, generate_manifests, slugify -from utils import ALLOWED_EXTENSIONS, ALLOWED_SAMPLE_EXTENSIONS, MMP_FOLDER, MMPZ_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH, SAMPLE_SRC, METADATA_FOLDER, XML_IMPORTED_PATH_PREFIX, SAMPLE_MANIFEST +from utils import ALLOWED_EXTENSIONS, ALLOWED_SAMPLE_EXTENSIONS, MMP_FOLDER, MMPZ_FOLDER, DATA_FOLDER, CERT_PATH, KEY_PATH, BASE_DATA, SRC_MMPSEARCH, SAMPLE_SRC, METADATA_FOLDER, XML_IMPORTED_PATH_PREFIX, SAMPLE_MANIFEST app = Flask(__name__) # --- CONFIGURAÇÃO DE SEGURANÇA E BANCO --- # IMPORTANTE: Troque esta chave em produção! -app.config['SECRET_KEY'] = 'chave_secreta_super_segura_mmp_ecosystem_2025' +app.config['SECRET_KEY'] = '25de5592bf94c2ca18e27baa0ae2d4ee22a63012f32e1be719d31f530c215a387b9ec0c9d96be38e80a7ccdd859e04408facefff8fd9119e7f5a2d987d85abb7' # O banco ficará salvo em /nethome/jotachina/projetos/mmpSearch/users.db (BASE_DATA) -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(BASE_DATA, 'users.db') +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(DATA_FOLDER, 'users.db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # CORS precisa suportar credenciais para o cookie de login funcionar @@ -43,6 +45,24 @@ class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(150), unique=True, nullable=False) password = db.Column(db.String(150), nullable=False) + # Novo campo para dizer se é chefe ou não + is_admin = db.Column(db.Boolean, default=False) + +class SecureModelView(ModelView): + def is_accessible(self): + return current_user.is_authenticated and current_user.is_admin + + def inaccessible_callback(self, name, **kwargs): + # Retorna erro JSON ou redireciona (como é painel, melhor redirecionar ou negar) + return jsonify({"error": "Acesso restrito a administradores."}), 403 + +# Classe para proteger a Home do Admin (Dashboard) +class SecureIndexView(AdminIndexView): + @expose('/') + def index(self): + if not current_user.is_authenticated or not current_user.is_admin: + return jsonify({"error": "Acesso restrito."}), 403 + return super(SecureIndexView, self).index() # Cria o banco na inicialização se não existir with app.app_context(): @@ -223,7 +243,7 @@ def process_and_build(filename): # ROTAS DE AUTENTICAÇÃO # ============================================================================== -@app.route('/api/register', methods=['POST']) +@app.route('/mmpSearch/api/register', methods=['POST']) def register(): data = request.json if not data or not data.get('username') or not data.get('password'): @@ -239,7 +259,7 @@ def register(): except Exception as e: return jsonify({"message": f"Erro: {str(e)}"}), 500 -@app.route('/api/login', methods=['POST']) +@app.route('/mmpSearch/api/login', methods=['POST']) def login(): data = request.json user = User.query.filter_by(username=data['username']).first() @@ -248,13 +268,13 @@ def login(): return jsonify({"message": "Login realizado", "user": user.username}), 200 return jsonify({"message": "Credenciais inválidas"}), 401 -@app.route('/api/logout', methods=['POST']) +@app.route('/mmpSearch/api/logout', methods=['POST']) @login_required def logout(): logout_user() return jsonify({"message": "Logout realizado"}), 200 -@app.route('/api/check_auth', methods=['GET']) +@app.route('/mmpSearch/api/check_auth', methods=['GET']) def check_auth(): if current_user.is_authenticated: return jsonify({"logged_in": True, "user": current_user.username}) @@ -264,7 +284,7 @@ def check_auth(): # ROTAS PRINCIPAIS # ============================================================================== -@app.route("/api/download/", methods=["GET"]) +@app.route("/mmpSearch/api/download/", methods=["GET"]) def download_project_package(project_name): """Gera um ZIP com caminhos limpos (Não exige login para baixar).""" if not project_name.lower().endswith('.mmp'): @@ -311,7 +331,7 @@ def download_project_package(project_name): return jsonify({"error": f"Erro ao gerar pacote: {str(e)}"}), 500 -@app.route("/api/upload", methods=["POST"]) +@app.route("/mmpSearch/api/upload", methods=["POST"]) @login_required # <--- PROTEGIDO def upload_file(): if "project_file" not in request.files: @@ -367,7 +387,7 @@ def upload_file(): return jsonify({"error": "Tipo de arquivo não permitido"}), 400 -@app.route("/api/upload/resolve", methods=["POST"]) +@app.route("/mmpSearch/api/upload/resolve", methods=["POST"]) @login_required # <--- PROTEGIDO def resolve_samples(): project_filename = request.form.get('project_file') @@ -395,7 +415,7 @@ def resolve_samples(): return jsonify({"error": "Falha ao atualizar o arquivo de projeto."}), 500 -@app.route('/api/upload/sample', methods=['POST']) +@app.route('/mmpSearch/api/upload/sample', methods=['POST']) @login_required # <--- PROTEGIDO def upload_sample_standalone(): if 'sample_file' not in request.files: return jsonify({'error': 'Nenhum arquivo'}), 400 @@ -412,12 +432,19 @@ def upload_sample_standalone(): return jsonify({'message': 'Sample enviado!'}), 200 return jsonify({'error': 'Erro no arquivo'}), 400 +# Inicializa o Flask-Admin +admin = Admin( + app, + name='MMP Admin', + index_view=SecureIndexView(url='/api/admin') +) + +# Adiciona a visualização da tabela de Usuários +admin.add_view(SecureModelView(User, db.session, name="Gerenciar Usuários")) + if __name__ == "__main__": - # Se estiver rodando atrás do Apache, NÃO use ssl_context. - # O Apache já cuida do SSL (https://alice.ufsj.edu.br). - # O Flask deve rodar em HTTP puro localmente. + # DEBUG: Isso vai mostrar no log todas as rotas que o Flask reconheceu + print(app.url_map) print("Iniciando servidor na porta 33002 (HTTP Mode)...") - app.run(host="0.0.0.0", port=33002, debug=True) - - # Removi o ssl_context=context para facilitar o proxy reverso \ No newline at end of file + app.run(host="0.0.0.0", port=33002, debug=True) \ No newline at end of file