async def login(): start = time.time() headers = request.headers # request headers, used for things such as user ip and agent if 'User-Agent' not in headers or headers['User-Agent'] != 'osu!': # request isn't sent from osu client, return html message = f"{pyfiglet.figlet_format(f'Asahi v{glob.version}')}\n\ntsunyoku attempts bancho v2, gone right :sunglasses:" return Response(message, mimetype='text/plain') if 'osu-token' not in headers: # sometimes a login request will be a re-connect attempt, in which case they will already have a token, if not: login the user data = await request.data # request data, used to get info such as username to login the user if len(info := data.decode().split('\n')[:-1]) != 3: # format data so we can use it easier & also ensure it is valid at the same time resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old | i assume that is the only valid reason for this to happen resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return resp if len(cinfo := info[2].split('|')) != 5: # format client data (hash, utc etc.) & ensure it is valid resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return resp
def restrict(self) -> None: # TODO: reason self.priv &= ~Privileges.Normal glob.db.execute('UPDATE users SET priv = %s WHERE id = %s', [int(self.priv), self.id]) if self in glob.players: # If user is online, notify and log them out. # XXX: If you want to lock the player's # client, you can send -3 rather than -1. self.enqueue(packets.userID(-1)) self.enqueue( packets.notification('Your account has been banned.\n\n' 'If you believe this was a mistake or ' 'have waited >= 2 months, you can appeal ' 'using the appeal form on the website.')) printlog(f'Restricted {self}.', Ansi.CYAN)
async def ban(self, admin: 'Player', reason: str) -> None: self.priv &= ~Privileges.Normal await glob.db.execute('UPDATE users SET priv = %s WHERE id = %s', [int(self.priv), self.id]) log_msg = f'{admin} banned for "{reason}".' await glob.db.execute( 'INSERT INTO logs (`from`, `to`, `msg`, `time`) ' 'VALUES (%s, %s, %s, NOW())', [admin.id, self.id, log_msg]) if self in glob.players: # if user is online, notify and log them out. # XXX: if you want to lock the player's # client, you can send -3 rather than -1. self.enqueue(packets.userID(-1)) self.enqueue( packets.notification('Your account has been banned.\n\n' 'If you believe this was a mistake or ' 'have waited >= 2 months, you can appeal ' 'using the appeal form on the website.')) log(f'Banned {self}.', Ansi.CYAN)
# and token in a tuple - we need both for our response. if len(s := origin.decode().split('\n')[:-1]) != 3: return username = s[0] login_time = time.time() if p := await glob.players.get(name=username): if (login_time - p.last_recv_time) > 10: # if the current player obj online hasn't # pinged the server in > 10 seconds, log # them out and login the new user. await p.logout() else: # the user is currently online, send back failure. data = packets.userID(-1) + \ packets.notification('User already logged in.') return data, 'no' if 'ainu' in headers: if not (t := await glob.players.get(name=username, sql=True)): return f'"{username}" not found.' reason = 'Cheat client found.' await t.ban(p, reason) pw_md5 = s[1].encode() if len(s := s[2].split('|')) != 5: return packets.userID(-2), 'no'
if not (r := regexes.osu_ver.match(client_info[0])): return # invalid request # quite a bit faster than using dt.strptime. osu_ver = dt(year=int(r['ver'][0:4]), month=int(r['ver'][4:6]), day=int(r['ver'][6:8])) # disallow the login if their osu! client is older # than two months old, forcing an update re-check. # NOTE: this is disabled on debug since older clients # can sometimes be quite useful when testing. if not glob.config.debug: if osu_ver < (dt.now() - td(60)): return (packets.versionUpdateForced() + packets.userID(-2)), 'no' # ensure utc_offset is a number (negative inclusive). if not _isdecimal(client_info[1], _negative=True): return # invalid request utc_offset = int(client_info[1]) #display_city = client_info[2] == '1' # Client hashes contain a few values useful to us. # [0]: md5(osu path) # [1]: adapters (network physical addresses delimited by '.') # [2]: md5(adapters) # [3]: md5(uniqueid) (osu! uninstall id) # [4]: md5(uniqueid2) (disk signature/serial num) if len(client_hashes := client_info[3].split(':')[:-1]) != 5:
# client sends a request without an osu-token. async def login(origin: bytes, ip: str) -> tuple[bytes, str]: # login is a bit special, we return the response bytes # and token in a tuple - we need both for our response. if len(s := origin.decode().split('\n')[:-1]) != 3: return if p := await glob.players.get_by_name(username := s[0]): if (time.time() - p.last_recv_time) > 10: # if the current player obj online hasn't # pinged the server in > 10 seconds, log # them out and login the new user. await p.logout() else: # the user is currently online, send back failure. data = packets.userID(-1) + \ packets.notification('User already logged in.') return data, 'no' del p pw_hash = s[1].encode() if len(s := s[2].split('|')) != 5: return if not (r := regexes.osu_ver.match(s[0])): # invalid client version? return packets.userID(-2), 'no'
# and token in a tuple - we need both for our response. if len(s := origin.decode().split('\n')[:-1]) != 3: return username = s[0] login_time = time.time() if p := await glob.players.get(name=username): if (login_time - p.last_recv_time) > 10: # if the current player obj online hasn't # pinged the server in > 10 seconds, log # them out and login the new user. await p.logout() else: # the user is currently online, send back failure. data = packets.userID(-1) + \ packets.notification('User already logged in.') return data, 'no' del p pw_md5 = s[1].encode() if len(s := s[2].split('|')) != 5: return packets.userID(-2), 'no' if not (r := regexes.osu_ver.match(s[0])): # invalid client version? return packets.userID(-2), 'no'
resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return resp if len(cinfo := info[2].split('|')) != 5: # format client data (hash, utc etc.) & ensure it is valid resp = await make_response(packets.userID(-2)) # -2 userid informs client it is too old resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return resp username = info[0] pw = info[1].encode() # password in md5 form, we will use this to compare against db's stored bcrypt later user = await glob.db.fetch('SELECT id, pw, country, name FROM users WHERE name = %s', [username]) if not user: # ensure user actually exists before attempting to do anything else if glob.config.debug: log(f'User {username} does not exist. | Time Elapsed: {(time.time() - start) * 1000:.2f}ms', Ansi.LRED) resp = await make_response(packets.userID(-1)) # -1 userid informs client of an auth error resp.headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return resp bcache = glob.cache['bcrypt'] # get our cached bcrypts to potentially enhance speed pw_bcrypt = user['pw'].encode() if pw_bcrypt in bcache: if pw != bcache[pw_bcrypt]: # compare provided md5 with the stored (cached) bcrypt to ensure they have provided the correct password if glob.config.debug: log(f"{username}'s login attempt failed: provided an incorrect password | Time Elapsed (with cached bcrypt): {(time.time() - start) * 1000:.2f}ms", Ansi.LRED) resp = await make_response(packets.userID(-1)) resp.headers['cho-token'] = 'no' return resp ub = True else: if not bcrypt.checkpw(pw, pw_bcrypt): # compare provided md5 with the stored bcrypt to ensure they have provided the correct password
async def login(): headers = request.headers # request headers, used for things such as user ip and agent if 'User-Agent' not in headers or headers['User-Agent'] != 'osu!': # request isn't sent from osu client, return nothing return if 'osu-token' not in headers: # sometimes a login request will be a re-connect attempt, in which case they will already have a token, if not: login the user data = await request.data # request data, used to get info such as username to login the user info = data.decode().split( '\n')[:-1] # format data so we can use it easier username = info[0] pw = info[1].encode( ) # password in md5 form, we will use this to compare against db's stored bcrypt later user = await glob.db.fetch( 'SELECT id, pw, country, name FROM users WHERE name = %s', [username]) if not user: # ensure user actually exists before attempting to do anything else log(f'User {username} does not exist.', Ansi.LRED) resp = await make_response(packets.userID(-1)) resp.headers['cho-token'] = 'no' return resp bcache = glob.cache[ 'bcrypt'] # get our cached bcrypts to potentially enhance speed pw_bcrypt = user['pw'].encode() if pw_bcrypt in bcache: if pw != bcache[ pw_bcrypt]: # compare provided md5 with the stored (cached) bcrypt to ensure they have provided the correct password log( f"{username}'s login attempt failed: provided an incorrect password", Ansi.LRED) resp = await make_response(packets.userID(-1)) resp.headers['cho-token'] = 'no' return resp else: if not bcrypt.checkpw( pw, pw_bcrypt ): # compare provided md5 with the stored bcrypt to ensure they have provided the correct password log( f"{username}'s login attempt failed: provided an incorrect password", Ansi.LRED) resp = await make_response(packets.userID(-1)) resp.headers['cho-token'] = 'no' return resp bcache[pw_bcrypt] = pw # cache pw for future token = uuid.uuid4() # generate token for client to use as auth ucache = glob.cache['user'] if str(token) not in ucache: ucache[str(token)] = user[ 'id'] # cache token to use outside of this request data = bytearray(packets.userID( user['id'])) # initiate login by providing the user's id data += packets.protocolVersion(19) # no clue what this does data += packets.banchoPrivileges( 1 << 4) # force priv to developer for now data += ( packets.userPresence(user) + packets.userStats(user) ) # provide user & other user's presence/stats (for f9 + user stats) data += packets.notification( f'Welcome to Asahi v{glob.version}' ) # send notification as indicator they've logged in iguess data += packets.channelInfoEnd() # no clue what this does either resp = await make_response(bytes(data)) resp.headers['cho-token'] = token log(f'{username} successfully logged in.', Ansi.GREEN) return resp # if we have made it this far then it's a reconnect attempt with token already provided user_token = headers['osu-token'] # client-provided token tcache = glob.cache[ 'user'] # get token/userid cache to see if we need to relog the user or not if user_token not in tcache: # user is logged in but token is not found? most likely a restart so we force a reconnection return packets.restartServer(0) user = await glob.db.fetch( 'SELECT id, pw, country, name FROM users WHERE id = %s', [tcache[user_token]]) body = await request.body # handle any packets the client has sent | doesn't really work **for now** for packet in BanchoPacketReader(body, glob.packets): await packet.handle(user) log(f'Handled packet {packet}') resp = await make_response(b'') resp.headers['Content-Type'] = 'text/html; charset=UTF-8' # ? return resp
# client sends a request without an osu-token. def login(origin: bytes, ip: str) -> Tuple[bytes, str]: # Login is a bit special, we return the response bytes # and token in a tuple - we need both for our response. s = origin.decode().split('\n') if p := glob.players.get_by_name(username := s[0]): if (time() - p.ping_time) > 20: # If the current player obj online hasn't # pinged the server in > 20 seconds, log # them out and login the new user. p.logout() else: # User is currently online, send back failure. return packets.notification('User already logged in.') \ + packets.userID(-1), 'no' del p pw_hash = s[1].encode() s = s[2].split('|') build_name = s[0] if not s[1].replace('-', '', 1).isnumeric(): return packets.userID(-1), 'no' utc_offset = int(s[1]) display_city = s[2] == '1' # TODO: use these