from flask import Blueprint, render_template, request, redirect, url_for, flash from flask_login import login_user, logout_user, login_required, current_user from werkzeug.security import check_password_hash from app.models import User, db from app.routes.reset_password import RequestResetForm, ResetPasswordForm from flask_mail import Message from app.routes.mail import mail from app.utils.token import generate_reset_token, verify_reset_token import re from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import DataRequired, Email, EqualTo, Length auth = Blueprint('auth', __name__) class LoginForm(FlaskForm): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()]) remember_me = BooleanField('Remember Me') submit = SubmitField('Sign In') class RegisterForm(FlaskForm): nickname = StringField('Nickname', validators=[DataRequired(), Length(min=3, max=32)]) email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired(), Length(min=8)]) password2 = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) submit = SubmitField('Register') class ForgotPasswordForm(FlaskForm): email = StringField('Email', validators=[DataRequired(), Email()]) submit = SubmitField('Request Password Reset') @auth.route('/login', methods=['GET', 'POST']) def login(): """User login page""" if current_user.is_authenticated: return redirect(url_for('main.index')) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user and user.check_password(form.password.data): login_user(user, remember=form.remember_me.data) next_page = request.args.get('next') if not next_page or not next_page.startswith('/'): next_page = url_for('community.index') flash(f'Welcome back, {user.nickname}!', 'success') return redirect(next_page) else: flash('Invalid email or password.', 'error') return render_template('auth/login.html', form=form) @auth.route('/register', methods=['GET', 'POST']) def register(): """User registration page""" if current_user.is_authenticated: return redirect(url_for('main.index')) form = RegisterForm() if form.validate_on_submit(): # Check if user already exists if User.query.filter_by(email=form.email.data).first(): flash('Email address already registered.', 'error') return render_template('auth/register.html', form=form) if User.query.filter_by(nickname=form.nickname.data).first(): flash('Nickname already taken.', 'error') return render_template('auth/register.html', form=form) # Validate password strength if not is_valid_password(form.password.data): flash('Password must be at least 8 characters long and contain at least one letter and one number.', 'error') return render_template('auth/register.html', form=form) # Create new user user = User( nickname=form.nickname.data, email=form.email.data ) user.set_password(form.password.data) try: db.session.add(user) db.session.commit() login_user(user) flash('Registration successful! Welcome to the community!', 'success') return redirect(url_for('community.index')) except Exception as e: db.session.rollback() flash('An error occurred during registration. Please try again.', 'error') return render_template('auth/register.html', form=form) @auth.route('/logout') @login_required def logout(): """User logout""" logout_user() flash('You have been logged out.', 'info') return redirect(url_for('main.index')) @auth.route('/forgot-password', methods=['GET', 'POST']) def forgot_password(): """Forgot password page""" if current_user.is_authenticated: return redirect(url_for('main.index')) form = RequestResetForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user: token = generate_reset_token(user.email) reset_url = url_for('auth.reset_password', token=token, _external=True) msg = Message( subject="Password Reset Request", recipients=[user.email], body=f"Hello {user.nickname},\n\nTo reset your password, click the link below:\n{reset_url}\n\nIf you did not request this, please ignore this email." ) try: mail.send(msg) except Exception as e: flash(f"Failed to send reset email: {e}", "danger") flash('If an account with that email exists, we\'ve sent password reset instructions.', 'info') return redirect(url_for('auth.login')) return render_template('auth/forgot_password.html', form=form) # Password reset route @auth.route('/reset-password/', methods=['GET', 'POST']) def reset_password(token): if current_user.is_authenticated: return redirect(url_for('main.index')) email = verify_reset_token(token) if not email: flash('Invalid or expired reset link.', 'danger') return redirect(url_for('auth.forgot_password')) user = User.query.filter_by(email=email).first() if not user: flash('Invalid or expired reset link.', 'danger') return redirect(url_for('auth.forgot_password')) form = ResetPasswordForm() if form.validate_on_submit(): user.set_password(form.password.data) db.session.commit() flash('Your password has been reset. You can now log in.', 'success') return redirect(url_for('auth.login')) return render_template('auth/reset_password.html', form=form) def is_valid_password(password): """Validate password strength""" if len(password) < 8: return False if not re.search(r'[A-Za-z]', password): return False if not re.search(r'\d', password): return False return True