# plugins/auth/routes.py from datetime import datetime from flask import ( Blueprint, render_template, redirect, flash, url_for, request ) from flask_login import ( login_user, logout_user, login_required, current_user ) from app import db from .models import User, Invitation from .forms import ( LoginForm, RegistrationForm, InviteForm, AdjustInvitesForm ) bp = Blueprint( 'auth', __name__, template_folder='templates', url_prefix='/auth' ) @bp.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('home')) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data.lower()).first() if user and user.check_password(form.password.data): login_user(user, remember=form.remember_me.data) flash('Logged in successfully.', 'success') next_page = request.args.get('next') or url_for('home') return redirect(next_page) flash('Invalid email or password.', 'danger') return render_template('auth/login.html', form=form) @bp.route('/logout') @login_required def logout(): logout_user() flash('Logged out.', 'info') return redirect(url_for('home')) @bp.route('/register', methods=['GET', 'POST']) def register(): if current_user.is_authenticated: return redirect(url_for('home')) invite_code = request.args.get('invite', '').strip() form = RegistrationForm(invitation_code=invite_code) if form.validate_on_submit(): # Validate invitation invitation = Invitation.query.filter_by( code=form.invitation_code.data, is_active=True, is_used=False ).first() if not invitation: flash('Registration is by invitation only. Provide a valid code.', 'warning') return render_template('auth/register.html', form=form) if invitation.recipient_email and \ invitation.recipient_email.lower() != form.email.data.lower(): flash('This invitation is not valid for that email address.', 'warning') return render_template('auth/register.html', form=form) # Create the user user = User(email=form.email.data.lower()) user.set_password(form.password.data) db.session.add(user) db.session.flush() # Mark invitation used invitation.mark_used(user, request.remote_addr) db.session.commit() flash('Account created! Please log in.', 'success') return redirect(url_for('auth.login')) return render_template('auth/register.html', form=form) @bp.route('/invite', methods=['GET', 'POST']) @login_required def send_invite(): form = InviteForm() # Block banned/suspended users from sending if current_user.is_banned or current_user.is_deleted or ( current_user.suspended_until and current_user.suspended_until > datetime.utcnow() ): flash('You are not permitted to send invitations.', 'warning') return redirect(url_for('home')) if form.validate_on_submit(): if current_user.invites_remaining < 1: flash('No invites remaining. Ask an admin to increase your quota.', 'danger') else: recipient = form.email.data.strip().lower() inv = Invitation( recipient_email=recipient, created_by=current_user.id, sender_ip=request.remote_addr ) db.session.add(inv) current_user.invites_remaining -= 1 db.session.commit() flash(f'Invitation sent to {recipient}.', 'success') return redirect(url_for('auth.send_invite')) return render_template( 'auth/invite.html', form=form, invites=current_user.invites_remaining ) @bp.route('/admin/adjust_invites/', methods=['GET', 'POST']) @login_required def adjust_invites(user_id): # assume current_user has admin rights user = User.query.get_or_404(user_id) form = AdjustInvitesForm() if form.validate_on_submit(): user.invites_remaining = max(0, user.invites_remaining + form.delta.data) db.session.commit() flash(f"{user.email}'s invite quota adjusted by {form.delta.data}.", 'info') return redirect(url_for('admin.user_list')) return render_template( 'auth/adjust_invites.html', form=form, user=user )