async def profile(id): # request args mode = request.args.get('mode', type=str) mods = request.args.get('mods', type=str) # check for valid mods if mods: if mods not in _valid_mods: return await render_template('404.html'), 404 else: mods = 'vn' # check for valid modes if mode: if mode not in _valid_modes: return await render_template('404.html'), 404 else: mode = 'std' user_data = await glob.db.fetch( 'SELECT name, id, priv, country FROM users WHERE id = %s OR safe_name = %s', [id, get_safe_name(id)]) # user is banned and we're not staff; render 404 is_staff = 'authenticated' in session and session['user_data']['is_staff'] if not user_data or not (user_data['priv'] & Privileges.Normal or is_staff): return await render_template('404.html'), 404 return await render_template('profile.html', user=user_data, mode=mode, mods=mods)
async def profile(id): mode = request.args.get('mode', type=str) mods = request.args.get('mods', type=str) # make sure mode & mods are valid args if mode is not None: if mode not in VALID_MODES: return await render_template('404.html'), 404 else: mode = 'std' if mods is not None: if mods not in VALID_MODS: return await render_template('404.html'), 404 else: mods = 'vn' user_data = await glob.db.fetch( 'SELECT name, id, priv, country ' 'FROM users ' 'WHERE id = %s OR safe_name = %s', [id, utils.get_safe_name(id)] # ^ allow lookup from both # id and username (safe) ) # user is banned and we're not staff; render 404 is_staff = 'authenticated' in session and session['user_data']['is_staff'] if not user_data or not (user_data['priv'] & Privileges.Normal or is_staff): return await render_template('404.html'), 404 return await render_template('profile.html', user=user_data, mode=mode, mods=mods)
async def settings_profile_post(): # if not authenticated; render login if not 'authenticated' in session: return await flash('error', 'You must be logged in to access profile settings!', 'login') # form data form = await request.form username = form.get('username') email = form.get('email') # no data has changed; deny post if username == session['user_data']['name'] and email == session['user_data']['email']: return await flash('error', 'No changes have been made.', 'settings/profile') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player if not _username_rgx.match(username): return await flash('error', 'Your new username syntax is invalid.', 'settings/profile') if '_' in username and ' ' in username: return await flash('error', 'Your new username may contain "_" or " ", but not both.', 'settings/profile') if username in glob.config.disallowed_names: return await flash('error', 'Your new username isn\'t allowed; pick another.', 'settings/profile') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s AND NOT name = %s', [username, session['user_data']['name']]): return await flash('error', 'Your new username already taken by another user.', 'settings/profile') # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not _email_rgx.match(email): return await flash('error', 'Your new email syntax is invalid.', 'settings/profile') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s AND NOT email = %s', [email, session['user_data']['email']]): return await flash('error', 'Your new email already taken by another user.', 'settings/profile') # username change successful if username != session['user_data']['name'] and session['user_data']['is_donator']: await glob.db.execute('UPDATE users SET name = %s, safe_name = %s WHERE safe_name = %s', [username, get_safe_name(username), get_safe_name(session['user_data']['name'])]) # email change successful if email != session['user_data']['email']: safe_name = get_safe_name(username) if username != session['user_data']['name'] else get_safe_name(session['user_data']['name']) await glob.db.execute('UPDATE users SET email = %s WHERE safe_name = %s', [email, safe_name]) # logout session.pop('authenticated', None) session.pop('user_data', None) return await flash('success', 'Your username/email have been changed! Please login again.', 'login')
async def get_user_info(): # get request args id = request.args.get('id', type=int) name = request.args.get('name', type=str) # check if required parameters are met if not name and not id: return b'missing parameters! (id or name)' # fetch user info and stats # user info q = ['SELECT u.id user_id, u.name username, u.safe_name username_safe, u.country, u.priv privileges, ' 'u.silence_end, u.donor_end, u.creation_time, u.latest_activity, u.clan_id, u.clan_priv, ' # total score 'tscore_vn_std, tscore_vn_taiko, tscore_vn_catch, tscore_vn_mania, ' 'tscore_rx_std, tscore_rx_taiko, tscore_rx_catch, ' 'tscore_ap_std, ' # ranked score 'rscore_vn_std, rscore_vn_taiko, rscore_vn_catch, rscore_vn_mania, ' 'rscore_rx_std, rscore_rx_taiko, rscore_rx_catch, ' 'rscore_ap_std, ' # pp 'pp_vn_std, pp_vn_taiko, pp_vn_catch, pp_vn_mania, ' 'pp_rx_std, pp_rx_taiko, pp_rx_catch, ' 'pp_ap_std, ' # plays 'plays_vn_std, plays_vn_taiko, plays_vn_catch, plays_vn_mania, ' 'plays_rx_std, plays_rx_taiko, plays_rx_catch, ' 'plays_ap_std, ' # playtime 'playtime_vn_std, playtime_vn_taiko, playtime_vn_catch, playtime_vn_mania, ' 'playtime_rx_std, playtime_rx_taiko, playtime_rx_catch, ' 'playtime_ap_std, ' # accuracy 'acc_vn_std, acc_vn_taiko, acc_vn_catch, acc_vn_mania, ' 'acc_rx_std, acc_rx_taiko, acc_rx_catch, ' 'acc_ap_std, ' # maximum combo 'maxcombo_vn_std, maxcombo_vn_taiko, maxcombo_vn_catch, maxcombo_vn_mania, ' 'maxcombo_rx_std, maxcombo_rx_taiko, maxcombo_rx_catch, ' 'maxcombo_ap_std ' # join users 'FROM stats JOIN users u ON stats.id = u.id'] # achivement q2 = [''' SELECT userid, achid FROM user_achievements ua INNER JOIN users u ON u.id = ua.userid '''] # argumnts args = [] # append request arguments (id or name) if id: q.append('WHERE u.id = %s') q2.append('WHERE u.id = %s') args.append(id) elif name: q.append('WHERE u.safe_name = %s') q2.append('WHERE u.safe_name = %s') args.append(get_safe_name(name)) q2.append('ORDER BY ua.achid ASC') if glob.config.debug: log(' '.join(q), Ansi.LGREEN) res = await glob.db.fetchall(' '.join(q), args) res_ach = await glob.db.fetchall(' '.join(q2), args) return jsonify(userdata=res,achivement=res_ach) if res else b'{}'
async def settings_profile_post(): form = await request.form new_name = form.get('username', type=str) new_email = form.get('email', type=str) if new_name is None or new_email is None: return await flash('error', 'Invalid parameters.', 'home') old_name = session['user_data']['name'] old_email = session['user_data']['email'] # no data has changed; deny post if ( new_name == old_name and new_email == old_email ): return await flash('error', 'No changes have been made.', 'settings/profile') if new_name != old_name: if not session['user_data']['is_donator'] or not session['user_data']['is_staff']: return await flash('error', 'Username changes are currently a supporter perk.', 'settings/profile') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player if not regexes.username.match(new_name): return await flash('error', 'Your new username syntax is invalid.', 'settings/profile') if '_' in new_name and ' ' in new_name: return await flash('error', 'Your new username may contain "_" or " ", but not both.', 'settings/profile') if new_name in glob.config.disallowed_names: return await flash('error', "Your new username isn't allowed; pick another.", 'settings/profile') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s', [new_name]): return await flash('error', 'Your new username already taken by another user.', 'settings/profile') safe_name = utils.get_safe_name(new_name) # username change successful await glob.db.execute( 'UPDATE users ' 'SET name = %s, safe_name = %s ' 'WHERE id = %s', [new_name, safe_name, session['user_data']['id']] ) if new_email != old_email: # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not regexes.email.match(new_email): return await flash('error', 'Your new email syntax is invalid.', 'settings/profile') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s', [new_email]): return await flash('error', 'Your new email already taken by another user.', 'settings/profile') # email change successful await glob.db.execute( 'UPDATE users ' 'SET email = %s ' 'WHERE id = %s', [new_email, session['user_data']['id']] ) # logout session.pop('authenticated', None) session.pop('user_data', None) return await flash('success', 'Your username/email have been changed! Please login again.', 'login')
async def register_post(): if 'authenticated' in session: return await flash('error', "You're already logged in.", 'home') if not glob.config.registration: return await flash('error', 'Registrations are currently disabled.', 'home') form = await request.form username = form.get('username', type=str) email = form.get('email', type=str) passwd_txt = form.get('password', type=str) if username is None or email is None or passwd_txt is None: return await flash('error', 'Invalid parameters.', 'home') if glob.config.hCaptcha_sitekey != 'changeme': captcha_data = form.get('h-captcha-response', type=str) if ( captcha_data is None or not await utils.validate_captcha(captcha_data) ): return await flash('error', 'Captcha failed.', 'register') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player # check if username exists if not regexes.username.match(username): return await flash('error', 'Invalid username syntax.', 'register') if '_' in username and ' ' in username: return await flash('error', 'Username may contain "_" or " ", but not both.', 'register') if username in glob.config.disallowed_names: return await flash('error', 'Disallowed username; pick another.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s', username): return await flash('error', 'Username already taken by another user.', 'register') # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not regexes.email.match(email): return await flash('error', 'Invalid email syntax.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s', email): return await flash('error', 'Email already taken by another user.', 'register') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 <= len(passwd_txt) <= 32: return await flash('error', 'Password must be 8-32 characters in length.', 'register') if len(set(passwd_txt)) <= 3: return await flash('error', 'Password must have more than 3 unique characters.', 'register') if passwd_txt.lower() in glob.config.disallowed_passwords: return await flash('error', 'That password was deemed too simple.', 'register') # TODO: add correct locking # (start of lock) pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode() k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) pw_hash = k.derive(pw_md5).decode('unicode-escape') glob.cache['pw'][pw_hash] = pw_md5 # cache pw safe_name = utils.get_safe_name(username) # fetch the users' country if 'CF-Connecting-IP' in request.headers: ip = request.headers['CF-Connecting-IP'] else: ip = request.headers['X-Forwarded-For'].split(',')[0] try: country = await utils.fetch_geoloc(ip) except: country = 'xx' user = await glob.db.execute( 'INSERT INTO users ' '(name, safe_name, email, pw, country, registered_at) ' 'VALUES (%s, %s, %s, %s, %s, UNIX_TIMESTAMP())', [username, safe_name, email, pw_hash, country] ) user_id = user # add to `stats` table. await glob.db.execute( 'INSERT INTO stats ' '(id) VALUES (%s)', user_id ) # (end of lock) if glob.config.debug: log(f'{username} has registered - awaiting verification.', Ansi.LGREEN) # user has successfully registered return await render_template('verify.html')
async def login_post(): if 'authenticated' in session: return await flash('error', "You're already logged in!", 'home') if glob.config.debug: login_time = time.time_ns() form = await request.form username = form.get('username', type=str) passwd_txt = form.get('password', type=str) if username is None or passwd_txt is None: return await flash('error', 'Invalid parameters.', 'home') # check if account exists user_info = await glob.db.fetchrow( 'SELECT id, name, email, priv, ' 'pw, silence_end ' 'FROM users ' 'WHERE safe_name = %s', [utils.get_safe_name(username)] ) # user doesn't exist; deny post # NOTE: Bot isn't a user. if not user_info or user_info['id'] == 1: if glob.config.debug: log(f"{username}'s login failed - account doesn't exist.", Ansi.LYELLOW) return await flash('error', 'Account does not exist.', 'login') # cache and other related password information pw_cache = glob.cache['pw'] pw_hash = user_info['pw'].encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1') pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode() # check credentials (password) against db # intentionally slow, will cache to speed up if pw_hash in pw_cache: if pw_md5 != pw_cache[pw_hash]: # ~0.1ms if glob.config.debug: log(f"{username}'s login failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Password is incorrect.', 'login') else: # ~200ms k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) try: k.verify(pw_md5, pw_hash) except: if glob.config.debug: log(f"{username}'s login failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Password is incorrect.', 'login') # login successful; cache password for next login pw_cache[pw_hash] = pw_md5 # user not verified; render verify if not user_info['priv'] & Privileges.Verified: if glob.config.debug: log(f"{username}'s login failed - not verified.", Ansi.LYELLOW) return await render_template('verify.html') # user banned; deny post if user_info['priv'] & Privileges.Disallowed: if glob.config.debug: log(f"{username}'s login failed - banned.", Ansi.RED) return await flash('error', 'Your account is restricted. You are not allowed to log in.', 'login') # login successful; store session data if glob.config.debug: log(f"{username}'s login succeeded.", Ansi.LGREEN) session['authenticated'] = True session['user_data'] = { 'id': user_info['id'], 'name': user_info['name'], 'email': user_info['email'], 'priv': user_info['priv'], 'silence_end': user_info['silence_end'], 'is_staff': user_info['priv'] & Privileges.Staff, 'is_donator': user_info['priv'] & Privileges.Supporter } if glob.config.debug: login_time = (time.time_ns() - login_time) / 1e6 log(f'Login took {login_time:.2f}ms!', Ansi.LYELLOW) return await flash('success', f'Hey, welcome back {username}!', 'home')
async def settings_password_post(): form = await request.form old_password = form.get('old_password') new_password = form.get('new_password') repeat_password = form.get('repeat_password') # new password and repeat password don't match; deny post if new_password != repeat_password: return await flash('error', "Your new password doesn't match your repeated password!", 'settings/password') # new password and old password match; deny post if old_password == new_password: return await flash('error', 'Your new password cannot be the same as your old password!', 'settings/password') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 < len(new_password) <= 32: return await flash('error', 'Your new password must be 8-32 characters in length.', 'settings/password') if len(set(new_password)) <= 3: return await flash('error', 'Your new password must have more than 3 unique characters.', 'settings/password') if new_password.lower() in glob.config.disallowed_passwords: return await flash('error', 'Your new password was deemed too simple.', 'settings/password') # cache and other password related information pw_cache = glob.cache['pw'] pw_hash = (await glob.db.fetchval( 'SELECT pw ' 'FROM users ' 'WHERE id = %s', [session['user_data']['id']]) ).encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1') pw_md5 = hashlib.md5(old_password.encode()).hexdigest().encode() # check old password against db # intentionally slow, will cache to speed up if pw_hash in pw_cache: if pw_md5 != pw_cache[pw_hash]: # ~0.1ms if glob.config.debug: log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') else: # ~200ms k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) try: k.verify(pw_hash, pw_md5) except: if glob.config.debug: log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') # remove old password from cache if pw_hash in pw_cache: del pw_cache[pw_hash] # calculate new md5 & bcrypt pw pw_md5 = hashlib.md5(new_password.encode()).hexdigest().encode() k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) pw_hash_new = k.derive(pw_md5).decode('unicode-escape') # update password in cache and db pw_cache[pw_hash_new] = pw_md5 await glob.db.execute( 'UPDATE users ' 'SET pw = %s ' 'WHERE safe_name = %s', [pw_hash_new, utils.get_safe_name(session['user_data']['name'])] ) # logout session.pop('authenticated', None) session.pop('user_data', None) return await flash('success', 'Your password has been changed! Please log in again.', 'login')
async def register_post(): # if authenticated; deny post if 'authenticated' in session: return await flash('error', f'Hey! You\'re already registered and logged in {session["user_data"]["name"]}!', 'home') # if registration is disabled; deny post if not glob.config.registration: return await flash('error', 'Hey! You can\'t register at this time! Sorry for the inconvenience!', 'home') # form data form = await request.form username = form.get('username') email = form.get('email') pw_txt = form.get('password') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player # check if username exists if not _username_rgx.match(username): return await flash('error', 'Invalid username syntax.', 'register') if '_' in username and ' ' in username: return await flash('error', 'Username may contain "_" or " ", but not both.', 'register') if username in glob.config.disallowed_names: return await flash('error', 'Disallowed username; pick another.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s', username): return await flash('error', 'Username already taken by another user.', 'register') # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not _email_rgx.match(email): return await flash('error', 'Invalid email syntax.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s', email): return await flash('error', 'Email already taken by another user.', 'register') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 < len(pw_txt) <= 32: return await flash('error', 'Password must be 8-32 characters in length', 'register') if len(set(pw_txt)) <= 3: return await flash('error', 'Password must have more than 3 unique characters.', 'register') if pw_txt.lower() in glob.config.disallowed_passwords: return await flash('error', 'That password was deemed too simple.', 'register') async with asyncio.Lock(): # cache password pw_md5 = hashlib.md5(pw_txt.encode()).hexdigest().encode() pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt()) glob.cache['bcrypt'][pw_bcrypt] = pw_md5 # get safe name safe_name = get_safe_name(username) # fetch the users' country if request.headers and (ip := request.headers.get('X-Real-IP')): country = await fetch_geoloc(ip) else:
async def login_post(): # if authenticated; deny post if 'authenticated' in session: return await flash('error', f'Hey! You\'re already logged in {session["user_data"]["name"]}!', 'home') login_time = time.time_ns() if glob.config.debug else 0 # form data # NOTE: we purposely don't store the raw password text for user security form = await request.form username = form.get('username') # check if account exists user_info = await glob.db.fetch( 'SELECT id, name, email, priv, pw_bcrypt, silence_end ' 'FROM users WHERE safe_name = %s', [get_safe_name(username)] ) # user doesn't exist; deny post # NOTE: Bot isn't a user. if not user_info or user_info['id'] == 1: if glob.config.debug: log(f'{username}\'s login failed - account doesn\'t exist.', Ansi.LYELLOW) return await flash('error', 'Account does not exist.', 'login') # cache and other related password information bcrypt_cache = glob.cache['bcrypt'] pw_bcrypt = user_info['pw_bcrypt'].encode() pw_md5 = hashlib.md5(form.get('password').encode()).hexdigest().encode() # check credentials (password) against db # intentionally slow, will cache to speed up if pw_bcrypt in bcrypt_cache: if pw_md5 != bcrypt_cache[pw_bcrypt]: # ~0.1ms if glob.config.debug: log(f'{username}\'s login failed - pw incorrect.', Ansi.LYELLOW) return await flash('error', 'Password is incorrect.', 'login') else: # ~200ms if not bcrypt.checkpw(pw_md5, pw_bcrypt): if glob.config.debug: log(f'{username}\'s login failed - pw incorrect.', Ansi.LYELLOW) return await flash('error', 'Password is incorrect.', 'login') # login successful; cache password for next login bcrypt_cache[pw_bcrypt] = pw_md5 # user not verified; render verify if not user_info['priv'] & Privileges.Verified: if glob.config.debug: log(f'{username}\'s login failed - not verified.', Ansi.LYELLOW) return await render_template('verify.html') # user banned; deny post if not user_info['priv'] & Privileges.Normal: if glob.config.debug: log(f'{username}\'s login failed - banned.', Ansi.RED) return await flash('error', 'You are banned!', 'login') # login successful; store session data if glob.config.debug: log(f'{username}\'s login succeeded.', Ansi.LGREEN) session['authenticated'] = True session['user_data'] = { 'id': user_info['id'], 'name': user_info['name'], 'email': user_info['email'], 'priv': user_info['priv'], 'silence_end': user_info['silence_end'], 'is_staff': user_info['priv'] & Privileges.Staff, 'is_donator': user_info['priv'] & Privileges.Donator } if glob.config.debug: login_time = (time.time_ns() - login_time) / 1e6 log(f'Login took {login_time:.2f}ms!', Ansi.LYELLOW) # authentication successful; render home return await flash('success', f'Hey! Welcome back {username.lower().capitalize()}!', 'home')
async def settings_password_post(): # if not authenticated; render login if not 'authenticated' in session: return await flash('error', 'You must be logged in to access password settings!', 'login') # form data form = await request.form old_password = form.get('old_password') new_password = form.get('new_password') repeat_password = form.get('repeat_password') # cache and other password related information bcrypt_cache = glob.cache['bcrypt'] pw_bcrypt = (await glob.db.fetch('SELECT pw_bcrypt FROM users WHERE safe_name = %s', [get_safe_name(session['user_data']['name'])]))['pw_bcrypt'].encode() pw_md5 = hashlib.md5(old_password.encode()).hexdigest().encode() # check old password against db # intentionally slow, will cache to speed up if pw_bcrypt in bcrypt_cache: if pw_md5 != bcrypt_cache[pw_bcrypt]: # ~0.1ms if glob.config.debug: log(f'{session["user_data"]["name"]}\'s change pw failed - pw incorrect.', Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') else: # ~200ms if not bcrypt.checkpw(pw_md5, pw_bcrypt): if glob.config.debug: log(f'{session["user_data"]["name"]}\'s change pw failed - pw incorrect.', Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') # new password and old password match; deny post if old_password.lower() == new_password.lower(): return await flash('error', 'Your new password cannot be the same as your old password!', 'settings/password') # new password and repeat password don't match; deny post if new_password != repeat_password: return await flash('error', "Your new password doesn't match your repeated password!", 'settings/password') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 < len(new_password) <= 32: return await flash('error', 'Your new password must be 8-32 characters in length.', 'settings/password') if len(set(new_password)) <= 3: return await flash('error', 'Your new password must have more than 3 unique characters.', 'settings/password') if new_password.lower() in glob.config.disallowed_passwords: return await flash('error', 'Your new password was deemed too simple.', 'settings/password') # remove old password from cache if pw_bcrypt in bcrypt_cache: del bcrypt_cache[pw_bcrypt] # update password in cache and db pw_md5 = hashlib.md5(new_password.encode()).hexdigest().encode() pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt()) bcrypt_cache[pw_bcrypt] = pw_md5 await glob.db.execute('UPDATE users SET pw_bcrypt = %s WHERE safe_name = %s', [pw_bcrypt, get_safe_name(session['user_data']['name'])]) # logout session.pop('authenticated', None) session.pop('user_data', None) return await flash('success', 'Your password has been changed! Please login again.', 'login')
async def register_post(): if 'authenticated' in session: return await flash('error', "You're already logged in.", 'home') if not glob.config.registration: return await flash('error', 'Registrations are currently disabled.', 'home') form = await request.form username = form.get('username', type=str) email = form.get('email', type=str) passwd_txt = form.get('password', type=str) if username is None or email is None or passwd_txt is None: return await flash('error', 'Invalid parameters.', 'home') if glob.config.hCaptcha_sitekey != 'changeme': captcha_data = form.get('h-captcha-response', type=str) if (captcha_data is None or not await utils.validate_captcha(captcha_data)): return await flash('error', 'Captcha failed.', 'register') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player # check if username exists if not regexes.username.match(username): return await flash('error', 'Invalid username syntax.', 'register') if '_' in username and ' ' in username: return await flash('error', 'Username may contain "_" or " ", but not both.', 'register') if username in glob.config.disallowed_names: return await flash('error', 'Disallowed username; pick another.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s', username): return await flash('error', 'Username already taken by another user.', 'register') # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not regexes.email.match(email): return await flash('error', 'Invalid email syntax.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s', email): return await flash('error', 'Email already taken by another user.', 'register') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 <= len(passwd_txt) <= 32: return await flash('error', 'Password must be 8-32 characters in length.', 'register') if len(set(passwd_txt)) <= 3: return await flash( 'error', 'Password must have more than 3 unique characters.', 'register') if passwd_txt.lower() in glob.config.disallowed_passwords: return await flash('error', 'That password was deemed too simple.', 'register') # TODO: add correct locking # (start of lock) pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode() pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt()) glob.cache['bcrypt'][pw_bcrypt] = pw_md5 # cache pw safe_name = utils.get_safe_name(username) # fetch the users' country if (request.headers and (ip := request.headers.get('X-Real-IP', type=str)) is not None): country = await utils.fetch_geoloc(ip)