Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions app/controllers/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from urllib.parse import urlparse

from flask import Blueprint, render_template, request, redirect, flash, session
from app.models.user import user_by_name
from app.models.user import user_by_name, user_by_mail
from app.models.errors import ErrNoResult
from app.services.passhash import match_string
from app.services.limiter import limit
Expand Down Expand Up @@ -66,17 +66,29 @@ def login_post():
# Track login attempts
attempts = session.get('login_attempt', 0)

# Try email lookup first, then username (email takes precedence)
user = None
try:
user = user_by_name(name)
try:
user = user_by_mail(name)
except ErrNoResult:
user = user_by_name(name)
except ErrNoResult:
pass # Neither found, user stays None
except Exception as e:
print(f"Login error: {e}")
flash('There was an error. Please try again later.', FLASH_ERROR)
return render_template('login/login.html')

if user:
# Check password
if match_string(user['password'], password):
# Login successful
clear_main_auth()
session['email'] = user['email']
session['name'] = user['name']
flash('Login successful!', FLASH_SUCCESS)

# Retrieve and clear the redirect from session
# Only allow relative URLs to prevent redirecting to external sites
redirect_url = session.pop('login_redirect', '/')
Expand All @@ -87,16 +99,11 @@ def login_post():
attempts += 1
session['login_attempt'] = attempts
flash(f'Password is incorrect - Attempt: {attempts}', FLASH_WARNING)

except ErrNoResult:
else:
attempts += 1
session['login_attempt'] = attempts
flash(f'Password is incorrect - Attempt: {attempts}', FLASH_WARNING)

except Exception as e:
print(f"Login error: {e}")
flash('There was an error. Please try again later.', FLASH_ERROR)

return render_template('login/login.html')


Expand Down
44 changes: 25 additions & 19 deletions app/controllers/password_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,10 @@ def reset_password_get(token):
"""Display the reset password form."""
try:
# Validate token exists and is not expired
get_reset_token(token)
return render_template('password_reset/reset.html', token=token)
token_doc = get_reset_token(token)
# Look up user to get username
user = user_by_mail(token_doc['email'])
return render_template('password_reset/reset.html', token=token, username=user['name'])
except ErrNoResult:
flash('This password reset link is invalid or has expired.', FLASH_ERROR)
return redirect('/forgot-password')
Expand All @@ -131,46 +133,50 @@ def reset_password_post(token):
new_password = request.form.get('new_password', '')
new_password_verify = request.form.get('new_password_verify', '')

# Validate token and get user first (so we can show username in error messages)
try:
token_doc = get_reset_token(token)
email = token_doc['email']
user = user_by_mail(email)
username = user['name']
except ErrNoResult:
flash('This password reset link is invalid or has expired.', FLASH_ERROR)
return redirect('/forgot-password')
except Exception as e:
print(f"Error validating reset token: {e}")
flash('An error occurred. Please try again.', FLASH_ERROR)
return redirect('/forgot-password')

# Validate passwords
if not new_password or not new_password_verify:
flash('Please fill in all password fields', FLASH_ERROR)
return render_template('password_reset/reset.html', token=token)
return render_template('password_reset/reset.html', token=token, username=username)

if new_password != new_password_verify:
flash('Passwords do not match', FLASH_ERROR)
return render_template('password_reset/reset.html', token=token)
return render_template('password_reset/reset.html', token=token, username=username)

if len(new_password) < 8:
flash('Password must be at least 8 characters long', FLASH_ERROR)
return render_template('password_reset/reset.html', token=token)
return render_template('password_reset/reset.html', token=token, username=username)

try:
# Validate token and get associated email
token_doc = get_reset_token(token)
email = token_doc['email']

# Find user by email
user = user_by_mail(email)

# Hash new password
hashed_password = hash_string(new_password)

# Update user's password
update_user_password(user['name'], hashed_password)
update_user_password(username, hashed_password)

# Delete the used token
delete_reset_token(token)

# Notify moderation channel
notify_password_reset_complete(user['name'], email)
notify_password_reset_complete(username, email)

flash('Your password has been reset successfully. You can now log in.', FLASH_SUCCESS)
flash(f'Password reset successfully for {username}. You can now log in.', FLASH_SUCCESS)
return redirect('/login')

except ErrNoResult:
flash('This password reset link is invalid or has expired.', FLASH_ERROR)
return redirect('/forgot-password')
except Exception as e:
print(f"Error resetting password: {e}")
flash('An error occurred while resetting your password. Please try again.', FLASH_ERROR)
return render_template('password_reset/reset.html', token=token)
return render_template('password_reset/reset.html', token=token, username=username)
47 changes: 37 additions & 10 deletions app/controllers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def register_post():
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
return redirect('/register')

# Check if email already exists
# Check if email already exists as an email
try:
user_by_mail(email)
flash(f'Account already exists for: {email}', FLASH_ERROR)
Expand All @@ -89,22 +89,49 @@ def register_post():
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)

# Check if username already exists
# Check if username already exists as a username
try:
user_by_name(name)
flash(f'Account already exists for: {name}', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)
except ErrNoResult:
# Username not taken, create account
try:
user_create(name, email, hashed_password)
flash(f'Account created successfully for: {name}', FLASH_SUCCESS)
return redirect('/login')
except Exception as e:
print(f"User creation error: {e}")
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
pass # Username not taken, continue
except Exception as e:
print(f"Database error: {e}")
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)

# Cross-check: username must not match an existing user's email
try:
user_by_mail(name)
flash('This username is not available', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)
except ErrNoResult:
pass # No conflict, continue
except Exception as e:
print(f"Database error: {e}")
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)

# Cross-check: email must not match an existing user's username
try:
user_by_name(email)
flash('This email is not available', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)
except ErrNoResult:
pass # No conflict, continue
except Exception as e:
print(f"Database error: {e}")
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)
return render_template('register/register.html', name=name, email=email)

# All checks passed, create account
try:
user_create(name, email, hashed_password)
flash(f'Account created successfully for: {name}', FLASH_SUCCESS)
return redirect('/login')
except Exception as e:
print(f"User creation error: {e}")
flash('An error occurred on the server. Please try again later.', FLASH_ERROR)

return render_template('register/register.html', name=name, email=email)
4 changes: 2 additions & 2 deletions templates/login/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label" for="name">Username</label>
<label class="form-label" for="name">Username or Email</label>
</div>
<div class="col-8 col-sm-12">
<input class="form-input" type="text" id="name" name="name" placeholder="Name">
<input class="form-input" type="text" id="name" name="name" placeholder="Username or Email">
</div>
</div>
<div class="form-group">
Expand Down
1 change: 1 addition & 0 deletions templates/password_reset/reset.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<div class="container grid-lg wrapper">
<div class="columns" style="justify-content: center;">
<div class="column col-6 col-xs-12 panel-input">
<p>Resetting password for: <strong>{{ username }}</strong></p>
<p>Enter your new password below.</p>
<form id="resetPasswordForm" class="form-horizontal" method="post" onsubmit="return validatePassword();">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
Expand Down