"""Authentication routes for user login, logout, and registration. This module handles user authentication including login, logout, and new user registration. """ from flask import Blueprint, render_template, redirect, url_for, flash, request from flask_login import login_user, logout_user, login_required, current_user from extensions import db, hash_password, check_password, limiter from models import User, ESPORT_GAMES from urllib.parse import urlparse def is_safe_url(url): """Validate that a URL is safe for redirection (same origin). Args: url: The URL to validate. Returns: bool: True if the URL is safe (relative or same origin). """ if not url: return False parsed = urlparse(url) # Allow relative URLs (no netloc) or same-origin URLs return not parsed.netloc or parsed.netloc == request.host auth_bp = Blueprint('auth', __name__, url_prefix='/auth') @auth_bp.route('/login', methods=['GET', 'POST']) @limiter.limit("10 per minute") def login(): """Handle user login authentication. GET: Render the login form. POST: Authenticate user credentials and log them in. Redirects authenticated users to dashboard. Validates credentials and checks account status before login. Returns: Response: Login form or redirect to dashboard/next page. """ if current_user.is_authenticated: return redirect(url_for('main.dashboard')) if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') user = User.query.filter_by(username=username).first() if user and check_password(user.password_hash, password): if not user.is_active_account: flash('This account has been deactivated.', 'danger') return render_template('pages/login.html') # Regenerate session to prevent session fixation attacks login_user(user) # Validate redirect URL to prevent open redirect vulnerability next_page = request.args.get('next') if next_page and not is_safe_url(next_page): next_page = None flash(f'Welcome back, {user.full_name}!', 'success') return redirect(next_page) if next_page else redirect(url_for('main.dashboard')) else: flash('Login unsuccessful. Please check username and password.', 'danger') return render_template('pages/login.html') @auth_bp.route('/register', methods=['GET', 'POST']) def register(): """Handle new player registration. GET: Render the registration form with E-Sports games list. POST: Create a new player account with provided details. Only players can register through this form. Validates username/email uniqueness and password confirmation. Returns: Response: Registration form or redirect to login. """ if current_user.is_authenticated: return redirect(url_for('main.dashboard')) if request.method == 'POST': username = request.form.get('username') email = request.form.get('email') password = request.form.get('password') confirm_password = request.form.get('confirm_password') full_name = request.form.get('full_name') phone = request.form.get('phone') # E-Sports fields selected_games = request.form.getlist('games') trn_username = request.form.get('trn_username', '').strip() discord_username = request.form.get('discord_username', '').strip() league_os_profile = request.form.get('league_os_profile', '').strip() if password != confirm_password: flash('Passwords do not match.', 'danger') return render_template('pages/register.html', esport_games=ESPORT_GAMES) if User.query.filter_by(username=username).first(): flash('Username already exists.', 'danger') return render_template('pages/register.html', esport_games=ESPORT_GAMES) if User.query.filter_by(email=email).first(): flash('Email already registered.', 'danger') return render_template('pages/register.html', esport_games=ESPORT_GAMES) hashed_password = hash_password(password) user = User( username=username, password_hash=hashed_password, role='player', full_name=full_name, email=email, phone=phone, games=','.join(selected_games) if selected_games else None, trn_username=trn_username or None, discord_username=discord_username or None, league_os_profile=league_os_profile or None ) db.session.add(user) db.session.commit() flash('Your account has been created! You can now log in.', 'success') return redirect(url_for('auth.login')) return render_template('pages/register.html', esport_games=ESPORT_GAMES) @auth_bp.route('/logout') @login_required def logout(): """Log out the current user. Clears the user session and redirects to the login page. Returns: Response: Redirect to login page with logout message. """ logout_user() flash('You have been logged out.', 'info') return redirect(url_for('auth.login'))