Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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')
Exemplo n.º 4
0
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'{}'
Exemplo n.º 5
0
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')
Exemplo n.º 6
0
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')
Exemplo n.º 7
0
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')
Exemplo n.º 8
0
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')
Exemplo n.º 9
0
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:
Exemplo n.º 10
0
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')
Exemplo n.º 11
0
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')
Exemplo n.º 12
0
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)