"""Admin routes for Sneaky Klaus application.""" from datetime import timedelta from flask import Blueprint, flash, redirect, render_template, session, url_for from src.app import bcrypt, db from src.decorators import admin_required from src.forms import LoginForm from src.models import Admin from src.utils import check_rate_limit, increment_rate_limit, reset_rate_limit admin_bp = Blueprint("admin", __name__, url_prefix="/admin") # Rate limiting constants MAX_LOGIN_ATTEMPTS = 5 LOGIN_WINDOW_MINUTES = 15 @admin_bp.route("/login", methods=["GET", "POST"]) def login(): """Handle admin login. GET: Display login form. POST: Process login credentials and create session. Returns: On GET: Rendered login form template. On POST success: Redirect to admin dashboard. On POST error: Re-render form with validation errors. """ # If already logged in, redirect to dashboard if "admin_id" in session: return redirect(url_for("admin.dashboard")) form = LoginForm() if form.validate_on_submit(): # Normalize email to lowercase email = form.email.data.lower() # Check rate limit rate_limit_key = f"login:admin:{email}" if check_rate_limit(rate_limit_key, MAX_LOGIN_ATTEMPTS, LOGIN_WINDOW_MINUTES): flash("Too many login attempts. Please try again in 15 minutes.", "error") return render_template("admin/login.html", form=form), 429 # Query admin by email admin = db.session.query(Admin).filter_by(email=email).first() # Verify credentials if admin and bcrypt.check_password_hash( admin.password_hash, form.password.data ): # Reset rate limit on successful login reset_rate_limit(rate_limit_key) # Create session session.clear() session["admin_id"] = admin.id session["admin_email"] = admin.email session.permanent = True # Set session duration based on remember_me if form.remember_me.data: session.permanent_session_lifetime = timedelta(days=30) else: session.permanent_session_lifetime = timedelta(days=7) flash("Welcome back!", "success") return redirect(url_for("admin.dashboard")) else: # Invalid credentials - increment rate limit increment_rate_limit(rate_limit_key, LOGIN_WINDOW_MINUTES) flash("Invalid email or password.", "error") return render_template("admin/login.html", form=form) @admin_bp.route("/logout", methods=["POST"]) @admin_required def logout(): """Handle admin logout. POST: Clear session and redirect to login page. Returns: Redirect to login page. """ session.clear() flash("You have been logged out.", "success") return redirect(url_for("admin.login")) @admin_bp.route("/dashboard") @admin_required def dashboard(): """Display admin dashboard. Returns: Rendered admin dashboard template. """ return render_template("admin/dashboard.html")