Add BEAMP Server auth for public view

This commit is contained in:
Maxim Khomutov 2023-07-12 11:19:04 +03:00
parent 3701bc441c
commit eb28a4d5cf
3 changed files with 145 additions and 37 deletions

View File

@ -45,6 +45,7 @@ if args.config:
config_path = args.config
config_provider = ConfigProvider(config_path)
config = config_provider.open_config()
builtins.config = config
if config.Server['debug'] is True:
utils.set_debug_status()
log.info("Debug enabled!")
@ -67,8 +68,7 @@ log.info(i18n.config_path.format(config_path))
log.debug("Initializing BEAMP Server system...")
# Key handler..
private = ((config.Auth['key'] is None or config.Auth['key'] == "") and config.Auth['private'])
if not private:
if not config.Auth['private'] and not config.Auth['key']:
log.warn(i18n.auth_need_key)
url = "https://beammp.com/k/keys"
if shortcuts.yes_no_dialog(
@ -93,11 +93,10 @@ if not private:
ok_text=i18n.GUI_ok,
cancel_text=i18n.GUI_cancel).run()
config_provider.save_config()
if not private:
if not config.Auth['private'] and not config.Auth['key']:
log.error(i18n.auth_empty_key)
log.info(i18n.stop)
exit(1)
builtins.config = config
# Console Init
log.debug("Initializing console...")
@ -107,9 +106,6 @@ console.builtins_hook()
console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop)
console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit)
if not os.path.exists("mods"):
os.mkdir("mods")
log.debug("Initializing PluginsLoader...")
if not os.path.exists("plugins"):
os.mkdir("plugins")

View File

