from flask import Blueprint, render_template, redirect, url_for, request, flash, current_app from flask_login import login_user, logout_user, login_required, current_user from app.forms import LoginForm, RegistrationForm, RequestPasswordResetForm, ResetPasswordForm from app import create_app, db from app import limiter from datetime import datetime, timezone from app.config import ALLOWED_EMAILS import logging from flask_mail import Message from app import mail from app.models import User bp = Blueprint('auth', __name__) logger = logging.getLogger(__name__) @bp.route('/login', methods=['GET', 'POST']) @limiter.limit("5 per minute") def login(): if current_user.is_authenticated: return redirect(url_for('main.calculator')) form = LoginForm() if form.validate_on_submit(): try: email = form.email.data.strip().lower() password = form.password.data # Log the login attempt current_app.logger.info(f'Login attempt for email: {email}') user = User.authenticate(email, password) if user: login_user(user, remember=form.remember_me.data) user.last_login = datetime.now(timezone.utc) db.session.commit() current_app.logger.info(f'User {user.email} logged in successfully') next_page = request.args.get('next') or url_for('main.calculator') return redirect(next_page) else: current_app.logger.warning(f'Failed login attempt for email: {email}') flash('Invalid email or password', 'danger') except Exception as e: current_app.logger.error(f'Login error: {str(e)}', exc_info=True) flash('An error occurred during login. Please try again.', 'danger') return render_template('auth/login.html', form=form) @bp.route('/register', methods=['GET', 'POST']) @limiter.limit(lambda: None if current_app.config.get('DEBUG', False) else "10 per hour") def register(): try: if current_user.is_authenticated: return redirect(url_for('main.home')) form = RegistrationForm() logger.info('Registration form initialized') if form.validate_on_submit(): try: email = form.email.data.strip().lower() username = form.username.data.strip().lower() company = form.company.data.strip().upper() logger.info(f'Processing registration for email: {email}, username: {username}, company: {company}') # Check if email is in allowed list or has adidas.com domain if email not in ALLOWED_EMAILS and not email.endswith('@adidas.com'): logger.warning(f'Registration attempt with non-allowed email: {email}') flash('Registration is not allowed for this email domain', 'danger') return render_template('auth/register.html', form=form) try: logger.info('Creating new user...') user = User.create( username=username, email=email, company=company, password=form.password.data ) logger.info('User created, committing to database...') db.session.commit() logger.info(f'New user registered successfully: {user.email}') flash('Registration successful! Please login', 'success') return redirect(url_for('auth.login')) except ValueError as e: logger.warning(f'Registration validation error: {str(e)}') flash(str(e), 'danger') except Exception as e: db.session.rollback() logger.error(f'Registration error: {str(e)}', exc_info=True) flash('An error occurred during registration', 'danger') except Exception as e: logger.error(f'Unexpected error during registration: {str(e)}', exc_info=True) flash('An unexpected error occurred', 'danger') elif form.errors: logger.warning(f'Form validation errors: {form.errors}') for field, errors in form.errors.items(): for error in errors: flash(f'{field}: {error}', 'danger') logger.warning(f'Field {field} validation error: {error}') logger.info('Rendering registration template') return render_template('auth/register.html', form=form) except Exception as e: logger.error(f'Critical error in registration route: {str(e)}', exc_info=True) flash('A critical error occurred', 'danger') return render_template('auth/register.html', form=form) @bp.route('/logout') @login_required def logout(): current_app.logger.info(f'User {current_user.email} logged out') logout_user() flash('You have been logged out.', 'info') return redirect(url_for('main.home')) @bp.route('/request-reset', methods=['GET', 'POST']) @limiter.limit("3 per hour") def request_reset(): if current_user.is_authenticated: return redirect(url_for('main.calculator')) form = RequestPasswordResetForm() if form.validate_on_submit(): try: email = form.email.data.strip().lower() user = User.query.filter_by(email=email).first() if user: token = user.generate_reset_token() db.session.commit() reset_url = url_for('auth.reset_password', token=token, _external=True) flash(f'Your password reset link is: {reset_url}', 'info') flash('This link will expire in 15 minutes.', 'warning') return redirect(url_for('auth.login')) # Always show success message to prevent email enumeration flash('If an account exists with that email, you will receive password reset instructions.', 'info') return redirect(url_for('auth.login')) except Exception as e: current_app.logger.error(f'Error generating reset token: {str(e)}', exc_info=True) flash('An error occurred while processing your request.', 'danger') return render_template('auth/request_reset.html', form=form) @bp.route('/reset-password/', methods=['GET', 'POST']) @limiter.limit("3 per hour") def reset_password(token): if current_user.is_authenticated: return redirect(url_for('main.calculator')) form = ResetPasswordForm() if form.validate_on_submit(): try: user = User.query.filter_by(reset_token=token).first() if not user or not user.verify_reset_token(token): flash('The password reset link is invalid or has expired.', 'danger') return redirect(url_for('auth.request_reset')) user.set_password(form.password.data) db.session.commit() current_app.logger.info(f'Password reset successful for user {user.email}') flash('Your password has been reset. You can now log in.', 'success') return redirect(url_for('auth.login')) except Exception as e: current_app.logger.error(f'Error resetting password: {str(e)}', exc_info=True) flash('An error occurred while resetting your password.', 'danger') return render_template('auth/reset_password.html', form=form)