from flask import Blueprint, render_template, request, redirect, url_for, flash from flask_login import login_user, logout_user, login_required, UserMixin, current_user from werkzeug.security import generate_password_hash, check_password_hash from extensions import db, login_manager, environment, htmx from jinja2_fragments import render_block auth = Blueprint('auth', __name__) def get_client_ip(): """Get real client IP address, checking proxy headers first""" # Check common proxy headers in order of preference if request.headers.get('X-Forwarded-For'): # X-Forwarded-For can contain multiple IPs, get the first (original client) return request.headers.get('X-Forwarded-For').split(',')[0].strip() elif request.headers.get('X-Real-IP'): return request.headers.get('X-Real-IP') elif request.headers.get('CF-Connecting-IP'): # Cloudflare return request.headers.get('CF-Connecting-IP') else: # Fallback to direct connection IP return request.remote_addr class User(UserMixin): def __init__(self, id, username, password_hash, created_at, theme_preference='light', email=None): self.id = id self.username = username self.password_hash = password_hash self.created_at = created_at self.theme_preference = theme_preference self.email = email @staticmethod def get(user_id): user_data = db.get_user(int(user_id)) if user_data: return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at'], theme_preference=user_data.get('theme_preference', 'light'), email=user_data.get('email')) return None @login_manager.user_loader def load_user(user_id): user_data = db.get_user(int(user_id)) if user_data: return User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at'], theme_preference=user_data.get('theme_preference', 'light'), email=user_data.get('email')) return None @auth.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template("login.html") username = request.form.get('username') password = request.form.get('password') if not username or not password: return render_template("login.html", error="Both username and password must be entered") user_data = db.get_user_by_username(username) if not user_data: # Record failed login attempt with real IP db.record_login(None, get_client_ip(), str(request.user_agent), False, "User not found") return render_template("login.html", error="User does not exist") if not check_password_hash(user_data['password_hash'], password): # Record failed login attempt with real IP db.record_login(user_data['id'], get_client_ip(), str(request.user_agent), False, "Invalid password") return render_template("login.html", error="Invalid username or password") user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at'], theme_preference=user_data.get('theme_preference', 'light'), email=user_data.get('email')) # Record successful login with real IP db.record_login(user.id, get_client_ip(), str(request.user_agent), True) login_user(user) next = request.args.get('next') return redirect(next or url_for('home.index')) @auth.route('/signup', methods=['GET', 'POST']) def signup(): if request.method == 'GET': return render_template("signup.html") username = request.form.get('username') password = request.form.get('password') if not username or not password: return render_template("signup.html", error="Both username and password must be entered") if len(username) < 10 or len(password) < 10: return render_template("signup.html", error="Both username and password must be at least 10 characters long") user = db.get_user_by_username(username) if user: return render_template("signup.html", error="User already exists") hashed_password = generate_password_hash(password) user_data = db.create_new_user(username, hashed_password) user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at'], theme_preference=user_data.get('theme_preference', 'light'), email=user_data.get('email')) login_user(user) return redirect(url_for('home.index')) @auth.route("/logout") @login_required def logout(): logout_user() return redirect(url_for('landing_page'))