Files
Gan, Jimmy 6c0d5a4e63 Initial commit: Clean Mold Cost Calculator application
- 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
2025-06-24 02:30:09 +08:00

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)