2022-11-15 09:19:08 +00:00
import secrets
2022-05-04 23:09:46 +00:00
from urllib . parse import urlencode
2022-11-15 09:19:08 +00:00
import requests
from files . __main__ import app , cache , get_CF , limiter
from files . classes . follows import Follow
2022-06-15 19:33:21 +00:00
from files . helpers . actions import *
2022-12-11 23:44:34 +00:00
from files . helpers . config . const import *
2022-11-15 09:19:08 +00:00
from files . helpers . settings import get_setting
2022-06-24 13:19:53 +00:00
from files . helpers . get import *
2022-11-15 09:19:08 +00:00
from files . helpers . mail import send_mail , send_verification_email
2022-11-30 18:09:31 +00:00
from files . helpers . logging import log_file
2022-11-15 09:19:08 +00:00
from files . helpers . regex import *
from files . helpers . security import *
from files . helpers . useractions import badge_grant
from files . routes . routehelpers import check_for_alts
from files . routes . wrappers import *
2022-05-04 23:09:46 +00:00
2022-11-18 20:34:11 +00:00
NO_LOGIN_REDIRECT_URLS = ( " /login " , " /logout " , " /signup " , " /forgot " , " /reset " , " /reset_2fa " , " /request_2fa_disable " )
2022-05-04 23:09:46 +00:00
@app.get ( " /login " )
@auth_desired
2022-11-26 21:00:03 +00:00
def login_get ( v : Optional [ User ] ) :
2022-12-28 09:44:39 +00:00
redir = request . values . get ( " redirect " , " " ) . strip ( ) . rstrip ( ' ? ' ) . lower ( )
2022-12-28 08:35:41 +00:00
if v :
if redir and is_site_url ( redir ) and redir not in NO_LOGIN_REDIRECT_URLS :
return redirect ( redir )
return redirect ( ' / ' )
2022-11-21 08:52:22 +00:00
return render_template ( " login/login.html " , failed = False , redirect = redir ) , 401
2022-05-04 23:09:46 +00:00
2022-10-28 17:58:59 +00:00
def login_deduct_when ( resp ) :
if not g :
return False
elif not hasattr ( g , ' login_failed ' ) :
return False
return g . login_failed
2022-05-04 23:09:46 +00:00
@app.post ( " /login " )
2022-11-30 16:49:15 +00:00
@auth_desired
2022-11-15 09:19:08 +00:00
@limiter.limit ( " 6/minute;10/day " , deduct_when = login_deduct_when )
2022-11-30 16:49:15 +00:00
def login_post ( v : Optional [ User ] ) :
if v : abort ( 400 )
2022-10-28 17:58:59 +00:00
g . login_failed = True
2022-05-04 23:09:46 +00:00
username = request . values . get ( " username " )
if not username : abort ( 400 )
2022-09-04 23:15:37 +00:00
username = username . lstrip ( ' @ ' ) . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
2022-05-04 23:09:46 +00:00
if not username : abort ( 400 )
if username . startswith ( ' @ ' ) : username = username [ 1 : ]
if " @ " in username :
try : account = g . db . query ( User ) . filter ( User . email . ilike ( username ) ) . one_or_none ( )
2022-11-28 23:35:23 +00:00
except : abort ( 400 , " Multiple usernames have this email attached;<br>Please specify the username you want to login to! " )
2022-05-04 23:09:46 +00:00
else : account = get_user ( username , graceful = True )
if not account :
time . sleep ( random . uniform ( 0 , 2 ) )
2022-11-21 08:52:22 +00:00
return render_template ( " login/login.html " , failed = True ) , 401
2022-05-04 23:09:46 +00:00
if request . values . get ( " password " ) :
if not account . verifyPass ( request . values . get ( " password " ) ) :
2022-10-26 18:31:17 +00:00
log_failed_admin_login_attempt ( account , " password " )
2022-05-04 23:09:46 +00:00
time . sleep ( random . uniform ( 0 , 2 ) )
2022-11-21 08:52:22 +00:00
return render_template ( " login/login.html " , failed = True ) , 401
2022-05-04 23:09:46 +00:00
if account . mfa_secret :
now = int ( time . time ( ) )
hash = generate_hash ( f " { account . id } + { now } +2fachallenge " )
2022-10-28 17:58:59 +00:00
g . login_failed = False
2022-11-21 08:52:22 +00:00
return render_template ( " login/login_2fa.html " ,
2022-09-04 23:15:37 +00:00
v = account ,
time = now ,
hash = hash ,
redirect = request . values . get ( " redirect " , " / " )
)
2022-05-04 23:09:46 +00:00
elif request . values . get ( " 2fa_token " , " x " ) :
now = int ( time . time ( ) )
2022-07-28 14:23:38 +00:00
try :
if now - int ( request . values . get ( " time " ) ) > 600 :
return redirect ( ' /login ' )
except :
abort ( 400 )
2022-05-04 23:09:46 +00:00
formhash = request . values . get ( " hash " )
if not validate_hash ( f " { account . id } + { request . values . get ( ' time ' ) } +2fachallenge " , formhash ) :
return redirect ( " /login " )
if not account . validate_2fa ( request . values . get ( " 2fa_token " , " " ) . strip ( ) ) :
2022-05-26 19:15:24 +00:00
hash = generate_hash ( f " { account . id } + { now } +2fachallenge " )
2022-10-26 23:41:29 +00:00
log_failed_admin_login_attempt ( account , " 2FA token " )
2022-11-21 08:52:22 +00:00
return render_template ( " login/login_2fa.html " ,
2022-09-04 23:15:37 +00:00
v = account ,
time = now ,
hash = hash ,
failed = True ,
2022-11-21 08:52:22 +00:00
) , 401
2022-05-04 23:09:46 +00:00
else :
abort ( 400 )
2022-10-28 17:58:59 +00:00
g . login_failed = False
2022-07-13 23:28:40 +00:00
on_login ( account )
2022-11-18 20:39:29 +00:00
redir = request . values . get ( " redirect " , " " ) . strip ( ) . rstrip ( ' ? ' ) . lower ( )
2022-12-28 08:35:41 +00:00
if redir and is_site_url ( redir ) and redir not in NO_LOGIN_REDIRECT_URLS :
return redirect ( redir )
2022-07-13 23:28:40 +00:00
return redirect ( ' / ' )
2022-10-26 18:31:17 +00:00
def log_failed_admin_login_attempt ( account : User , type : str ) :
if not account or account . admin_level < PERMS [ ' SITE_WARN_ON_INVALID_AUTH ' ] : return
ip = get_CF ( )
2022-12-13 17:11:26 +00:00
print ( f " A site admin from { ip } failed to login to account @ { account . user_name } (invalid { type } ) " )
2022-11-30 18:09:31 +00:00
t = time . strftime ( " %d / % B/ % Y % H: % M: % S UTC " , time . gmtime ( time . time ( ) ) )
log_file ( f " { t } , { ip } , { account . username } , { type } " , " admin_failed_logins.log " )
2022-10-26 18:31:17 +00:00
2022-07-13 23:28:40 +00:00
def on_login ( account , redir = None ) :
2022-05-04 23:09:46 +00:00
session [ " lo_user " ] = account . id
session [ " login_nonce " ] = account . login_nonce
if account . id == AEVANN_ID : session [ " verified " ] = time . time ( )
2022-09-23 12:33:58 +00:00
check_for_alts ( account )
2022-05-04 23:09:46 +00:00
2022-10-28 17:58:59 +00:00
2022-05-04 23:09:46 +00:00
@app.get ( " /me " )
@app.get ( " /@me " )
@auth_required
2022-11-26 21:00:03 +00:00
def me ( v : User ) :
2022-10-15 09:11:36 +00:00
if v . client : return v . json
2022-05-04 23:09:46 +00:00
else : return redirect ( v . url )
@app.post ( " /logout " )
2022-11-13 06:43:35 +00:00
@limiter.limit ( DEFAULT_RATELIMIT_SLOWER )
2022-05-04 23:09:46 +00:00
@auth_required
2022-12-30 18:43:13 +00:00
@limiter.limit ( DEFAULT_RATELIMIT_SLOWER , key_func = lambda : f ' { SITE } - { g . v . id } ' )
2022-05-04 23:09:46 +00:00
def logout ( v ) :
2022-05-25 20:16:26 +00:00
loggedin = cache . get ( f ' { SITE } _loggedin ' ) or { }
if session . get ( " lo_user " ) in loggedin : del loggedin [ session [ " lo_user " ] ]
cache . set ( f ' { SITE } _loggedin ' , loggedin )
2022-05-04 23:09:46 +00:00
session . pop ( " lo_user " , None )
return { " message " : " Logout successful! " }
@app.get ( " /signup " )
@auth_desired
2022-11-26 21:00:03 +00:00
def sign_up_get ( v : Optional [ User ] ) :
2022-11-30 18:26:07 +00:00
if not get_setting ( ' signups ' ) :
2022-11-25 18:27:18 +00:00
abort ( 403 , " New account registration is currently closed. Please come back later. " )
2022-05-04 23:09:46 +00:00
2022-10-27 17:53:08 +00:00
if v : return redirect ( SITE_FULL )
2022-05-04 23:09:46 +00:00
ref = request . values . get ( " ref " )
if ref :
2022-09-04 23:15:37 +00:00
ref = ref . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
2022-05-04 23:09:46 +00:00
ref_user = g . db . query ( User ) . filter ( User . username . ilike ( ref ) ) . one_or_none ( )
else :
ref_user = None
if ref_user and ( ref_user . id in session . get ( " history " , [ ] ) ) :
2022-11-21 08:52:22 +00:00
return render_template ( " login/sign_up_failed_ref.html " ) , 403
2022-05-04 23:09:46 +00:00
now = int ( time . time ( ) )
2022-11-15 09:19:08 +00:00
token = secrets . token_hex ( 16 )
2022-05-04 23:09:46 +00:00
session [ " signup_token " ] = token
2022-05-26 20:53:24 +00:00
formkey_hashstr = str ( now ) + token + g . agent
2022-05-04 23:09:46 +00:00
2022-10-10 09:06:27 +00:00
formkey = hmac . new ( key = bytes ( SECRET_KEY , " utf-16 " ) ,
2022-09-04 23:15:37 +00:00
msg = bytes ( formkey_hashstr , " utf-16 " ) ,
digestmod = ' md5 '
) . hexdigest ( )
2022-05-04 23:09:46 +00:00
error = request . values . get ( " error " )
2022-11-09 05:35:24 +00:00
redir = request . values . get ( " redirect " , " / " ) . strip ( ) . rstrip ( ' ? ' )
2022-08-30 19:03:49 +00:00
if redir :
if not is_site_url ( redir ) : redir = " / "
2022-11-21 08:52:22 +00:00
status_code = 200 if not error else 400
return render_template ( " login/sign_up.html " ,
2022-09-04 23:15:37 +00:00
formkey = formkey ,
now = now ,
ref_user = ref_user ,
2022-11-11 18:34:06 +00:00
turnstile = TURNSTILE_SITEKEY ,
2022-09-04 23:15:37 +00:00
error = error ,
redirect = redir
2022-11-21 08:52:22 +00:00
) , status_code
2022-05-04 23:09:46 +00:00
@app.post ( " /signup " )
2022-05-25 23:25:51 +00:00
@limiter.limit ( " 1/second;10/day " )
2022-05-04 23:09:46 +00:00
@auth_desired
2022-11-26 21:00:03 +00:00
def sign_up_post ( v : Optional [ User ] ) :
2022-11-30 18:26:07 +00:00
if not get_setting ( ' signups ' ) :
2022-11-25 18:27:18 +00:00
abort ( 403 , " New account registration is currently closed. Please come back later. " )
2022-05-04 23:09:46 +00:00
if v : abort ( 403 )
form_timestamp = request . values . get ( " now " , ' 0 ' )
form_formkey = request . values . get ( " formkey " , " none " )
2022-12-30 17:29:04 +00:00
username = request . values . get ( " username " )
if not username : abort ( 400 )
username = username . strip ( )
email = request . values . get ( " email " ) . strip ( ) . lower ( )
ref_id = 0
try :
ref_id = int ( request . values . get ( " referred_by " , 0 ) )
except :
pass
redir = request . values . get ( " redirect " , " " ) . strip ( ) . rstrip ( ' ? ' ) . lower ( )
band-aid fix for frozen session issue on signup (#50)
through some reason or another, people are somehow getting cookies that aren't prepended with a dot.
this is a problem because both sessions at, as best as I can tell, mix so it tries to read from a different cookie than we write to. this essentially "freezes" the session in place. users are unable to login, logout, signup, toggle poor mode, toggle NSFW, etc.
~~this attempts to delete bad session cookies (i.e. cookies with a domain that don't start with a dot).~~
~~we don't do this on "dotless" domains (and by extension localhost) because browser support for setting cookies on FQDNs that only have one dot has tenuous support among browsers anyway).~~
~~this *may* log some people out, but... their days of being able to do stuff on the site were numbered anyway.~~
**edit: as amazing as this thought was, browsers just wipe the entire cookies completely and there's no way to specifically target dotless cookies. for an issue that affects a few users, better to just tell them to clear their cookies. if *this* doesn't work, delete service-worker.js and be done with the whole service worker crap. forever. permanently. this PR also includes some QOL improvements.**
Co-authored-by: justcool393 <justcool393@gmail.com>
Reviewed-on: https://fsdfsd.net/rDrama/rDrama/pulls/50
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
2022-12-06 22:07:12 +00:00
def signup_error ( error , clear = False ) :
2022-12-30 17:29:04 +00:00
if ref_id :
ref = ref . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
ref_user = g . db . query ( User ) . filter ( User . username . ilike ( ref ) ) . one_or_none ( )
else :
ref_user = None
now = int ( time . time ( ) )
token = secrets . token_hex ( 16 )
session [ " signup_token " ] = token
formkey_hashstr = str ( now ) + token + g . agent
formkey = hmac . new ( key = bytes ( SECRET_KEY , " utf-16 " ) ,
msg = bytes ( formkey_hashstr , " utf-16 " ) ,
digestmod = ' md5 '
) . hexdigest ( )
band-aid fix for frozen session issue on signup (#50)
through some reason or another, people are somehow getting cookies that aren't prepended with a dot.
this is a problem because both sessions at, as best as I can tell, mix so it tries to read from a different cookie than we write to. this essentially "freezes" the session in place. users are unable to login, logout, signup, toggle poor mode, toggle NSFW, etc.
~~this attempts to delete bad session cookies (i.e. cookies with a domain that don't start with a dot).~~
~~we don't do this on "dotless" domains (and by extension localhost) because browser support for setting cookies on FQDNs that only have one dot has tenuous support among browsers anyway).~~
~~this *may* log some people out, but... their days of being able to do stuff on the site were numbered anyway.~~
**edit: as amazing as this thought was, browsers just wipe the entire cookies completely and there's no way to specifically target dotless cookies. for an issue that affects a few users, better to just tell them to clear their cookies. if *this* doesn't work, delete service-worker.js and be done with the whole service worker crap. forever. permanently. this PR also includes some QOL improvements.**
Co-authored-by: justcool393 <justcool393@gmail.com>
Reviewed-on: https://fsdfsd.net/rDrama/rDrama/pulls/50
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
2022-12-06 22:07:12 +00:00
if clear :
session . clear ( )
resp . delete_cookie ( app . config [ " SESSION_COOKIE_NAME " ] , httponly = True , secure = True , samesite = " Lax " )
2022-12-30 17:29:04 +00:00
return render_template ( " login/sign_up.html " ,
formkey = formkey ,
now = now ,
ref_user = ref_user ,
turnstile = TURNSTILE_SITEKEY ,
error = error ,
redirect = redir ,
username = username ,
email = email ,
) , 400
band-aid fix for frozen session issue on signup (#50)
through some reason or another, people are somehow getting cookies that aren't prepended with a dot.
this is a problem because both sessions at, as best as I can tell, mix so it tries to read from a different cookie than we write to. this essentially "freezes" the session in place. users are unable to login, logout, signup, toggle poor mode, toggle NSFW, etc.
~~this attempts to delete bad session cookies (i.e. cookies with a domain that don't start with a dot).~~
~~we don't do this on "dotless" domains (and by extension localhost) because browser support for setting cookies on FQDNs that only have one dot has tenuous support among browsers anyway).~~
~~this *may* log some people out, but... their days of being able to do stuff on the site were numbered anyway.~~
**edit: as amazing as this thought was, browsers just wipe the entire cookies completely and there's no way to specifically target dotless cookies. for an issue that affects a few users, better to just tell them to clear their cookies. if *this* doesn't work, delete service-worker.js and be done with the whole service worker crap. forever. permanently. this PR also includes some QOL improvements.**
Co-authored-by: justcool393 <justcool393@gmail.com>
Reviewed-on: https://fsdfsd.net/rDrama/rDrama/pulls/50
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
2022-12-06 22:07:12 +00:00
2022-05-04 23:09:46 +00:00
submitted_token = session . get ( " signup_token " , " " )
band-aid fix for frozen session issue on signup (#50)
through some reason or another, people are somehow getting cookies that aren't prepended with a dot.
this is a problem because both sessions at, as best as I can tell, mix so it tries to read from a different cookie than we write to. this essentially "freezes" the session in place. users are unable to login, logout, signup, toggle poor mode, toggle NSFW, etc.
~~this attempts to delete bad session cookies (i.e. cookies with a domain that don't start with a dot).~~
~~we don't do this on "dotless" domains (and by extension localhost) because browser support for setting cookies on FQDNs that only have one dot has tenuous support among browsers anyway).~~
~~this *may* log some people out, but... their days of being able to do stuff on the site were numbered anyway.~~
**edit: as amazing as this thought was, browsers just wipe the entire cookies completely and there's no way to specifically target dotless cookies. for an issue that affects a few users, better to just tell them to clear their cookies. if *this* doesn't work, delete service-worker.js and be done with the whole service worker crap. forever. permanently. this PR also includes some QOL improvements.**
Co-authored-by: justcool393 <justcool393@gmail.com>
Reviewed-on: https://fsdfsd.net/rDrama/rDrama/pulls/50
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
2022-12-06 22:07:12 +00:00
if not submitted_token :
return signup_error ( f " An error occurred while attempting to signup. If you get this repeatedly, please make sure cookies are enabled. " , clear = True )
2022-05-04 23:09:46 +00:00
2022-05-26 20:53:24 +00:00
correct_formkey_hashstr = form_timestamp + submitted_token + g . agent
2022-10-10 09:06:27 +00:00
correct_formkey = hmac . new ( key = bytes ( SECRET_KEY , " utf-16 " ) ,
2022-05-04 23:09:46 +00:00
msg = bytes ( correct_formkey_hashstr , " utf-16 " ) ,
digestmod = ' md5 '
2022-09-04 23:15:37 +00:00
) . hexdigest ( )
2022-05-04 23:09:46 +00:00
now = int ( time . time ( ) )
if now - int ( form_timestamp ) < 5 :
return signup_error ( " There was a problem. Please try again. " )
if not hmac . compare_digest ( correct_formkey , form_formkey ) :
2022-12-30 17:29:04 +00:00
if IS_LOCALHOST : return signup_error ( " There was a problem. Please try again! " )
2022-11-15 18:59:38 +00:00
return signup_error ( " There was a problem. Please try again. " )
2022-05-04 23:09:46 +00:00
if not request . values . get (
" password " ) == request . values . get ( " password_confirm " ) :
return signup_error ( " Passwords did not match. Please try again. " )
if not valid_username_regex . fullmatch ( username ) :
return signup_error ( " Invalid username " )
if not valid_password_regex . fullmatch ( request . values . get ( " password " ) ) :
return signup_error ( " Password must be between 8 and 100 characters. " )
if email :
if not email_regex . fullmatch ( email ) :
return signup_error ( " Invalid email. " )
else : email = None
existing_account = get_user ( username , graceful = True )
if existing_account :
return signup_error ( " An account with that username already exists. " )
2022-11-15 16:55:00 +00:00
if TURNSTILE_SITEKEY != DEFAULT_CONFIG_VALUE :
2022-11-11 18:34:06 +00:00
token = request . values . get ( " cf-turnstile-response " )
2022-05-04 23:09:46 +00:00
if not token :
return signup_error ( " Unable to verify captcha [1]. " )
2022-11-11 18:34:06 +00:00
data = { " secret " : TURNSTILE_SECRET ,
2022-05-04 23:09:46 +00:00
" response " : token ,
2022-11-11 18:34:06 +00:00
" sitekey " : TURNSTILE_SITEKEY }
url = " https://challenges.cloudflare.com/turnstile/v0/siteverify "
2022-05-04 23:09:46 +00:00
2022-09-26 04:01:25 +00:00
x = requests . post ( url , data = data , timeout = 5 )
2022-05-04 23:09:46 +00:00
if not x . json ( ) [ " success " ] :
return signup_error ( " Unable to verify captcha [2]. " )
session . pop ( " signup_token " )
2022-05-09 11:21:49 +00:00
users_count = g . db . query ( User ) . count ( )
2022-05-04 23:09:46 +00:00
2022-07-09 10:09:33 +00:00
profileurl = None
if PFP_DEFAULT_MARSEY :
profileurl = ' /e/ ' + random . choice ( marseys_const ) + ' .webp '
2022-05-04 23:09:46 +00:00
new_user = User (
username = username ,
original_username = username ,
password = request . values . get ( " password " ) ,
email = email ,
referred_by = ref_id or None ,
profileurl = profileurl
)
2022-12-27 02:50:42 +00:00
if users_count == 4 :
new_user . admin_level = 3
session [ " history " ] = [ ]
2022-05-04 23:09:46 +00:00
g . db . add ( new_user )
2022-07-06 10:56:39 +00:00
g . db . commit ( )
2022-05-04 23:09:46 +00:00
if ref_id :
2022-06-24 13:19:53 +00:00
ref_user = get_account ( ref_id )
2022-05-04 23:09:46 +00:00
if ref_user :
2022-06-15 19:33:21 +00:00
badge_grant ( user = ref_user , badge_id = 10 )
2022-06-15 20:29:25 +00:00
# off-by-one: newly referred user isn't counted
if ref_user . referral_count > = 9 :
2022-06-15 19:33:21 +00:00
badge_grant ( user = ref_user , badge_id = 11 )
2022-06-15 20:29:25 +00:00
if ref_user . referral_count > = 99 :
2022-06-15 19:33:21 +00:00
badge_grant ( user = ref_user , badge_id = 12 )
2022-05-04 23:09:46 +00:00
2022-06-13 16:28:37 +00:00
if email :
2022-09-11 01:56:47 +00:00
send_verification_email ( new_user )
2022-05-04 23:09:46 +00:00
2022-05-25 22:02:54 +00:00
2022-11-14 17:32:13 +00:00
session [ " lo_user " ] = new_user . id
2022-09-23 12:33:58 +00:00
check_for_alts ( new_user )
2022-05-04 23:09:46 +00:00
send_notification ( new_user . id , WELCOME_MSG )
2022-05-14 13:11:11 +00:00
2022-10-10 06:37:42 +00:00
if SIGNUP_FOLLOW_ID :
signup_autofollow = get_account ( SIGNUP_FOLLOW_ID )
new_follow = Follow ( user_id = new_user . id , target_id = signup_autofollow . id )
2022-10-05 22:22:57 +00:00
g . db . add ( new_follow )
2022-10-10 06:37:42 +00:00
signup_autofollow . stored_subscriber_count + = 1
g . db . add ( signup_autofollow )
send_notification ( signup_autofollow . id , f " A new user - @ { new_user . username } - has followed you automatically! " )
elif CARP_ID :
send_notification ( CARP_ID , f " A new user - @ { new_user . username } - has signed up! " )
2022-12-19 20:32:21 +00:00
2022-12-28 08:35:41 +00:00
if redir and is_site_url ( redir ) and redir not in NO_LOGIN_REDIRECT_URLS :
return redirect ( redir )
2022-08-30 19:03:49 +00:00
return redirect ( ' / ' )
2022-05-04 23:09:46 +00:00
@app.get ( " /forgot " )
def get_forgot ( ) :
2022-11-21 08:52:22 +00:00
return render_template ( " login/forgot_password.html " )
2022-05-04 23:09:46 +00:00
@app.post ( " /forgot " )
2022-11-13 06:43:35 +00:00
@limiter.limit ( DEFAULT_RATELIMIT_SLOWER )
2022-05-04 23:09:46 +00:00
def post_forgot ( ) :
username = request . values . get ( " username " )
if not username : abort ( 400 )
email = request . values . get ( " email " , ' ' ) . strip ( ) . lower ( )
if not email_regex . fullmatch ( email ) :
2022-11-21 08:52:22 +00:00
return render_template ( " login/forgot_password.html " , error = " Invalid email. " ) , 400
2022-05-04 23:09:46 +00:00
2022-09-04 23:15:37 +00:00
username = username . lstrip ( ' @ ' ) . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
email = email . replace ( ' \\ ' , ' ' ) . replace ( ' _ ' , ' \ _ ' ) . replace ( ' % ' , ' ' ) . strip ( )
2022-05-04 23:09:46 +00:00
user = g . db . query ( User ) . filter (
User . username . ilike ( username ) ,
User . email . ilike ( email ) ) . one_or_none ( )
if user :
now = int ( time . time ( ) )
token = generate_hash ( f " { user . id } + { now } +forgot+ { user . login_nonce } " )
url = f " { SITE_FULL } /reset?id= { user . id } &time= { now } &token= { token } "
send_mail ( to_address = user . email ,
2022-09-04 23:15:37 +00:00
subject = " Password Reset Request " ,
html = render_template ( " email/password_reset.html " ,
action_url = url ,
v = user )
)
2022-05-04 23:09:46 +00:00
2022-11-21 08:52:22 +00:00
return render_template ( " login/forgot_password.html " ,
msg = " If the username and email matches an account, you will be sent a password reset email. You have ten minutes to complete the password reset process. " ) , 202
2022-05-04 23:09:46 +00:00
@app.get ( " /reset " )
def get_reset ( ) :
user_id = request . values . get ( " id " )
2022-10-16 09:51:42 +00:00
timestamp = 0
try :
timestamp = int ( request . values . get ( " time " , 0 ) )
except :
pass
2022-05-04 23:09:46 +00:00
token = request . values . get ( " token " )
now = int ( time . time ( ) )
if now - timestamp > 600 :
2022-11-16 05:32:57 +00:00
abort ( 410 , " This password reset link has expired. " )
2022-05-04 23:09:46 +00:00
2022-06-24 13:19:53 +00:00
user = get_account ( user_id )
2022-05-04 23:09:46 +00:00
if not validate_hash ( f " { user_id } + { timestamp } +forgot+ { user . login_nonce } " , token ) :
abort ( 400 )
reset_token = generate_hash ( f " { user . id } + { timestamp } +reset+ { user . login_nonce } " )
2022-11-21 08:52:22 +00:00
return render_template ( " login/reset_password.html " ,
2022-09-04 23:15:37 +00:00
v = user ,
token = reset_token ,
time = timestamp ,
)
2022-05-04 23:09:46 +00:00
@app.post ( " /reset " )
2022-11-13 06:43:35 +00:00
@limiter.limit ( DEFAULT_RATELIMIT_SLOWER )
2022-05-04 23:09:46 +00:00
@auth_desired
2022-11-26 21:00:03 +00:00
def post_reset ( v : Optional [ User ] ) :
2022-05-04 23:09:46 +00:00
if v : return redirect ( ' / ' )
user_id = request . values . get ( " user_id " )
2022-10-16 09:51:42 +00:00
timestamp = 0
try :
timestamp = int ( request . values . get ( " time " ) )
except :
abort ( 400 )
2022-05-04 23:09:46 +00:00
token = request . values . get ( " token " )
password = request . values . get ( " password " )
confirm_password = request . values . get ( " confirm_password " )
now = int ( time . time ( ) )
if now - timestamp > 600 :
2022-11-16 05:32:57 +00:00
abort ( 410 , " This password reset link has expired. " )
2022-05-04 23:09:46 +00:00
2022-06-24 13:19:53 +00:00
user = get_account ( user_id )
2022-05-04 23:09:46 +00:00
if not validate_hash ( f " { user_id } + { timestamp } +reset+ { user . login_nonce } " , token ) :
abort ( 400 )
if password != confirm_password :
2022-11-21 08:52:22 +00:00
return render_template ( " login/reset_password.html " ,
2022-09-04 23:15:37 +00:00
v = user ,
token = token ,
time = timestamp ,
2022-11-21 08:52:22 +00:00
error = " Passwords didn ' t match. " ) , 400
2022-05-04 23:09:46 +00:00
user . passhash = hash_password ( password )
g . db . add ( user )
return render_template ( " message_success.html " ,
2022-09-04 23:15:37 +00:00
title = " Password reset successful! " ,
message = " Login normally to access your account. " )
2022-05-04 23:09:46 +00:00
@app.get ( " /lost_2fa " )
@auth_desired
2022-11-26 21:00:03 +00:00
def lost_2fa ( v : Optional [ User ] ) :
2022-11-15 16:56:23 +00:00
if v and not v . mfa_secret : abort ( 400 , " You don ' t have 2FA enabled " )
2022-11-21 08:52:22 +00:00
return render_template ( " login/lost_2fa.html " , v = v )
2022-05-04 23:09:46 +00:00
@app.post ( " /request_2fa_disable " )
@limiter.limit ( " 1/second;6/minute;200/hour;1000/day " )
def request_2fa_disable ( ) :
username = request . values . get ( " username " )
user = get_user ( username , graceful = True )
if not user or not user . email or not user . mfa_secret :
return render_template ( " message.html " ,
2022-09-04 23:15:37 +00:00
title = " Removal request received " ,
2022-11-21 08:52:22 +00:00
message = " If username, password, and email match, we will send you an email. " ) , 202
2022-05-04 23:09:46 +00:00
email = request . values . get ( " email " ) . strip ( ) . lower ( )
if not email_regex . fullmatch ( email ) :
2022-11-16 05:32:57 +00:00
abort ( 400 , " Invalid email " )
2022-05-04 23:09:46 +00:00
password = request . values . get ( " password " )
if not user . verifyPass ( password ) :
return render_template ( " message.html " ,
2022-09-04 23:15:37 +00:00
title = " Removal request received " ,
2022-11-21 08:52:22 +00:00
message = " If username, password, and email match, we will send you an email. " ) , 202
2022-05-04 23:09:46 +00:00
valid = int ( time . time ( ) )
token = generate_hash ( f " { user . id } + { user . username } +disable2fa+ { valid } + { user . mfa_secret } + { user . login_nonce } " )
action_url = f " { SITE_FULL } /reset_2fa?id= { user . id } &t= { valid } &token= { token } "
send_mail ( to_address = user . email ,
2022-09-04 23:15:37 +00:00
subject = " 2FA Removal Request " ,
html = render_template ( " email/2fa_remove.html " ,
action_url = action_url ,
v = user )
)
2022-05-04 23:09:46 +00:00
return render_template ( " message.html " ,
2022-09-04 23:15:37 +00:00
title = " Removal request received " ,
2022-11-21 08:52:22 +00:00
message = " If username, password, and email match, we will send you an email. " ) , 202
2022-05-04 23:09:46 +00:00
@app.get ( " /reset_2fa " )
def reset_2fa ( ) :
now = int ( time . time ( ) )
t = request . values . get ( " t " )
if not t : abort ( 400 )
2022-10-16 09:51:42 +00:00
try :
t = int ( t )
except :
abort ( 400 )
2022-05-04 23:09:46 +00:00
if now > t + 3600 * 24 :
2022-11-16 05:32:57 +00:00
abort ( 410 , " This 2FA reset link has expired. " )
2022-05-04 23:09:46 +00:00
token = request . values . get ( " token " )
uid = request . values . get ( " id " )
user = get_account ( uid )
if not validate_hash ( f " { user . id } + { user . username } +disable2fa+ { t } + { user . mfa_secret } + { user . login_nonce } " , token ) :
abort ( 403 )
user . mfa_secret = None
g . db . add ( user )
return render_template ( " message_success.html " ,
2022-09-04 23:15:37 +00:00
title = " Two-factor authentication removed. " ,
message = " Login normally to access your account. " )