@ -5,16 +5,19 @@
# Licence: FPA
# (c) kuitoi.su 2023
import asyncio
import os
import time
import traceback
import zlib
from threading import Thread
import aiohttp
import uvicorn
from core import utils
from modules.WebAPISystem import app as webapp
from core.tcp_server import TCPServer
from core.udp_server import UDPServer
from modules.WebAPISystem import app as webapp
class Client:
@ -133,7 +136,7 @@ class Client:
match code:
case "H":
# Client connected
await self.tcp_send(b"Sn"+bytes(self.nick, "utf-8"))
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8"))
case "C":
# Chat
await self.tcp_send(data)
@ -144,8 +147,12 @@ class Core:
def __init__(self):
self.log = utils.get_logger("core")
self.loop = asyncio.get_event_loop()
self.run = False
self.direct = False
self.clients = {}
self.clients_counter = 0
self.mods_dir = "./mods"
self.mods_list = [0, ]
self.server_ip = config.Server["server_ip"]
self.server_port = config.Server["server_port"]
self.tcp = TCPServer
@ -154,6 +161,9 @@ class Core:
self.web_pool = webapp.data_pool
self.web_stop = None
self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0"
def get_client(self, sock=None, cid=None):
if cid:
return self.clients.get(cid)
@ -197,37 +207,132 @@ class Core:
await asyncio.sleep(1)
raise KeyboardInterrupt
# noinspection SpellCheckingInspection,PyPep8Naming
async def authenticate(self, test=False):
if config.Auth["private"] or self.direct:
if test:
self.log.info(f"Server runnig in Direct connect mode.")
self.direct = True
return
BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"]
modlist = ""
for mod in self.mods_list:
if type(mod) == int:
continue
modlist += f"/{os.path.basename(mod['path'])};"
modstotalsize = self.mods_list[0]
modstotal = len(self.mods_list) - 1
while self.run:
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
"private": config.Auth['private'], "version": self.BEAMP_version, "clientversion": self.client_major_version,
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
self.log.debug(f"Auth: data {data}")
# Sentry?
ok = False
body = {}
code = 0
for server_url in BEAM_backend:
url = "https://" + server_url + "/heartbeat"
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, data=data, headers={"api-v": "2"}) as response:
code = response.status
body = await response.json()
self.log.debug(f"Auth: code {code}, body {body}")
ok = True
break
except Exception as e:
self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`")
continue
if ok:
if not (body.get("status") is not None and
body.get("code") is not None and
body.get("msg") is not None):
self.log.error("Missing/invalid json members in backend response")
raise KeyboardInterrupt
if test:
status = body.get("status")
msg = body.get("msg")
if status == "2000":
self.log.info(f"Authenticated! {msg}")
elif status == "200":
self.log.info(f"Resumed authenticated session. {msg}")
else:
self.log.error(f"Backend REFUSED the auth key. Reason: "
f"{msg or 'Backend did not provide a reason'}")
self.log.info(f"Server still runnig, but only in Direct connect mode.")
self.direct = True
else:
self.direct = True
if test:
self.log.error("Cannot auth...")
if not config.Auth['private']:
raise KeyboardInterrupt
if test:
self.log.info(f"Server still runnig, but only in Direct connect mode.")
if test:
return ok
await asyncio.sleep(5)
async def main(self):
self.tcp = self.tcp(self, self.server_ip, self.server_port)
self.udp = self.udp(self, self.server_ip, self.server_port)
tasks = [self.tcp.start(), self.udp.start(), console.start(), self.stop_me()] # self.check_alive()
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
if config.WebAPI["enabled"]:
self.log.debug("Initializing WebAPI...")
web_thread = Thread(target=self.start_web)
web_thread.start()
self.web_thread = web_thread
self.web_stop = webapp._stop
self.log.info(i18n.start)
# TODO: Server auth
ev.call_event("on_started")
await t
# while True:
# try:
# tasks = [console.start(), self.tcp.start(), self.udp.start()] # self.check_alive()
# await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
# except Exception as e:
# await asyncio.sleep(1)
# print("Error: " + str(e))
# traceback.print_exc()
# break
# except KeyboardInterrupt:
# raise KeyboardInterrupt
try:
self.run = True
self.tcp = self.tcp(self, self.server_ip, self.server_port)
self.udp = self.udp(self, self.server_ip, self.server_port)
# WebApi Start
if config.WebAPI["enabled"]:
self.log.debug("Initializing WebAPI...")
web_thread = Thread(target=self.start_web)
web_thread.start()
self.web_thread = web_thread
self.web_stop = webapp._stop
# Mods handler
self.log.debug("Listing mods..")
if not os.path.exists(self.mods_dir):
os.mkdir(self.mods_dir)
for file in os.listdir(self.mods_dir):
path = os.path.join(self.mods_dir, file).replace("\\", "/")
if os.path.isfile(path) and path.endswith(".zip"):
size = os.path.getsize(path)
self.mods_list.append({"path": path, "size": size})
self.mods_list[0] += size
self.log.debug(f"mods_list: {self.mods_list}")
lmods = len(self.mods_list) - 1
if lmods > 0:
self.log.info(f"Loaded {lmods} mods: {round(self.mods_list[0] / MB, 2)}mb")
await self.authenticate(True)
tasks = [self.tcp.start(), self.udp.start(), console.start(),
self.stop_me(), self.authenticate(),] # self.check_alive()
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
self.log.info(i18n.start)
ev.call_event("on_started")
await t
# Wait the end.
except Exception as e:
self.log.error(f"Exception: {e}")
traceback.print_exc()
except KeyboardInterrupt:
pass
finally:
self.run = False
def start(self):
asyncio.run(self.main())
def stop(self):
self.run = False
self.log.info(i18n.stop)
asyncio.run(self.web_stop())
exit(0)

View File

@ -40,23 +40,30 @@ class Client:
class Core:
def __init__(self):
self.clients_counter: int = 0
self.log = utils.get_logger("core")
self.loop = asyncio.get_event_loop()
self.run = False
self.direct = False
self.clients = dict()
self.clients_counter: int = 0
self.mods_dir: str = "mods"
self.mods_list: list = []
self.server_ip = config.Server["server_ip"]
self.server_port = config.Server["server_port"]
self.loop = asyncio.get_event_loop()
self.tcp = TCPServer
self.udp = UDPServer
self.web_thread: Thread = None
self.web_stop: Callable = lambda: None
self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0"
def insert_client(self, client: Client) -> None: ...
def create_client(self, *args, **kwargs) -> Client: ...
async def check_alive(self) -> None: ...
@staticmethod
def start_web() -> None: ...
@staticmethod
def stop_me(self) -> None: ...
def stop_me() -> None: ...
async def authenticate(self, test=False) -> None: ...
async def main(self) -> None: ...
def start(self) -> None: ...
def stop(self) -> None: ...