2023-08-05 21:25:18 +12:00
from datetime import date , datetime , timedelta
2023-12-30 19:03:44 +13:00
from flask import redirect , url_for , flash , request , make_response , session , Markup , current_app , g
2023-08-05 21:25:18 +12:00
from werkzeug . urls import url_parse
from flask_login import login_user , logout_user , current_user
from flask_babel import _
2024-02-02 15:30:03 +13:00
from wtforms import Label
2023-12-31 12:09:20 +13:00
from app import db , cache
2023-08-05 21:25:18 +12:00
from app . auth import bp
from app . auth . forms import LoginForm , RegistrationForm , ResetPasswordRequestForm , ResetPasswordForm
2024-08-01 16:24:36 +08:00
from app . auth . util import random_token , normalize_utf , ip2location
2024-02-02 15:30:03 +13:00
from app . email import send_verification_email , send_password_reset_email
2024-02-05 16:22:17 +13:00
from app . models import User , utcnow , IpBan , UserRegistration , Notification , Site
2024-02-02 15:30:03 +13:00
from app . utils import render_template , ip_address , user_ip_banned , user_cookie_banned , banned_ip_addresses , \
2024-03-22 14:35:51 +13:00
finalize_user_setup , blocked_referrers
2023-08-05 21:25:18 +12:00
@bp.route ( ' /login ' , methods = [ ' GET ' , ' POST ' ] )
def login ( ) :
if current_user . is_authenticated :
next_page = request . args . get ( ' next ' )
if not next_page or url_parse ( next_page ) . netloc != ' ' :
next_page = url_for ( ' main.index ' )
return redirect ( next_page )
form = LoginForm ( )
if form . validate_on_submit ( ) :
2023-11-22 20:48:27 +13:00
user = User . query . filter_by ( user_name = form . user_name . data , ap_id = None ) . first ( )
2023-08-05 21:25:18 +12:00
if user is None :
2023-12-29 17:32:35 +13:00
flash ( _ ( ' No account exists with that user name. ' ) , ' error ' )
return redirect ( url_for ( ' auth.login ' ) )
if user . deleted :
flash ( _ ( ' No account exists with that user name. ' ) , ' error ' )
2023-08-05 21:25:18 +12:00
return redirect ( url_for ( ' auth.login ' ) )
if not user . check_password ( form . password . data ) :
if user . password_hash is None :
2024-01-22 21:14:40 +13:00
message = Markup ( _ ( ' Invalid password. Please <a href= " /auth/reset_password_request " >reset your password</a>. ' ) )
flash ( message , ' error ' )
2023-08-05 21:25:18 +12:00
return redirect ( url_for ( ' auth.login ' ) )
flash ( _ ( ' Invalid password ' ) )
return redirect ( url_for ( ' auth.login ' ) )
2024-02-19 15:01:53 +13:00
if user . id != 1 and ( user . banned or user_ip_banned ( ) or user_cookie_banned ( ) ) :
2023-12-30 19:03:44 +13:00
flash ( _ ( ' You have been banned. ' ) , ' error ' )
response = make_response ( redirect ( url_for ( ' auth.login ' ) ) )
# Detect if a banned user tried to log in from a new IP address
if user . banned and not user_ip_banned ( ) :
# If so, ban their new IP address as well
new_ip_ban = IpBan ( ip_address = ip_address ( ) , notes = user . user_name + ' used new IP address ' )
db . session . add ( new_ip_ban )
db . session . commit ( )
2024-01-04 17:07:02 +13:00
cache . delete_memoized ( banned_ip_addresses )
2023-12-30 19:03:44 +13:00
# Set a cookie so we have another way to track banned people
response . set_cookie ( ' sesion ' , ' 17489047567495 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
return response
2024-02-02 15:30:03 +13:00
if user . waiting_for_approval ( ) :
return redirect ( url_for ( ' auth.please_wait ' ) )
2023-08-05 21:25:18 +12:00
login_user ( user , remember = True )
2024-05-09 13:59:52 +12:00
session [ ' ui_language ' ] = user . interface_language
2023-12-12 08:53:35 +13:00
current_user . last_seen = utcnow ( )
2023-12-30 19:03:44 +13:00
current_user . ip_address = ip_address ( )
2024-08-01 16:24:36 +08:00
if current_user . ip_address_country is None or current_user . ip_address_country == ' ' :
ip_address_info = ip2location ( current_user . ip_address )
current_user . ip_address_country = ip_address_info [ ' country ' ] if ip_address_info else current_user . ip_address_country
2023-08-05 21:25:18 +12:00
db . session . commit ( )
next_page = request . args . get ( ' next ' )
if not next_page or url_parse ( next_page ) . netloc != ' ' :
2024-01-28 18:11:32 +13:00
if len ( current_user . communities ( ) ) == 0 :
next_page = url_for ( ' topic.choose_topics ' )
else :
next_page = url_for ( ' main.index ' )
2024-01-08 19:41:32 +13:00
response = make_response ( redirect ( next_page ) )
if form . low_bandwidth_mode . data :
response . set_cookie ( ' low_bandwidth ' , ' 1 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
else :
response . set_cookie ( ' low_bandwidth ' , ' 0 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
return response
2023-08-05 21:25:18 +12:00
return render_template ( ' auth/login.html ' , title = _ ( ' Login ' ) , form = form )
@bp.route ( ' /logout ' )
def logout ( ) :
logout_user ( )
2024-01-08 19:41:32 +13:00
response = make_response ( redirect ( url_for ( ' main.index ' ) ) )
response . set_cookie ( ' low_bandwidth ' , ' 0 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
return response
2023-08-05 21:25:18 +12:00
@bp.route ( ' /register ' , methods = [ ' GET ' , ' POST ' ] )
def register ( ) :
2023-12-30 19:22:22 +13:00
disallowed_usernames = [ ' admin ' ]
2023-08-05 21:25:18 +12:00
if current_user . is_authenticated :
return redirect ( url_for ( ' main.index ' ) )
form = RegistrationForm ( )
2024-03-31 11:42:34 +13:00
# Recaptcha is optional
if not current_app . config [ ' RECAPTCHA_PUBLIC_KEY ' ] or not current_app . config [ ' RECAPTCHA_PRIVATE_KEY ' ] :
del form . recaptcha
2024-02-02 15:30:03 +13:00
if g . site . registration_mode != ' RequireApplication ' :
form . question . validators = ( )
2023-08-05 21:25:18 +12:00
if form . validate_on_submit ( ) :
if form . email . data == ' ' : # ignore any registration where the email field is filled out. spam prevention
if form . real_email . data . lower ( ) . startswith ( ' postmaster@ ' ) or form . real_email . data . lower ( ) . startswith ( ' abuse@ ' ) or \
form . real_email . data . lower ( ) . startswith ( ' noc@ ' ) :
flash ( _ ( ' Sorry, you cannot use that email address ' ) , ' error ' )
2023-12-30 19:22:22 +13:00
if form . user_name . data in disallowed_usernames :
flash ( _ ( ' Sorry, you cannot use that user name ' ) , ' error ' )
2023-08-05 21:25:18 +12:00
else :
2024-03-29 16:02:02 +13:00
# Nazis use 88 and 14 in their user names very often.
if ' 88 ' in form . user_name . data or ' 14 ' in form . user_name . data :
resp = make_response ( redirect ( url_for ( ' auth.please_wait ' ) ) )
resp . set_cookie ( ' sesion ' , ' 17489047567495 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
return resp
2024-03-22 14:35:51 +13:00
for referrer in blocked_referrers ( ) :
2024-03-27 19:36:09 +13:00
if referrer in session . get ( ' Referer ' , ' ' ) :
2024-03-22 14:35:51 +13:00
resp = make_response ( redirect ( url_for ( ' auth.please_wait ' ) ) )
resp . set_cookie ( ' sesion ' , ' 17489047567495 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
return resp
2023-08-26 15:41:11 +12:00
verification_token = random_token ( 16 )
2023-11-26 23:21:04 +13:00
form . user_name . data = form . user_name . data . strip ( )
2024-01-15 17:26:57 +13:00
before_normalize = form . user_name . data
form . user_name . data = normalize_utf ( form . user_name . data )
if before_normalize != form . user_name . data :
flash ( _ ( ' Your username contained special letters so it was changed to %(name)s . ' , name = form . user_name . data ) , ' warning ' )
2024-01-01 14:49:15 +13:00
user = User ( user_name = form . user_name . data , title = form . user_name . data , email = form . real_email . data ,
2024-01-04 16:00:19 +13:00
verification_token = verification_token , instance_id = 1 , ip_address = ip_address ( ) ,
2024-02-24 13:33:28 +13:00
banned = user_ip_banned ( ) or user_cookie_banned ( ) , email_unread_sent = False ,
2024-03-27 19:36:09 +13:00
referrer = session . get ( ' Referer ' , ' ' ) )
2023-08-05 21:25:18 +12:00
user . set_password ( form . password . data )
2024-08-01 16:24:36 +08:00
ip_address_info = ip2location ( user . ip_address )
user . ip_address_country = ip_address_info [ ' country ' ] if ip_address_info else ' '
2023-08-26 15:41:11 +12:00
db . session . add ( user )
2023-08-05 21:25:18 +12:00
db . session . commit ( )
2023-08-26 15:41:11 +12:00
send_verification_email ( user )
if current_app . config [ ' MODE ' ] == ' development ' :
current_app . logger . info ( ' Verify account: ' + url_for ( ' auth.verify_email ' , token = user . verification_token , _external = True ) )
2024-02-02 15:30:03 +13:00
if g . site . registration_mode == ' RequireApplication ' :
application = UserRegistration ( user_id = user . id , answer = form . question . data )
db . session . add ( application )
2024-02-05 16:22:17 +13:00
for admin in Site . admins ( ) :
notify = Notification ( title = ' New registration ' , url = ' /admin/approve_registrations ' , user_id = admin . id ,
author_id = user . id )
admin . unread_notifications + = 1
db . session . add ( notify )
# todo: notify everyone with the "approve registrations" permission, instead of just all admins
2024-02-02 15:30:03 +13:00
db . session . commit ( )
return redirect ( url_for ( ' auth.please_wait ' ) )
else :
return redirect ( url_for ( ' auth.check_email ' ) )
2023-08-05 21:25:18 +12:00
2024-01-28 18:11:32 +13:00
resp = make_response ( redirect ( url_for ( ' topic.choose_topics ' ) ) )
2023-12-30 19:03:44 +13:00
if user_ip_banned ( ) :
resp . set_cookie ( ' sesion ' , ' 17489047567495 ' , expires = datetime ( year = 2099 , month = 12 , day = 30 ) )
2023-08-05 21:25:18 +12:00
return resp
2024-02-02 15:30:03 +13:00
else :
if g . site . registration_mode == ' RequireApplication ' and g . site . application_question != ' ' :
form . question . label = Label ( ' question ' , g . site . application_question )
if g . site . registration_mode != ' RequireApplication ' :
del form . question
return render_template ( ' auth/register.html ' , title = _ ( ' Register ' ) , form = form , site = g . site )
@bp.route ( ' /please_wait ' , methods = [ ' GET ' ] )
def please_wait ( ) :
return render_template ( ' auth/please_wait.html ' , title = _ ( ' Account under review ' ) , site = g . site )
@bp.route ( ' /check_email ' , methods = [ ' GET ' ] )
def check_email ( ) :
return render_template ( ' auth/check_email.html ' , title = _ ( ' Check your email ' ) , site = g . site )
2023-08-05 21:25:18 +12:00
@bp.route ( ' /reset_password_request ' , methods = [ ' GET ' , ' POST ' ] )
def reset_password_request ( ) :
if current_user . is_authenticated :
return redirect ( url_for ( ' main.index ' ) )
form = ResetPasswordRequestForm ( )
if form . validate_on_submit ( ) :
if form . email . data . lower ( ) . startswith ( ' postmaster@ ' ) or form . email . data . lower ( ) . startswith ( ' abuse@ ' ) or \
form . email . data . lower ( ) . startswith ( ' noc@ ' ) :
2024-01-06 15:30:50 +13:00
flash ( _ ( ' Sorry, you cannot use that email address. ' ) , ' error ' )
2023-08-05 21:25:18 +12:00
else :
user = User . query . filter_by ( email = form . email . data ) . first ( )
if user :
send_password_reset_email ( user )
2024-01-06 15:30:50 +13:00
flash ( _ ( ' Check your email for a link to reset your password. ' ) )
2023-08-05 21:25:18 +12:00
return redirect ( url_for ( ' auth.login ' ) )
else :
flash ( _ ( ' No account with that email address exists ' ) , ' warning ' )
return render_template ( ' auth/reset_password_request.html ' ,
title = _ ( ' Reset Password ' ) , form = form )
@bp.route ( ' /reset_password/<token> ' , methods = [ ' GET ' , ' POST ' ] )
def reset_password ( token ) :
if current_user . is_authenticated :
return redirect ( url_for ( ' main.index ' ) )
user = User . verify_reset_password_token ( token )
if not user :
return redirect ( url_for ( ' main.index ' ) )
form = ResetPasswordForm ( )
if form . validate_on_submit ( ) :
user . set_password ( form . password . data )
db . session . commit ( )
2024-01-06 15:30:50 +13:00
flash ( _ ( ' Your password has been reset. Please use it to log in with user name of %(name)s . ' , name = user . user_name ) )
2023-08-05 21:25:18 +12:00
return redirect ( url_for ( ' auth.login ' ) )
2023-08-26 15:41:11 +12:00
return render_template ( ' auth/reset_password.html ' , form = form )
@bp.route ( ' /verify_email/<token> ' )
def verify_email ( token ) :
if token != ' ' :
user = User . query . filter_by ( verification_token = token ) . first ( )
if user is not None :
2023-10-23 13:03:35 +13:00
if user . banned :
flash ( ' You have been banned. ' , ' error ' )
return redirect ( url_for ( ' main.index ' ) )
2023-08-29 22:01:06 +12:00
if user . verified : # guard against users double-clicking the link in the email
2024-05-25 22:37:17 +12:00
flash ( _ ( ' Thank you for verifying your email address. ' ) )
2023-08-29 22:01:06 +12:00
return redirect ( url_for ( ' main.index ' ) )
2023-08-26 15:41:11 +12:00
user . verified = True
db . session . commit ( )
2024-05-25 22:37:17 +12:00
if not user . waiting_for_approval ( ) and user . private_key is None : # only finalize user set up if this is a brand new user. People can also end up doing this process when they change their email address in which case we DO NOT want to reset their keys, etc!
2024-02-02 15:30:03 +13:00
finalize_user_setup ( user )
else :
flash ( _ ( ' Thank you for verifying your email address. ' ) )
2023-08-26 15:41:11 +12:00
else :
flash ( _ ( ' Email address validation failed. ' ) , ' error ' )
2024-05-25 22:37:17 +12:00
return redirect ( url_for ( ' main.index ' ) )
2024-02-02 15:30:03 +13:00
if user . waiting_for_approval ( ) :
return redirect ( url_for ( ' auth.please_wait ' ) )
2024-01-28 18:11:32 +13:00
else :
2024-02-02 15:30:03 +13:00
login_user ( user , remember = True )
if len ( user . communities ( ) ) == 0 :
return redirect ( url_for ( ' topic.choose_topics ' ) )
else :
return redirect ( url_for ( ' main.index ' ) )
2023-10-23 13:03:35 +13:00
@bp.route ( ' /validation_required ' )
def validation_required ( ) :
return render_template ( ' auth/validation_required.html ' )
2023-12-17 00:12:49 +13:00
@bp.route ( ' /permission_denied ' )
def permission_denied ( ) :
return render_template ( ' auth/permission_denied.html ' )