def tfa_init_post_(request): userid, status = login.authenticate_bcrypt(define.get_display_name( request.userid), request.params['password'], request=None) # The user's password failed to authenticate if status == "invalid": return Response( define.webpage( request.userid, "control/2fa/init.html", [define.get_display_name(request.userid), "password"], title="Enable 2FA: Step 1")) # Unlikely that this block will get triggered, but just to be safe, check for it elif status == "unicode-failure": raise HTTPSeeOther(location='/signin/unicode-failure') # The user has authenticated, so continue with the initialization process. else: tfa_secret, tfa_qrcode = tfa.init(request.userid) _set_totp_code_on_session(tfa_secret) return Response( define.webpage(request.userid, "control/2fa/init_qrcode.html", [ define.get_display_name(request.userid), tfa_secret, tfa_qrcode, None ], title="Enable 2FA: Step 2"))
def tfa_generate_recovery_codes_verify_password_post_(request): userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid), request.params['password'], request=None) # The user's password failed to authenticate if status == "invalid": return Response(define.webpage( request.userid, "control/2fa/generate_recovery_codes_verify_password.html", ["password"], title="Generate Recovery Codes: Verify Password" )) # The user has authenticated, so continue with generating the new recovery codes. else: # Edge case prevention: Stop the user from having two Weasyl sessions open and trying # to proceed through the generation process with two sets of recovery codes. invalidate_other_sessions(request.userid) # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent # a user from confusing themselves if they visit the request page twice. sess = request.weasyl_session gen_rec_codes = True if '2fa_recovery_codes_timestamp' in sess.additional_data: # Are the codes on the current session < 30 minutes old? tstamp = sess.additional_data['2fa_recovery_codes_timestamp'] if arrow.now().timestamp - tstamp < 1800: # We have recent codes on the session, use them instead of generating fresh codes. recovery_codes = sess.additional_data['2fa_recovery_codes'].split(',') gen_rec_codes = False if gen_rec_codes: # Either this is a fresh request to generate codes, or the timelimit was exceeded. recovery_codes = tfa.generate_recovery_codes() _set_recovery_codes_on_session(','.join(recovery_codes)) return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [ recovery_codes, None ], title="Generate Recovery Codes: Save New Recovery Codes"))
def signin_2fa_auth_get_(request): sess = define.get_weasyl_session() # Only render page if the session exists //and// the password has # been authenticated (we have a UserID stored in the session) if not sess.additional_data or '2fa_pwd_auth_userid' not in sess.additional_data: raise WeasylError('InsufficientPermissions') tfa_userid = sess.additional_data['2fa_pwd_auth_userid'] # Maximum secondary authentication time: 5 minutes session_life = arrow.now( ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp'] if session_life > 300: _cleanup_2fa_session() raise WeasylError('TwoFactorAuthenticationAuthenticationTimeout') else: ref = request.params["referer"] if "referer" in request.params else "/" return Response( define.webpage( request.userid, "etc/signin_2fa_auth.html", [ define.get_display_name(tfa_userid), ref, two_factor_auth.get_number_of_recovery_codes(tfa_userid), None ], title="Sign In - 2FA"))
def frienduser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: return Response( define.errorpage(request.userid, "You cannot friend yourself.")) if form.action == "sendfriendrequest": if not frienduser.check(request.userid, otherid) and not frienduser.already_pending( request.userid, otherid): frienduser.request(request.userid, otherid) elif form.action == "withdrawfriendrequest": if frienduser.already_pending(request.userid, otherid): frienduser.remove_request(request.userid, otherid) elif form.action == "unfriend": frienduser.remove(request.userid, otherid) if form.feature == "pending": raise HTTPSeeOther(location="/manage/friends?feature=pending") else: # typical value will be user raise HTTPSeeOther( location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def help_verification_(request): username = define.get_display_name(request.userid) return Response( define.webpage(request.userid, "help/verification.html", [username], title="Account Verification"))
def control_username_get_(request): latest_change = define.engine.execute( "SELECT username, active, extract(epoch from now() - replaced_at)::int8 AS seconds" " FROM username_history" " WHERE userid = %(user)s" " AND NOT cosmetic" " ORDER BY historyid DESC LIMIT 1", user=request.userid, ).first() if latest_change is None: existing_redirect = None days = None else: existing_redirect = latest_change.username if latest_change.active else None days = latest_change.seconds // (3600 * 24) return Response( define.webpage( request.userid, "control/username.html", (define.get_display_name(request.userid), existing_redirect, days if days is not None and days < 30 else None), title="Change Username", ))
def tfa_init_qrcode_post_(request): # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456') tfaresponse = request.params['tfaresponse'].replace(' ', '') tfa_secret_sess = _get_totp_code_from_session() # Check to see if the tfaresponse matches the tfasecret when run through the TOTP algorithm tfa_secret, recovery_codes = tfa.init_verify_tfa(tfa_secret_sess, tfaresponse) # The 2FA TOTP code did not match with the generated 2FA secret if not tfa_secret: return Response( define.webpage( request.userid, "control/2fa/init_qrcode.html", [ define.get_display_name(request.userid), tfa_secret_sess, tfa.generate_tfa_qrcode(request.userid, tfa_secret_sess), "2fa" ], title="Enable 2FA: Step 2")) else: _set_recovery_codes_on_session(','.join(recovery_codes)) return Response( define.webpage(request.userid, "control/2fa/init_verify.html", [recovery_codes, None], title="Enable 2FA: Final Step"))
def control_streaming_post_(request): form = request.web_input(target="", set_stream="", stream_length="", stream_url="", stream_text="") if form.target and request.userid not in staff.MODS: raise WeasylError('InsufficientPermissions') if form.target: target = int(form.target) else: target = request.userid stream_length = define.clamp(define.get_int(form.stream_length), 0, 360) p = orm.Profile() p.stream_text = form.stream_text p.stream_url = define.text_fix_url(form.stream_url.strip()) set_stream = form.set_stream profile.edit_streaming_settings(request.userid, target, p, set_stream=set_stream, stream_length=stream_length) if form.target: target_username = define.get_sysname(define.get_display_name(target)) raise HTTPSeeOther(location="/modcontrol/manageuser?name=" + target_username) else: raise HTTPSeeOther(location="/control")
def frienduser_(request): if not define.is_vouched_for(request.userid): raise WeasylError("vouchRequired") form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: raise WeasylError('cannotSelfFriend') if form.action == "sendfriendrequest": if not frienduser.check(request.userid, otherid) and not frienduser.already_pending( request.userid, otherid): frienduser.request(request.userid, otherid) elif form.action == "withdrawfriendrequest": if frienduser.already_pending(request.userid, otherid): frienduser.remove_request(request.userid, otherid) elif form.action == "unfriend": frienduser.remove(request.userid, otherid) if form.feature == "pending": raise HTTPSeeOther(location="/manage/friends?feature=pending") else: # typical value will be user raise HTTPSeeOther( location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def vouch_(request): if not define.is_vouched_for(request.userid): raise WeasylError("vouchRequired") targetid = int(request.POST['targetid']) updated = define.engine.execute( "UPDATE login SET voucher = %(voucher)s WHERE userid = %(target)s AND voucher IS NULL RETURNING email", voucher=request.userid, target=targetid, ).first() target_username = define.get_display_name(targetid) if updated is not None: define._get_all_config.invalidate(targetid) emailer.send(updated.email, "Weasyl Account Verified", define.render("email/verified.html", [target_username])) if target_username is None: assert updated is None raise WeasylError("Unexpected") raise HTTPSeeOther(location=request.route_path( 'profile_tilde', name=define.get_sysname(target_username)))
def tfa_generate_recovery_codes_verify_password_post_(request): userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid), request.params['password'], request=None) # The user's password failed to authenticate if status == "invalid": return Response(define.webpage( request.userid, "control/2fa/generate_recovery_codes_verify_password.html", ["password"], title="Generate Recovery Codes: Verify Password" )) # The user has authenticated, so continue with generating the new recovery codes. else: # Edge case prevention: Stop the user from having two Weasyl sessions open and trying # to proceed through the generation process with two sets of recovery codes. invalidate_other_sessions(request.userid) # Edge case prevention: Do we have existing (and recent) codes on this session? Prevent # a user from confusing themselves if they visit the request page twice. sess = request.weasyl_session gen_rec_codes = True if '2fa_recovery_codes_timestamp' in sess.additional_data: # Are the codes on the current session < 30 minutes old? tstamp = sess.additional_data['2fa_recovery_codes_timestamp'] if arrow.now().timestamp - tstamp < 1800: # We have recent codes on the session, use them instead of generating fresh codes. recovery_codes = sess.additional_data['2fa_recovery_codes'].split(',') gen_rec_codes = False if gen_rec_codes: # Either this is a fresh request to generate codes, or the timelimit was exceeded. recovery_codes = tfa.generate_recovery_codes() _set_recovery_codes_on_session(','.join(recovery_codes)) return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [ recovery_codes, None ], title="Generate Recovery Codes: Save New Recovery Codes"))
def signin_2fa_auth_get_(request): sess = define.get_weasyl_session() # Only render page if the password has been authenticated (we have a UserID stored in the session) if '2fa_pwd_auth_userid' not in sess.additional_data: return Response(define.errorpage(request.userid, errorcode.permission)) tfa_userid = sess.additional_data['2fa_pwd_auth_userid'] # Maximum secondary authentication time: 5 minutes session_life = arrow.now( ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp'] if session_life > 300: _cleanup_2fa_session() return Response( define.errorpage( request.userid, errorcode. error_messages['TwoFactorAuthenticationAuthenticationTimeout'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]])) else: ref = request.params["referer"] if "referer" in request.params else "/" return Response( define.webpage( request.userid, "etc/signin_2fa_auth.html", [ define.get_display_name(tfa_userid), ref, two_factor_auth.get_number_of_recovery_codes(tfa_userid), None ], title="Sign In - 2FA"))
def control_streaming_post_(request): form = request.web_input(target="", set_stream="", stream_length="", stream_url="", stream_text="") if form.target and request.userid not in staff.MODS: return Response(define.errorpage(request.userid, errorcode.permission)) if form.target: target = int(form.target) else: target = request.userid stream_length = define.clamp(define.get_int(form.stream_length), 0, 360) p = orm.Profile() p.stream_text = form.stream_text p.stream_url = define.text_fix_url(form.stream_url.strip()) set_stream = form.set_stream profile.edit_streaming_settings(request.userid, target, p, set_stream=set_stream, stream_length=stream_length) if form.target: target_username = define.get_sysname(define.get_display_name(target)) raise HTTPSeeOther(location="/modcontrol/manageuser?name=" + target_username) else: raise HTTPSeeOther(location="/control")
def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent) if logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logid and logerror == "2fa": # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now) if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) index.template_fields.invalidate(logid) # Check if out of recovery codes; this should *never* execute normally, save for crafted # webtests. However, check for it and log an error to Sentry if it happens. remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid) if remaining_recovery_codes == 0: raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " + str(logid) + " was zero upon password authentication succeeding, " + "which should be impossible.") # Store the authenticated userid & password auth time to the session sess = define.get_weasyl_session() # The timestamp at which password authentication succeeded sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp # The userid of the user attempting authentication sess.additional_data['2fa_pwd_auth_userid'] = logid # The number of times the user has attempted to authenticate via 2FA sess.additional_data['2fa_pwd_auth_attempts'] = 0 sess.save = True return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(logid), form.referer, remaining_recovery_codes, None], title="Sign In - 2FA" )) elif logerror == "invalid": return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response(define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact %s for assistance." % (reason, MACRO_SUPPORT_ADDRESS))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response(define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "%s for assistance." % (suspension.reason, define.convert_date(suspension.release), MACRO_SUPPORT_ADDRESS))) raise WeasylError("Unexpected") # pragma: no cover
def signin_2fa_auth_post_(request): sess = define.get_weasyl_session() # Only render page if the password has been authenticated (we have a UserID stored in the session) if '2fa_pwd_auth_userid' not in sess.additional_data: return Response(define.errorpage(request.userid, errorcode.permission)) tfa_userid = sess.additional_data['2fa_pwd_auth_userid'] session_life = arrow.now( ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp'] if session_life > 300: # Maximum secondary authentication time: 5 minutes _cleanup_2fa_session() return Response( define.errorpage( request.userid, errorcode. error_messages['TwoFactorAuthenticationAuthenticationTimeout'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]])) elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]): # 2FA passed, so login and cleanup. _cleanup_2fa_session() login.signin(tfa_userid) ref = request.params["referer"] or "/" # User is out of recovery codes, so force-deactivate 2FA if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0: two_factor_auth.force_deactivate(tfa_userid) return Response( define.errorpage( tfa_userid, errorcode.error_messages[ 'TwoFactorAuthenticationZeroRecoveryCodesRemaining'], [["2FA Dashboard", "/control/2fa/status"], ["Return to the Home Page", "/"]])) # Return to the target page, restricting to the path portion of 'ref' per urlparse. raise HTTPSeeOther(location=urlparse.urlparse(ref).path) elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5: # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts. _cleanup_2fa_session() return Response( define.errorpage( request.userid, errorcode.error_messages[ 'TwoFactorAuthenticationAuthenticationAttemptsExceeded'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]])) else: # Log the failed authentication attempt to the session and save sess.additional_data['2fa_pwd_auth_attempts'] += 1 sess.save = True # 2FA failed; redirect to 2FA input page & inform user that authentication failed. return Response( define.webpage( request.userid, "etc/signin_2fa_auth.html", [ define.get_display_name(tfa_userid), request.params["referer"], two_factor_auth.get_number_of_recovery_codes(tfa_userid), "2fa" ], title="Sign In - 2FA"))
def tfa_disable_post_(request): tfaresponse = request.params['tfaresponse'] verify_checkbox = 'verify' in request.params if verify_checkbox: # If 2FA was successfully deactivated... return to 2FA dashboard if tfa.deactivate(request.userid, tfaresponse): raise HTTPSeeOther(location="/control/2fa/status") else: return Response(define.webpage(request.userid, "control/2fa/disable.html", [ define.get_display_name(request.userid), "2fa" ], title="Disable 2FA")) # The user didn't check the verification checkbox (despite HTML5's client-side check) elif not verify_checkbox: return Response(define.webpage(request.userid, "control/2fa/disable.html", [ define.get_display_name(request.userid), "verify" ], title="Disable 2FA"))
def tfa_init_post_(request): userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid), request.params['password'], request=None) # The user's password failed to authenticate if status == "invalid": return Response(define.webpage(request.userid, "control/2fa/init.html", [ define.get_display_name(request.userid), "password" ], title="Enable 2FA: Step 1")) # The user has authenticated, so continue with the initialization process. else: tfa_secret, tfa_qrcode = tfa.init(request.userid) _set_totp_code_on_session(tfa_secret) return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [ define.get_display_name(request.userid), tfa_secret, tfa_qrcode, None ], title="Enable 2FA: Step 2"))
def tfa_disable_post_(request): tfaresponse = request.params['tfaresponse'] verify_checkbox = 'verify' in request.params if verify_checkbox: # If 2FA was successfully deactivated... return to 2FA dashboard if tfa.deactivate(request.userid, tfaresponse): raise HTTPSeeOther(location="/control/2fa/status") else: return Response(define.webpage(request.userid, "control/2fa/disable.html", [ define.get_display_name(request.userid), "2fa" ], title="Disable 2FA")) # The user didn't check the verification checkbox (despite HTML5's client-side check) elif not verify_checkbox: return Response(define.webpage(request.userid, "control/2fa/disable.html", [ define.get_display_name(request.userid), "verify" ], title="Disable 2FA"))
def ignoreuser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if form.action == "ignore": ignoreuser.insert(request.userid, [otherid]) elif form.action == "unignore": ignoreuser.remove(request.userid, [otherid]) raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def select_myself(userid): if not userid: return None return { "userid": userid, "username": d.get_display_name(userid), "is_mod": userid in staff.MODS, "is_verified": d.is_vouched_for(userid), "user_media": media.get_user_media(userid), }
def ignoreuser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if form.action == "ignore": if not ignoreuser.check(request.userid, otherid): ignoreuser.insert(request.userid, otherid) elif form.action == "unignore": ignoreuser.remove(request.userid, otherid) raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def control_editprofile_put_(request): form = request.web_input(full_name="", catchphrase="", profile_text="", set_commish="", set_trade="", set_request="", set_stream="", stream_url="", stream_text="", show_age="", gender="", country="", profile_display="", site_names=[], site_values=[]) if len(form.site_names) != len(form.site_values): raise WeasylError('Unexpected') if 'more' in form: form.username = define.get_display_name(request.userid) form.sorted_user_links = [ (name, [value]) for name, value in zip(form.site_names, form.site_values) ] form.settings = form.set_commish + form.set_trade + form.set_request form.config = form.profile_display return Response( define.webpage(request.userid, "control/edit_profile.html", [form, form], title="Edit Profile", options=["typeahead"])) p = orm.Profile() p.full_name = form.full_name p.catchphrase = form.catchphrase p.profile_text = form.profile_text set_trade = profile.get_exchange_setting(profile.EXCHANGE_TYPE_TRADE, form.set_trade) set_request = profile.get_exchange_setting(profile.EXCHANGE_TYPE_REQUEST, form.set_request) set_commission = profile.get_exchange_setting( profile.EXCHANGE_TYPE_COMMISSION, form.set_commish) profile.edit_profile(request.userid, p, set_trade=set_trade, set_request=set_request, set_commission=set_commission, profile_display=form.profile_display) profile.edit_userinfo(request.userid, form) raise HTTPSeeOther(location="/control")
def POST(self): form = web.input(userid="") otherid = define.get_int(form.userid) if form.action == "ignore": if not ignoreuser.check(self.user_id, otherid): ignoreuser.insert(self.user_id, otherid) elif form.action == "unignore": ignoreuser.remove(self.user_id, otherid) raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def tfa_init_post_(request): userid, status = login.authenticate_bcrypt(define.get_display_name(request.userid), request.params['password'], request=None) # The user's password failed to authenticate if status == "invalid": return Response(define.webpage(request.userid, "control/2fa/init.html", [ define.get_display_name(request.userid), "password" ], title="Enable 2FA: Step 1")) # Unlikely that this block will get triggered, but just to be safe, check for it elif status == "unicode-failure": raise HTTPSeeOther(location='/signin/unicode-failure') # The user has authenticated, so continue with the initialization process. else: tfa_secret, tfa_qrcode = tfa.init(request.userid) _set_totp_code_on_session(tfa_secret) return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [ define.get_display_name(request.userid), tfa_secret, tfa_qrcode, None ], title="Enable 2FA: Step 2"))
def signin_2fa_auth_post_(request): sess = define.get_weasyl_session() # Only render page if the session exists //and// the password has # been authenticated (we have a UserID stored in the session) if not sess.additional_data or '2fa_pwd_auth_userid' not in sess.additional_data: return Response(define.errorpage(request.userid, errorcode.permission)) tfa_userid = sess.additional_data['2fa_pwd_auth_userid'] session_life = arrow.now().timestamp - sess.additional_data['2fa_pwd_auth_timestamp'] if session_life > 300: # Maximum secondary authentication time: 5 minutes _cleanup_2fa_session() return Response(define.errorpage( request.userid, errorcode.error_messages['TwoFactorAuthenticationAuthenticationTimeout'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]] )) elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]): # 2FA passed, so login and cleanup. _cleanup_2fa_session() login.signin(request, tfa_userid, ip_address=request.client_addr, user_agent=request.user_agent) ref = request.params["referer"] or "/" # User is out of recovery codes, so force-deactivate 2FA if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0: two_factor_auth.force_deactivate(tfa_userid) return Response(define.errorpage( tfa_userid, errorcode.error_messages['TwoFactorAuthenticationZeroRecoveryCodesRemaining'], [["2FA Dashboard", "/control/2fa/status"], ["Return to the Home Page", "/"]] )) # Return to the target page, restricting to the path portion of 'ref' per urlparse. raise HTTPSeeOther(location=urlparse.urlparse(ref).path) elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5: # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts. _cleanup_2fa_session() return Response(define.errorpage( request.userid, errorcode.error_messages['TwoFactorAuthenticationAuthenticationAttemptsExceeded'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]] )) else: # Log the failed authentication attempt to the session and save sess.additional_data['2fa_pwd_auth_attempts'] += 1 sess.save = True # 2FA failed; redirect to 2FA input page & inform user that authentication failed. return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(tfa_userid), request.params["referer"], two_factor_auth.get_number_of_recovery_codes(tfa_userid), "2fa"], title="Sign In - 2FA"))
def followuser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: raise WeasylError("cannotSelfFollow") if form.action == "follow": followuser.insert(request.userid, otherid) elif form.action == "unfollow": followuser.remove(request.userid, otherid) raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def POST(self): form = web.input(userid="") otherid = define.get_int(form.userid) if self.user_id == otherid: return define.errorpage(self.user_id, "You cannot follow yourself.") if form.action == "follow": if not followuser.check(self.user_id, otherid): followuser.insert(self.user_id, otherid) elif form.action == "unfollow": followuser.remove(self.user_id, otherid) raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def followuser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: return Response(define.errorpage(request.userid, "You cannot follow yourself.")) if form.action == "follow": if not followuser.check(request.userid, otherid): followuser.insert(request.userid, otherid) elif form.action == "unfollow": followuser.remove(request.userid, otherid) raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def followuser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: return Response( define.errorpage(request.userid, "You cannot follow yourself.")) if form.action == "follow": if not followuser.check(request.userid, otherid): followuser.insert(request.userid, otherid) elif form.action == "unfollow": followuser.remove(request.userid, otherid) raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def test_init(): """ Verify we get a usable 2FA Secret and QRCode from init() """ user_id = db_utils.create_user() tfa_secret, tfa_qrcode = tfa.init(user_id) computed_uri = pyotp.TOTP(tfa_secret).provisioning_uri(d.get_display_name(user_id), issuer_name="Weasyl") qr = QrCode.encode_text(computed_uri, QrCode.Ecc.MEDIUM) qr_xml = qr.to_svg_str(4) # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0) computed_qrcode = urllib.quote(qr_svg_only) # The QRcode we make locally should match that from init() assert tfa_qrcode == computed_qrcode # The tfa_secret from init() should be 16 characters, and work if passed in to pyotp.TOTP.now() assert len(tfa_secret) == 16 assert len(pyotp.TOTP(tfa_secret).now()) == tfa.LENGTH_TOTP_CODE
def test_init(): """ Verify we get a usable 2FA Secret and QRCode from init() """ user_id = db_utils.create_user() tfa_secret, tfa_qrcode = tfa.init(user_id) computed_uri = pyotp.TOTP(tfa_secret).provisioning_uri( d.get_display_name(user_id), issuer_name="Weasyl") qr = QrCode.encode_text(computed_uri, QrCode.Ecc.MEDIUM) qr_xml = qr.to_svg_str(4) # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0) computed_qrcode = urlquote(qr_svg_only) # The QRcode we make locally should match that from init() assert tfa_qrcode == computed_qrcode # The tfa_secret from init() should be 16 characters, and work if passed in to pyotp.TOTP.now() assert len(tfa_secret) == 16 assert len(pyotp.TOTP(tfa_secret).now()) == tfa.LENGTH_TOTP_CODE
def frienduser_(request): form = request.web_input(userid="") otherid = define.get_int(form.userid) if request.userid == otherid: return Response(define.errorpage(request.userid, "You cannot friend yourself.")) if form.action == "sendfriendrequest": if not frienduser.check(request.userid, otherid) and not frienduser.already_pending(request.userid, otherid): frienduser.request(request.userid, otherid) elif form.action == "withdrawfriendrequest": if frienduser.already_pending(request.userid, otherid): frienduser.remove_request(request.userid, otherid) elif form.action == "unfriend": frienduser.remove(request.userid, otherid) if form.feature == "pending": raise HTTPSeeOther(location="/manage/friends?feature=pending") else: # typical value will be user raise HTTPSeeOther(location="/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def POST(self): form = web.input(userid="") otherid = define.get_int(form.userid) if self.user_id == otherid: return define.errorpage(self.user_id, "You cannot friend yourself.") if form.action == "sendfriendrequest": if not frienduser.check(self.user_id, otherid) and not frienduser.already_pending(self.user_id, otherid): frienduser.request(self.user_id, otherid) elif form.action == "withdrawfriendrequest": if frienduser.already_pending(self.user_id, otherid): frienduser.remove_request(self.user_id, otherid) elif form.action == "unfriend": frienduser.remove(self.user_id, otherid) if form.feature == "pending": raise web.seeother("/manage/friends?feature=pending") else: # typical value will be user raise web.seeother("/~%s" % (define.get_sysname(define.get_display_name(otherid))))
def generate_tfa_qrcode(userid, tfa_secret): """ Generate a 2FA QRCode on-demand, with the provisioning URI based on the supplied 2FA-secret. Used as a helper function for init(), or to regenerate the QRCode from a failed attempt at verifying the init()/init_verify_tfa() phases. Parameters: userid: The userid for the calling user; used to retrieve the username for the provisioning URI. tfa_secret: The tfa_secret as generated from init(), initially. Returns: A URL-quoted SVG--containing the TOTP provisioning URI--capable of being inserted into a data-uri for display to the user. """ totp_uri = pyotp.TOTP(tfa_secret).provisioning_uri(d.get_display_name(userid), issuer_name="Weasyl") # Generate the QRcode qr = QrCode.encode_text(totp_uri, QrCode.Ecc.MEDIUM) qr_xml = qr.to_svg_str(4) # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0) return urllib.quote(qr_svg_only)
def tfa_init_qrcode_post_(request): # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456') tfaresponse = request.params['tfaresponse'].replace(' ', '') tfa_secret_sess = _get_totp_code_from_session() # Check to see if the tfaresponse matches the tfasecret when run through the TOTP algorithm tfa_secret, recovery_codes = tfa.init_verify_tfa(request.userid, tfa_secret_sess, tfaresponse) # The 2FA TOTP code did not match with the generated 2FA secret if not tfa_secret: return Response(define.webpage(request.userid, "control/2fa/init_qrcode.html", [ define.get_display_name(request.userid), tfa_secret_sess, tfa.generate_tfa_qrcode(request.userid, tfa_secret_sess), "2fa" ], title="Enable 2FA: Step 2")) else: _set_recovery_codes_on_session(','.join(recovery_codes)) return Response(define.webpage(request.userid, "control/2fa/init_verify.html", [ recovery_codes, None ], title="Enable 2FA: Final Step"))
def vouch_(request): if not define.is_vouched_for(request.userid): raise WeasylError("vouchRequired") targetid = int(request.POST['targetid']) result = define.engine.execute( "UPDATE login SET voucher = %(voucher)s WHERE userid = %(target)s AND voucher IS NULL", voucher=request.userid, target=targetid, ) if result.rowcount != 0: define._get_all_config.invalidate(targetid) target_username = define.get_display_name(targetid) if target_username is None: assert result.rowcount == 0 raise WeasylError("Unexpected") raise HTTPSeeOther(location=request.route_path( 'profile_tilde', name=define.get_sysname(target_username)))
def generate_tfa_qrcode(userid, tfa_secret): """ Generate a 2FA QRCode on-demand, with the provisioning URI based on the supplied 2FA-secret. Used as a helper function for init(), or to regenerate the QRCode from a failed attempt at verifying the init()/init_verify_tfa() phases. Parameters: userid: The userid for the calling user; used to retrieve the username for the provisioning URI. tfa_secret: The tfa_secret as generated from init(), initially. Returns: A URL-quoted SVG--containing the TOTP provisioning URI--capable of being inserted into a data-uri for display to the user. """ totp_uri = pyotp.TOTP(tfa_secret).provisioning_uri( d.get_display_name(userid), issuer_name="Weasyl") # Generate the QRcode qr = QrCode.encode_text(totp_uri, QrCode.Ecc.MEDIUM) qr_xml = qr.to_svg_str(4) # We only care about the content in the <svg> tags; strip '\n' to permit re.search to work qr_svg_only = re.search(r"<svg.*<\/svg>", qr_xml.replace('\n', '')).group(0) return urllib.quote(qr_svg_only)
def signin_2fa_auth_get_(request): sess = define.get_weasyl_session() # Only render page if the password has been authenticated (we have a UserID stored in the session) if '2fa_pwd_auth_userid' not in sess.additional_data: return Response(define.errorpage(request.userid, errorcode.permission)) tfa_userid = sess.additional_data['2fa_pwd_auth_userid'] # Maximum secondary authentication time: 5 minutes session_life = arrow.now().timestamp - sess.additional_data['2fa_pwd_auth_timestamp'] if session_life > 300: _cleanup_2fa_session() return Response(define.errorpage( request.userid, errorcode.error_messages['TwoFactorAuthenticationAuthenticationTimeout'], [["Sign In", "/signin"], ["Return to the Home Page", "/"]])) else: ref = request.params["referer"] if "referer" in request.params else "/" return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(tfa_userid), ref, two_factor_auth.get_number_of_recovery_codes(tfa_userid), None], title="Sign In - 2FA"))
def api_whoami_(request): return { "login": d.get_display_name(request.userid), "userid": request.userid, }
def GET(self): return json.dumps({ "login": d.get_display_name(self.user_id), "userid": self.user_id, })
def tfa_init_get_(request): return Response(define.webpage(request.userid, "control/2fa/init.html", [ define.get_display_name(request.userid), None ], title="Enable 2FA: Step 1"))
def change_username(acting_user, target_user, bypass_limit, new_username): new_username = clean_display_name(new_username) new_sysname = d.get_sysname(new_username) old_username = d.get_display_name(target_user) old_sysname = d.get_sysname(old_username) if new_username == old_username: return cosmetic = new_sysname == old_sysname def change_username_transaction(db): if not cosmetic and not bypass_limit: seconds = db.scalar( "SELECT extract(epoch from now() - replaced_at)::int8" " FROM username_history" " WHERE userid = %(target)s" " AND NOT cosmetic" " ORDER BY historyid DESC LIMIT 1", target=target_user, ) if seconds is not None: days = seconds // (3600 * 24) if days < 30: raise WeasylError("usernameChangedTooRecently") if not cosmetic: release_username( db, acting_user=acting_user, target_user=target_user, ) conflict = db.scalar( "SELECT EXISTS (SELECT FROM login WHERE login_name = %(new_sysname)s AND userid != %(target)s)" " OR EXISTS (SELECT FROM useralias WHERE alias_name = %(new_sysname)s)" " OR EXISTS (SELECT FROM logincreate WHERE login_name = %(new_sysname)s)" " OR EXISTS (SELECT FROM username_history WHERE active AND login_name = %(new_sysname)s)", target=target_user, new_sysname=new_sysname, ) if conflict: raise WeasylError("usernameExists") db.execute( "INSERT INTO username_history (userid, username, login_name, replaced_at, replaced_by, active, cosmetic)" " VALUES (%(target)s, %(old_username)s, %(old_sysname)s, now(), %(target)s, NOT %(cosmetic)s, %(cosmetic)s)", target=target_user, old_username=old_username, old_sysname=old_sysname, cosmetic=cosmetic, ) if not cosmetic: result = db.execute( "UPDATE login SET login_name = %(new_sysname)s WHERE userid = %(target)s AND login_name = %(old_sysname)s", target=target_user, old_sysname=old_sysname, new_sysname=new_sysname, ) if result.rowcount != 1: raise WeasylError("Unexpected") db.execute( "UPDATE profile SET username = %(new_username)s WHERE userid = %(target)s", target=target_user, new_username=new_username, ) d.serializable_retry(change_username_transaction) d._get_display_name.invalidate(target_user)
def tfa_disable_get_(request): return Response( define.webpage(request.userid, "control/2fa/disable.html", [define.get_display_name(request.userid), None], title="Disable 2FA"))
def tfa_init_get_(request): return Response( define.webpage(request.userid, "control/2fa/init.html", [define.get_display_name(request.userid), None], title="Enable 2FA: Step 1"))
def edit_email_password(userid, username, password, newemail, newemailcheck, newpassword, newpasscheck): """ Edit the email address and/or password for a given Weasyl account. After verifying the user's current login credentials, edit the user's email address and/or password if validity checks pass. If the email is modified, a confirmation email is sent to the user's target email with a token which, if used, finalizes the email address change. Parameters: userid: The `userid` of the Weasyl account to modify. username: User-entered username for password-based authentication. password: The user's current plaintext password. newemail: If changing the email on the account, the new email address. Optional. newemailcheck: A verification field for the above to serve as a typo-check. Optional, but mandatory if `newemail` provided. newpassword: If changing the password, the user's new password. Optional. newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if `newpassword` provided. """ from weasyl import login # Track if any changes were made for later display back to the user. changes_made = "" # Check that credentials are correct logid, logerror = login.authenticate_bcrypt(username, password, request=None) # Run checks prior to modifying anything... if userid != logid or logerror is not None: raise WeasylError("loginInvalid") if newemail: if newemail != newemailcheck: raise WeasylError("emailMismatch") if newpassword: if newpassword != newpasscheck: raise WeasylError("passwordMismatch") elif not login.password_secure(newpassword): raise WeasylError("passwordInsecure") # If we are setting a new email, then write the email into a holding table pending confirmation # that the email is valid. if newemail: # Only actually attempt to change the email if unused; prevent finding out if an email is already registered if not login.email_exists(newemail): token = security.generate_key(40) # Store the current token & email, updating them to overwrite a previous attempt if needed d.engine.execute(""" INSERT INTO emailverify (userid, email, token, createtimestamp) VALUES (%(userid)s, %(newemail)s, %(token)s, NOW()) ON CONFLICT (userid) DO UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW() """, userid=userid, newemail=newemail, token=token) # Send out the email containing the verification token. emailer.append([newemail], None, "Weasyl Email Change Confirmation", d.render("email/verify_emailchange.html", [token, d.get_display_name(userid)])) else: # The target email exists: let the target know this query_username = d.engine.scalar(""" SELECT login_name FROM login WHERE email = %(email)s """, email=newemail) emailer.append([newemail], None, "Weasyl Account Information - Duplicate Email on Accounts Rejected", d.render( "email/email_in_use_email_change.html", [query_username]) ) # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed. changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n" # If the password is being updated, update the hash, and clear other sessions. if newpassword: d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid]) # Invalidate all sessions for `userid` except for the current one invalidate_other_sessions(userid) # Then add to `changes_made` detailing that the password change has successfully occurred. changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions." if changes_made != "": return changes_made else: return False
def edit_email_password(userid, username, password, newemail, newemailcheck, newpassword, newpasscheck): """ Edit the email address and/or password for a given Weasyl account. After verifying the user's current login credentials, edit the user's email address and/or password if validity checks pass. If the email is modified, a confirmation email is sent to the user's target email with a token which, if used, finalizes the email address change. Parameters: userid: The `userid` of the Weasyl account to modify. username: User-entered username for password-based authentication. password: The user's current plaintext password. newemail: If changing the email on the account, the new email address. Optional. newemailcheck: A verification field for the above to serve as a typo-check. Optional, but mandatory if `newemail` provided. newpassword: If changing the password, the user's new password. Optional. newpasswordcheck: Verification field for `newpassword`. Optional, but mandatory if `newpassword` provided. """ from weasyl import login # Track if any changes were made for later display back to the user. changes_made = "" # Check that credentials are correct logid, logerror = login.authenticate_bcrypt(username, password, request=None) # Run checks prior to modifying anything... if userid != logid or logerror is not None: raise WeasylError("loginInvalid") if newemail: if newemail != newemailcheck: raise WeasylError("emailMismatch") if newpassword: if newpassword != newpasscheck: raise WeasylError("passwordMismatch") elif not login.password_secure(newpassword): raise WeasylError("passwordInsecure") # If we are setting a new email, then write the email into a holding table pending confirmation # that the email is valid. if newemail: # Only actually attempt to change the email if unused; prevent finding out if an email is already registered if not login.email_exists(newemail): token = security.generate_key(40) # Store the current token & email, updating them to overwrite a previous attempt if needed d.engine.execute(""" INSERT INTO emailverify (userid, email, token, createtimestamp) VALUES (%(userid)s, %(newemail)s, %(token)s, NOW()) ON CONFLICT (userid) DO UPDATE SET email = %(newemail)s, token = %(token)s, createtimestamp = NOW() """, userid=userid, newemail=newemail, token=token) # Send out the email containing the verification token. emailer.append([newemail], None, "Weasyl Email Change Confirmation", d.render("email/verify_emailchange.html", [token, d.get_display_name(userid)])) else: # The target email exists: let the target know this query_username = d.engine.scalar(""" SELECT login_name FROM login WHERE email = %(email)s """, email=newemail) emailer.append( [newemail], None, "Weasyl Account Information - Duplicate Email on Accounts Rejected", d.render("email/email_in_use_email_change.html", [query_username])) # Then add text to `changes_made` telling that we have completed the email change request, and how to proceed. changes_made += "Your email change request is currently pending. An email has been sent to **" + newemail + "**. Follow the instructions within to finalize your email address change.\n" # If the password is being updated, update the hash, and clear other sessions. if newpassword: d.execute("UPDATE authbcrypt SET hashsum = '%s' WHERE userid = %i", [login.passhash(newpassword), userid]) # Invalidate all sessions for `userid` except for the current one invalidate_other_sessions(userid) # Then add to `changes_made` detailing that the password change has successfully occurred. changes_made += "Your password has been successfully changed. As a security precaution, you have been logged out of all other active sessions." if changes_made != "": return changes_made else: return False
def tfa_disable_get_(request): return Response(define.webpage(request.userid, "control/2fa/disable.html", [ define.get_display_name(request.userid), None ], title="Disable 2FA"))
def select_comments(userid): queries = [] # Shout comments current_username = d.get_sysname(d.get_display_name(userid)) queries.append({ "type": 4010, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "ownername": current_username, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.targetid, pr.username FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid WHERE we.userid = %(user)s AND we.type = 4010 ORDER BY we.unixtime DESC """, user=userid)) # Shout replies queries.append({ "type": 4015, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "ownerid": i.ownerid, "ownername": i.owner_username, "replyid": i.referid, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, px.userid AS ownerid, px.username AS owner_username FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN comments sh ON we.referid = sh.commentid INNER JOIN profile px ON sh.target_user = px.userid WHERE we.userid = %(user)s AND we.type = 4015 ORDER BY we.unixtime DESC """, user=userid)) # Staff comment replies queries.append({ "type": 4016, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "ownerid": i.ownerid, "ownername": i.owner_username, "replyid": i.referid, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, px.userid AS ownerid, px.username AS owner_username FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN comments sh ON we.referid = sh.commentid INNER JOIN profile px ON sh.target_user = px.userid WHERE we.userid = %(user)s AND we.type = 4016 ORDER BY we.unixtime DESC """, user=userid)) # Submission comments queries.append({ "type": 4020, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "submitid": i.referid, "title": i.title, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.title FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN submission su ON we.referid = su.submitid WHERE we.userid = %(user)s AND we.type = 4020 ORDER BY we.unixtime DESC """, user=userid)) # Submission comment replies queries.append({ "type": 4025, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "submitid": i.submitid, "title": i.title, "replyid": i.referid, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.submitid, su.title FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN comments sc ON we.referid = sc.commentid INNER JOIN submission su ON sc.target_sub = su.submitid WHERE we.userid = %(user)s AND we.type = 4025 ORDER BY we.unixtime DESC """, user=userid)) # Character comments queries.append({ "type": 4040, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "charid": i.referid, "title": i.char_name, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, ch.char_name FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN character ch ON we.referid = ch.charid WHERE we.userid = %(user)s AND we.type = 4040 ORDER BY we.unixtime DESC """, user=userid)) # Character comment replies queries.append({ "type": 4045, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "charid": i.charid, "title": i.char_name, "replyid": i.referid, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, ch.charid, ch.char_name FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN charcomment cc ON we.referid = cc.commentid INNER JOIN character ch ON cc.targetid = ch.charid WHERE we.userid = %(user)s AND we.type = 4045 ORDER BY we.unixtime DESC """, user=userid)) # Journal comments queries.append({ "type": 4030, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "journalid": i.referid, "title": i.title, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, jo.title FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN journal jo ON we.referid = jo.journalid WHERE we.userid = %(user)s AND we.type = 4030 ORDER BY we.unixtime DESC """, user=userid)) # Journal comment replies queries.append({ "type": 4035, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "journalid": i.journalid, "title": i.title, "replyid": i.referid, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, jo.journalid, jo.title FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN journalcomment jc ON we.referid = jc.commentid INNER JOIN journal jo ON jc.targetid = jo.journalid WHERE we.userid = %(user)s AND we.type = 4035 ORDER BY we.unixtime DESC """, user=userid)) # Collection comments queries.append({ "type": 4050, "id": i.welcomeid, "unixtime": i.unixtime, "userid": i.otherid, "username": i.username, "submitid": i.referid, "title": i.title, "commentid": i.targetid, } for i in d.engine.execute(""" SELECT we.welcomeid, we.unixtime, we.otherid, we.referid, we.targetid, pr.username, su.title FROM welcome we INNER JOIN profile pr ON we.otherid = pr.userid INNER JOIN submission su ON we.referid = su.submitid WHERE we.userid = %(user)s AND we.type = 4050 ORDER BY we.unixtime DESC """, user=userid)) return list(chain(*queries))
def signin_post_(request): form = request.web_input(username="", password="", referer="", sfwmode="nsfw") form.referer = form.referer or '/' logid, logerror = login.authenticate_bcrypt(form.username, form.password, request=request, ip_address=request.client_addr, user_agent=request.user_agent) if logid and logerror == 'unicode-failure': raise HTTPSeeOther(location='/signin/unicode-failure') elif logid and logerror is None: if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) # Invalidate cached versions of the frontpage to respect the possibly changed SFW settings. index.template_fields.invalidate(logid) raise HTTPSeeOther(location=form.referer) elif logid and logerror == "2fa": # Password authentication passed, but user has 2FA set, so verify second factor (Also set SFW mode now) if form.sfwmode == "sfw": request.set_cookie_on_response("sfwmode", "sfw", 31536000) index.template_fields.invalidate(logid) # Check if out of recovery codes; this should *never* execute normally, save for crafted # webtests. However, check for it and log an error to Sentry if it happens. remaining_recovery_codes = two_factor_auth.get_number_of_recovery_codes(logid) if remaining_recovery_codes == 0: raise RuntimeError("Two-factor Authentication: Count of recovery codes for userid " + str(logid) + " was zero upon password authentication succeeding, " + "which should be impossible.") # Store the authenticated userid & password auth time to the session sess = define.get_weasyl_session() # The timestamp at which password authentication succeeded sess.additional_data['2fa_pwd_auth_timestamp'] = arrow.now().timestamp # The userid of the user attempting authentication sess.additional_data['2fa_pwd_auth_userid'] = logid # The number of times the user has attempted to authenticate via 2FA sess.additional_data['2fa_pwd_auth_attempts'] = 0 sess.save = True return Response(define.webpage( request.userid, "etc/signin_2fa_auth.html", [define.get_display_name(logid), form.referer, remaining_recovery_codes, None], title="Sign In - 2FA" )) elif logerror == "invalid": return Response(define.webpage(request.userid, "etc/signin.html", [True, form.referer])) elif logerror == "banned": reason = moderation.get_ban_reason(logid) return Response(define.errorpage( request.userid, "Your account has been permanently banned and you are no longer allowed " "to sign in.\n\n%s\n\nIf you believe this ban is in error, please " "contact [email protected] for assistance." % (reason,))) elif logerror == "suspended": suspension = moderation.get_suspension(logid) return Response(define.errorpage( request.userid, "Your account has been temporarily suspended and you are not allowed to " "be logged in at this time.\n\n%s\n\nThis suspension will be lifted on " "%s.\n\nIf you believe this suspension is in error, please contact " "[email protected] for assistance." % (suspension.reason, define.convert_date(suspension.release)))) elif logerror == "address": return Response("IP ADDRESS TEMPORARILY BLOCKED") return Response(define.errorpage(request.userid))