from flask import Blueprint, render_template, redirect, url_for, request, flash from flask_login import login_user, logout_user, login_required, current_user from app import db from app.models import User from werkzeug.security import generate_password_hash, check_password_hash import time bp = Blueprint('auth', __name__) # ── Rate limiting (dict-based, no external lib) ─────────────────────── _login_attempts: dict = {} _LOGIN_MAX = 10 _LOGIN_WINDOW = 900 # 15 min def _is_rate_limited(ip: str) -> bool: now = time.time() times = [t for t in _login_attempts.get(ip, []) if now - t < _LOGIN_WINDOW] _login_attempts[ip] = times return len(times) >= _LOGIN_MAX def _record_failed(ip: str): _login_attempts.setdefault(ip, []).append(time.time()) # ── Routes ──────────────────────────────────────────────────────────── @bp.route('/') def index(): return redirect(url_for('auth.login')) @bp.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': ip = request.remote_addr or '0.0.0.0' if _is_rate_limited(ip): flash('Demasiados intentos fallidos. Espera 15 minutos.', 'error') return render_template('login.html') email = request.form.get('email', '').strip() password = request.form.get('password', '') user = User.query.filter_by(email=email).first() if user and user.is_active and check_password_hash(user.password_hash, password): login_user(user) if user.role == 'admin': return redirect(url_for('admin.dashboard')) else: return redirect(url_for('owner.dashboard')) else: _record_failed(ip) flash('Credenciales inválidas', 'error') return render_template('login.html') @bp.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('auth.login'))