diff --git a/api.py b/api.py index 0bf0c80..4d87af8 100644 --- a/api.py +++ b/api.py @@ -4,6 +4,7 @@ import inspect from flask import Flask, Response, jsonify, request from flask_cors import CORS +import firebase_manager import generic_executor app = Flask(__name__) @@ -44,6 +45,19 @@ def parse_and_validate_request(parameters: [str]) -> list[str]: data = request.get_json() if not data: raise Exception("Empty request body.") + if 'jwt' not in data: + raise Exception("Missing 'token' in request body. The API doesn't support anonymous access anymore.") + else: + valid, user_id = firebase_manager.verify_jwt_token(data['jwt']) + 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.") + pass for name in parameters: if name not in data: raise Exception(f"Missing parameter {name}") @@ -73,13 +87,14 @@ def dynamic_route_handler(path): route_fn = route_handlers[path] parameters = [] sig = inspect.signature(route_fn) - for param in sig.parameters.values(): parameters.append(param.name) - - mapped_parameters = parse_and_validate_request(parameters) - status: http.HTTPStatus = route_fn(*mapped_parameters) - return generic_response_maker(status) + try : + mapped_parameters = parse_and_validate_request(parameters) + status: http.HTTPStatus = route_fn(*mapped_parameters) + return generic_response_maker(status) + except Exception as e: + return generic_response_maker(http.HTTPStatus.BAD_REQUEST, str(e)) if __name__ == '__main__': diff --git a/api_sender.html b/api_sender.html index 865bc99..76ffd92 100644 --- a/api_sender.html +++ b/api_sender.html @@ -42,6 +42,7 @@ document.addEventListener('DOMContentLoaded', () => { button.addEventListener('click', async event => { const form = event.target.closest('form'); const action = button.dataset.action; + const jwt = "0"; const email = document.getElementById('accountEmail').value; const port = document.getElementById('accountPort').value; const name = document.getElementById('serverName').value; @@ -52,28 +53,28 @@ document.addEventListener('DOMContentLoaded', () => { var data = {} switch(action) { case 'AccountCreate': - data = {email, port} + data = {email, port, jwt} break; case 'AccountDelete': - data = {email, port} + data = {email, port, jwt} break; case 'ServerCreate': - data = {port, name, version} + data = {port, name, version, jwt} break; case 'ServerDelete': - data = {port, name} + data = {port, name, jwt} break; case 'ServerRun': - data = {port, name} + data = {port, name, jwt} break; case 'ServerStop': - data = {port, name} + data = {port, name, jwt} break; case 'UpdateProperty': - data = {port, name, prop, value} + data = {port, name, prop, value, jwt} break; case 'Command': - data = {port, name, command} + data = {port, name, command, jwt} break; } sendRequest(action, data) diff --git a/firebase_manager.py b/firebase_manager.py index 10bb4fa..8c28485 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -1,10 +1,13 @@ +from datetime import datetime + import jwt import firebase_admin -from firebase_admin import credentials, auth +from firebase_admin import credentials, auth, firestore +from google.api_core.exceptions import NotFound, PermissionDenied, Aborted, ResourceExhausted, OutOfRange, DataLoss cred = credentials.Certificate('servii.json') firebase_admin.initialize_app(cred) - +firestore_database = firestore.client() ''' TODO @@ -15,6 +18,7 @@ Tell the database they have now stopped running. Also ensure the program can add an additional argument to avoid this checking for scalability. ''' + def get_user_from_id(user_id): return auth.get_user(user_id) @@ -28,3 +32,44 @@ def verify_jwt_token(token): return False, None except jwt.InvalidTokenError: return False, None + + +def create_firestore(user_id: str, data: dict) -> bool: + doc_ref = firestore_database.collection('users').document(user_id) + try: + doc_ref.create(data) + return True + except (NotFound, PermissionDenied, Aborted, ResourceExhausted, + OutOfRange, DataLoss, TypeError, Exception, ValueError) as e: + log_exception_to_firestore(e, user_id, data) + return False + + +def update_firestore(user_id: str, data: dict) -> bool: + doc_ref = firestore_database.collection('users').document(user_id) + try: + doc_ref.update(data) + return True + except (NotFound, PermissionDenied, Aborted, ResourceExhausted, + OutOfRange, DataLoss, TypeError, Exception, ValueError) as e: + log_exception_to_firestore(e, user_id, data) + return False + + +def log_exception_to_firestore(exception: Exception = None, user_id: str = None, data: dict = None): + new_id: str = datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z%z') + log_entry = { + 'exception_name': str(type(exception).__name__), + 'exception': str(exception) if exception else 'No exception', + 'user_id': str(user_id) if user_id else 'No user_id', + 'data': str(data) if data else 'No data provided', + } + try: + firestore_database.collection('firebase.logs').document(new_id).create(log_entry) + print("Log entry added successfully.") + except Exception as e: + print(f"Failed to add log entry: {e}") + + +if __name__ == "__main__": + pass diff --git a/generic_executor.py b/generic_executor.py index abacf71..9d45646 100644 --- a/generic_executor.py +++ b/generic_executor.py @@ -1,5 +1,6 @@ from server_mc_manager import MinecraftServerManager from http import HTTPStatus + import file_manager mc_manager: MinecraftServerManager = MinecraftServerManager() @@ -90,3 +91,6 @@ def run_command(port: str, command: str) -> HTTPStatus: print(f"Error executing command: {e}") return HTTPStatus.INTERNAL_SERVER_ERROR + +if __name__ == "__main__": + pass diff --git a/requirements.txt b/requirements.txt index 13fc6de..d073e04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ +firebase_admin==6.5.0 Flask==3.0.3 Flask_Cors==4.0.1 -plotly==5.22.0 -psutil==5.9.8 +PyJWT==2.8.0