6c0d5a4e63
- Flask-based mold cost calculation application - PostgreSQL database with SQLAlchemy ORM - Docker/Podman containerization - Comprehensive documentation in docs/ - Clean, organized codebase without obsolete files - Production-ready deployment configuration - Enhanced pytest configuration with coverage reporting - Master/Development branch structure
171 lines
7.6 KiB
Python
171 lines
7.6 KiB
Python
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/<token>', 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) |