From bd1f5f51e00897ef2864eefa0307d00d85df0ff9 Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Thu, 19 Sep 2024 16:32:45 +0200 Subject: [PATCH 1/7] [+] Enabled command block globally --- firebase_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase_manager.py b/firebase_manager.py index cb73a4f..929634b 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -130,7 +130,7 @@ def create_server(user_id: str, server_name: str, version: str, port: int, frame "pvp": "true", "onlineMode": "true", "maxPlayers": "20", - "enableCommandBlock": "false"}) + "enableCommandBlock": "true"}) def get_server_field(user_id: str, name: str, field_name: str) -> Union[str, bool, None]: From bf49e5c0385188793b8c880695fc185529759565 Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 09:59:00 +0200 Subject: [PATCH 2/7] [+] Enabled command block globally --- servers/shared/server.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers/shared/server.properties b/servers/shared/server.properties index ac91754..96a0385 100644 --- a/servers/shared/server.properties +++ b/servers/shared/server.properties @@ -9,7 +9,7 @@ rcon.port=25575 gamemode=survival server-port=7878 allow-nether=true -enable-command-block=false +enable-command-block=true enable-rcon=false sync-chunk-writes=true enable-query=false From 29b42be411e7b8cd256269914ddf33b758085e1a Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 09:59:21 +0200 Subject: [PATCH 3/7] [+] Added a longer scheduler. Soon will be implemented a modpack update check. --- app.py | 8 ++++++-- generic_executor.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index f7aba6c..4ad3101 100644 --- a/app.py +++ b/app.py @@ -188,11 +188,15 @@ app.register_blueprint(apiBP) if __name__ == '__main__': parser = argparse.ArgumentParser(description="Background Scheduler") - parser.add_argument('--interval', type=int, default=10, help="Interval in minutes") + (parser.add_argument + ('--servers-interval', type=int, default=10, help="Interval to check for idle servers. (in minutes)")) + (parser.add_argument + ('--modpacks-interval', type=int, default=48, help="Interval to check for modpack updates. (in hours)")) args = parser.parse_args() scheduler = BackgroundScheduler() - scheduler.add_job(generic_executor.scheduled_actions, 'interval', minutes=args.interval) + scheduler.add_job(generic_executor.scheduled_actions_short, 'interval', minutes=args.servers_interval) + scheduler.add_job(generic_executor.scheduled_actions_long, 'interval', hours=args.modpacks_interval) scheduler.start() run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False) diff --git a/generic_executor.py b/generic_executor.py index 0cd4ef4..71e5712 100644 --- a/generic_executor.py +++ b/generic_executor.py @@ -328,9 +328,13 @@ def run_command(user: UserRecord, command: str, name: str) -> tuple[HTTPStatus, return HTTPStatus.INTERNAL_SERVER_ERROR, f"Error executing command: {command} || {str(e)}" -def scheduled_actions() -> None: +def scheduled_actions_short() -> None: mc_manager.check_servers_idle() +def scheduled_actions_long() -> None: + pass + + if __name__ == "__main__": pass From 874236d09ee0ba0fe7cdb5c316f73f6c1dff44b7 Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 11:38:54 +0200 Subject: [PATCH 4/7] [~] Tiny coding style update --- app.py | 4 ++-- firebase_manager.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 4ad3101..6861aca 100644 --- a/app.py +++ b/app.py @@ -189,9 +189,9 @@ app.register_blueprint(apiBP) if __name__ == '__main__': parser = argparse.ArgumentParser(description="Background Scheduler") (parser.add_argument - ('--servers-interval', type=int, default=10, help="Interval to check for idle servers. (in minutes)")) + ('--servers-interval', type=int, default=10, help="Interval to check for idle servers. (minutes)")) (parser.add_argument - ('--modpacks-interval', type=int, default=48, help="Interval to check for modpack updates. (in hours)")) + ('--modpacks-interval', type=int, default=48, help="Interval to check for modpack updates. (hours)")) args = parser.parse_args() scheduler = BackgroundScheduler() diff --git a/firebase_manager.py b/firebase_manager.py index 929634b..043d49b 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -183,7 +183,8 @@ def set_servers_not_running(): for server_doc in server_docs: server_id = server_doc.id - firestore_database.collection(u'users').document(user_id).collection(u'servers').document(server_id).update({u'running': False}) + (firestore_database.collection(u'users') + .document(user_id).collection(u'servers').document(server_id).update({u'running': False})) print("All servers have been set to not running.") From 18acd1e08d87be37e281e46811384ebf8540248f Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 11:41:16 +0200 Subject: [PATCH 5/7] [~] Excluded modpacks from git upload --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2bbdcb7..8e6d206 100644 --- a/.gitignore +++ b/.gitignore @@ -163,6 +163,8 @@ cython_debug/ /__pycache__/ /servers/bukkit/ +/server/fabric/ +/servers/forge/ /servers/paper/ /servers/spigot/ /users/ From 9ad1bdaff9663b01bd75a5bd0a4635a60eaa3bbc Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 15:54:41 +0200 Subject: [PATCH 6/7] [+] Added super-secure-token authentication Now uses firebase complete tokens; it verifies the signature and integrity of the token, the origin of the project the token was issued for, the secret key, and finally the sub before verifying the account. --- api_sender.html | 39 ++++++++++++++++++++++----------------- app.py | 37 ++++++++++++++++++------------------- firebase_manager.py | 5 ++++- unit_test.py | 6 ++++++ 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/api_sender.html b/api_sender.html index 08d2c91..0b7680f 100644 --- a/api_sender.html +++ b/api_sender.html @@ -107,6 +107,8 @@ document.addEventListener('DOMContentLoaded', () => { const uploadForm = document.getElementById('uploadForm'); const messageDiv = document.getElementById('message'); + const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODM5NTUwLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4Mzk1NTAsImV4cCI6MTcyNjg0MzE1MCwiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.Aa3mdAmOzYET_QsGk2-QKxLGhxtXGfyAcTRnnM6cPGx0UJeSoQ-EhMIgK7HDiLVni_eMHbnwMSeEXDHEpsCWosm6e3e96zwMU3GXI1nowcnZ3CYTDH8jDCs2-6_ODomZtT2S1Lp3fD7IoSD4tDGFdo9kZNyuFGApTHhFHNAyHvfBqGL_c0c71Gfh-6ywl5C8nc07YPVbYGJu6GrS28L1vOjRSkl89Xm7o6atf38YWYWwg84QsrugRlF7Nz6yZJf7cjRY5x2guilqxrWVCWhlLiCMqFhe4oIW3BL7s3AfUC6U7DvlTyGwZJoN3fUr7V1Q5xloqSz7dcexRe1YkXXrCA"; + // File Upload functionality uploadForm.addEventListener('submit', async event => { @@ -129,6 +131,7 @@ document.addEventListener('DOMContentLoaded', () => { try { const response = await fetch('http://localhost:3000/Upload', { method: 'POST', + headers: {'SST': token}, body: formData }); @@ -157,7 +160,8 @@ document.addEventListener('DOMContentLoaded', () => { button.addEventListener('click', async event => { event.preventDefault(); const action = button.dataset.action; - const token = "MpkbDMOO8PQddQgB5VgBQdTMWF53"; + //const token = "dhmNGJYaVzNkKWgMAEOoAjaPWdc2"; + const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODM5NTUwLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4Mzk1NTAsImV4cCI6MTcyNjg0MzE1MCwiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.Aa3mdAmOzYET_QsGk2-QKxLGhxtXGfyAcTRnnM6cPGx0UJeSoQ-EhMIgK7HDiLVni_eMHbnwMSeEXDHEpsCWosm6e3e96zwMU3GXI1nowcnZ3CYTDH8jDCs2-6_ODomZtT2S1Lp3fD7IoSD4tDGFdo9kZNyuFGApTHhFHNAyHvfBqGL_c0c71Gfh-6ywl5C8nc07YPVbYGJu6GrS28L1vOjRSkl89Xm7o6atf38YWYWwg84QsrugRlF7Nz6yZJf7cjRY5x2guilqxrWVCWhlLiCMqFhe4oIW3BL7s3AfUC6U7DvlTyGwZJoN3fUr7V1Q5xloqSz7dcexRe1YkXXrCA"; const framework = document.getElementById('serverFramework').value; const subdomain = document.getElementById('subdomain').value; const email = document.getElementById('accountEmail').value; @@ -172,58 +176,59 @@ document.addEventListener('DOMContentLoaded', () => { switch(action) { case 'FetchServers': - data = {token}; + data = {}; break; case 'FetchLogs': - data = {token, name}; + data = {name}; break; case 'FetchPlayersStatus': - data = {token, name}; + data = { name}; break; case 'FetchDirContent': - data = {token, name}; + data = { name}; break; case 'AccountCreate': - data = {email, port, token}; + data = {email, port, }; break; case 'AccountDelete': - data = {subdomain, port, token}; + data = {subdomain, port, }; break; case 'ServerCreate': - data = {port, name, version, token, framework}; + data = {port, name, version, framework}; break; case 'ServerDelete': - data = {port, name, token}; + data = {port, name, }; break; case 'ServerRun': - data = {port, name, token}; + data = {port, name, }; break; case 'ServerStop': - data = {port, name, token}; + data = {port, name, }; break; case 'UpdateProperties': - data = {port, name, props, value, token}; + data = {port, name, props, value, }; break; case 'Command': - data = {port, name, command, token}; + data = {port, name, command, }; break; case 'SetSubdomain': - data = {token, subdomain}; + data = {subdomain}; break; } - sendRequest(action, data) + sendRequest(action, data, token) .then(response => response.text()) .then(data => alert(`Response: ${data}`)) .catch(error => console.error('Error:', error)); }); }); - function sendRequest(endpoint, payload) { + function sendRequest(endpoint, payload, token) { return fetch(`http://localhost:3000/${endpoint}`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'SST': token, }, body: JSON.stringify(payload) }); diff --git a/app.py b/app.py index 6861aca..d5fe54c 100644 --- a/app.py +++ b/app.py @@ -55,28 +55,25 @@ TODO : replace 53 by the given statement. ''' -def authenticate_request(data: dict): - if 'token' not in data: - raise Exception("Missing 'token' in request body. The API doesn't support anonymous access anymore.") +def authenticate_request(token: str): + valid, user_id = firebase_manager.verify_jwt_token(token) + if not valid: + raise Exception("Invalid JWT token.") else: - valid, user_id = True, data['token'] - if not valid: - raise Exception("Invalid JWT token.") - else: - user = firebase_manager.get_user_from_id(user_id) - if not user: - raise Exception("User not found.") - if not user.email_verified: - raise Exception("Your google account isn't verified yet.") + user = firebase_manager.get_user_from_id(user_id) + if not user: + raise Exception("User not found.") + if not user.email_verified: + raise Exception("Your google account isn't verified yet.") return user -def parse_and_validate_request(parameters: list[str]) -> Union[list[str], None]: +def parse_and_validate_request(parameters: list[str], token: str) -> Union[list[str], None]: fn_args = [] data = request.get_json() if not data: raise Exception("Empty request body.") - user = authenticate_request(data) + user = authenticate_request(token) data['user'] = user for name in parameters: if name not in data: @@ -115,7 +112,10 @@ def dynamic_route_handler(path): for param in sig.parameters.values(): parameters.append(param.name) try: - mapped_parameters = parse_and_validate_request(parameters) + token: str or None = request.headers.get('SST') + if not token: + return generic_response_maker(http.HTTPStatus.BAD_REQUEST, "No provided token.") + mapped_parameters = parse_and_validate_request(parameters, token) if mapped_parameters is None: return generic_response_maker(http.HTTPStatus.BAD_REQUEST) status, message = route_fn(*mapped_parameters) @@ -130,7 +130,7 @@ def dynamic_route_handler(path): @apiBP.route('/Upload', methods=['POST']) def upload(): form = request.form - token: str or None = form.get('token') + token: str or None = request.headers.get('SST') name: str or None = form.get('name') files: ImmutableMultiDict[str, FileStorage] = request.files @@ -144,8 +144,7 @@ def upload(): if not name: raise KeyError('name') - data: dict = {'token': token} - user: UserRecord = authenticate_request(data) + user: UserRecord = authenticate_request(token) user_id: str = user.uid for _, file in files.items(): @@ -200,4 +199,4 @@ if __name__ == '__main__': scheduler.start() run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False) - api_cleanup() \ No newline at end of file + #api_cleanup() \ No newline at end of file diff --git a/firebase_manager.py b/firebase_manager.py index 043d49b..8e9ce5c 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -22,13 +22,16 @@ def get_user_from_id(user_id): def verify_jwt_token(token): try: - decoded_token = jwt.decode(token, options={"verify_signature": False}) + decoded_token = auth.verify_id_token(token, app=app, check_revoked=True) user_id = decoded_token.get('sub') return True, user_id except jwt.ExpiredSignatureError: return False, None except jwt.InvalidTokenError: return False, None + except Exception as e: + log_exception_to_firestore(e, None, {"user": None, "error-step": "auth"}) + return False, None def fetch_port() -> Union[int, None]: diff --git a/unit_test.py b/unit_test.py index 88cf05a..cccf392 100644 --- a/unit_test.py +++ b/unit_test.py @@ -2,6 +2,9 @@ import os import shutil from typing import Callable, Union +from firebase_admin import auth +from jwt.api_jws import decode_complete + import firebase_manager import server_mc_manager from generic_executor import mc_manager @@ -41,4 +44,7 @@ if __name__ == '__main__': listdir("/home/hapso/Desktop/Personal/servii-backend/servers/paper"), "/home/hapso/Desktop/Personal/servii-backend/servers/paper") ''' + token: str = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwM2E2ODg3YWU3ZjNkMTAyNzNjNjRiMDU3ZTY1MzE1MWUyOTBiNzIiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiSXR6IFNlbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NLOElVZHlzcW5kZkxxNFc5ZWlRNlpjTFpkbUVDX29UNXBVaURGQ2gzY2VDZTZXSGxvWD1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9zZXJ2aS1lNjcwNSIsImF1ZCI6InNlcnZpLWU2NzA1IiwiYXV0aF90aW1lIjoxNzI2ODI1ODEzLCJ1c2VyX2lkIjoiTXBrYkRNT084UFFkZFFnQjVWZ0JRZFRNV0Y1MyIsInN1YiI6Ik1wa2JETU9POFBRZGRRZ0I1VmdCUWRUTVdGNTMiLCJpYXQiOjE3MjY4MjU4MTMsImV4cCI6MTcyNjgyOTQxMywiZW1haWwiOiJ0ZWNobm9wcm9kMjU0NTg1NjVAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZ29vZ2xlLmNvbSI6WyIxMTQ0Mzk0NjEyOTM5OTE1NzU5MTgiXSwiZW1haWwiOlsidGVjaG5vcHJvZDI1NDU4NTY1QGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.K1xpVX3S83b8AIUShE33bcTcN0XaxA1Uh4oe-sjVE51BGbrpHWw5SNhMzBAiaadPf_mET6-85WdmTYTFcXoDhiC5YhXDfu4fsyQq3K-zwi0ZDNOB0A3Xa7kdsCTwYSxb1DAq3zUZSLH6OHq6af1mGFfsH1WmQ9FT34ULgiBV4W1IHH4PtuYIc1kszgNAxU2lJehi2YsCYB2OZ47VohtOpfYtisJzA9er-L9WmtrMKokxTuCXAuhKIZwb0xAr_ZkZSDx8J1uhGPnPPMeID-7cXXg_tcvCv_WSlTXioQ20hG8J4Lq8Xz1ldQmbcdXl_owqty5m3MdIDiDvP8C9Oc_yLg" + decoded = auth.verify_id_token(token, app=firebase_manager.app, check_revoked=True) + print(decoded) pass From 8d5993e84bf258d6422ff9ed1a86f8b824f2b1bd Mon Sep 17 00:00:00 2001 From: Charles Le Maux Date: Fri, 20 Sep 2024 16:18:48 +0200 Subject: [PATCH 7/7] [+] Revert api_cleanup --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index d5fe54c..9feb7ef 100644 --- a/app.py +++ b/app.py @@ -199,4 +199,4 @@ if __name__ == '__main__': scheduler.start() run_simple('0.0.0.0', 3000, app, use_debugger=False, use_reloader=False) - #api_cleanup() \ No newline at end of file + api_cleanup() \ No newline at end of file