Files
natureinpots_community/plugins/auth/routes.py

167 lines
5.3 KiB
Python

# File: 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 sqlalchemy.exc import SQLAlchemyError
from app import db
from .models import User, Invitation
from .forms import LoginForm, RegistrationForm, InviteForm
bp = Blueprint(
'auth', __name__,
url_prefix='/auth',
template_folder='templates'
)
@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):
now = datetime.utcnow()
# Block banned or suspended accounts
if user.is_banned or (user.suspended_until and user.suspended_until > now):
flash('Your account is not active.', 'danger')
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
return redirect(next_page or url_for('home'))
flash('Invalid email or password.', 'danger')
return render_template('auth/login.html', form=form)
@bp.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('home'))
@bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('home'))
# Pull invite code from query param 'invite'
invite_code = request.args.get('invite', '').strip()
# Look up a valid, unused invitation
invitation_obj = Invitation.query.filter_by(
code=invite_code,
is_active=True,
is_used=False
).first()
# Secure the GET: only allow viewing form if invite is valid
if request.method == 'GET':
if not invitation_obj:
flash('Registration is by invitation only. Provide a valid invite link.', 'warning')
return redirect(url_for('home'))
form = RegistrationForm(invitation_code=invite_code)
if form.validate_on_submit():
# Re-validate invitation on POST
invitation = Invitation.query.filter_by(
code=form.invitation_code.data,
is_active=True,
is_used=False
).first()
if not invitation:
flash('Invalid or expired invitation code.', 'warning')
return render_template('auth/register.html', form=form)
# Create the new user
user = User(
email=form.email.data.lower(),
username=form.username.data
)
user.set_password(form.password.data)
db.session.add(user)
db.session.flush()
# Mark the invitation as 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():
now = datetime.utcnow()
# Block banned or suspended users from generating invites
if current_user.is_banned or (current_user.suspended_until and current_user.suspended_until > now):
flash('Your account cannot send invitations.', 'danger')
return redirect(url_for('home'))
form = InviteForm()
invites_left = current_user.invites_remaining
if form.validate_on_submit():
if invites_left < 1:
flash('No invites remaining.', 'danger')
else:
# Create the invitation record
inv = Invitation(
recipient_email=form.email.data,
created_by=current_user.id,
sender_ip=request.remote_addr
)
db.session.add(inv)
current_user.invites_remaining = invites_left - 1
try:
db.session.commit()
except SQLAlchemyError:
db.session.rollback()
flash('An error occurred creating the invitation.', 'danger')
return redirect(url_for('auth.send_invite'))
# Build shareable link and messages
link = url_for('auth.register', invite=inv.code, _external=True)
blurb = (
"I'd like to invite you to join Nature In Pots Community! "
f"Use this link to register: {link}"
)
email_preview = (
"Subject: Join me on Nature In Pots Community\n\n"
"Hi there,\n\n"
"I'd love for you to join me on Nature In Pots Community. "
"Please click this link to sign up:\n\n"
f"{link}\n\n"
"See you inside!\n"
)
return render_template(
'auth/invite.html',
form=form,
invites=current_user.invites_remaining,
inv_link=link,
inv_blurb=blurb,
email_preview=email_preview
)
# GET: simply render the form if allowed
return render_template(
'auth/invite.html',
form=form,
invites=invites_left
)