diff --git a/CHANGELOG.md b/CHANGELOG.md index d81afb9f..dfd883a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,18 @@ ## --- [4.2.2] - 2023/TBD ### New features TBD +### Refactor +- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670)) ### Bug fixes -TBD +- Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664)) ### Tweaks -TBD +- Homogenize Panel logos/branding ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/666)) +- Retain previous tab when revisiting server details page (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667)) +- Add server name tag in panel header (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667)) +- Setup logging for panel authentication attempts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669)) +- Update minimum password length from 6 to 8, and unrestrict maximum password length ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669)) +- Fix Unban button failing to pardon users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/671)) +- Fix stack in API error handling ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/674)) ### Lang TBD

diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index 92a68d81..5c4df789 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -38,6 +38,7 @@ class ServersController(metaclass=Singleton): self.servers_list = [] self.stats = Stats(self.helper, self) self.ws = WebSocketManager() + self.server_subpage = {} # ********************************************************************************** # Generic Servers Methods diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py index ed53ad61..87cc513c 100644 --- a/app/classes/controllers/users_controller.py +++ b/app/classes/controllers/users_controller.py @@ -45,8 +45,7 @@ class UsersController: }, "password": { "type": "string", - "maxLength": 20, - "minLength": 6, + "minLength": 8, "examples": ["crafty"], "title": "Password", }, diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 23586696..27104b62 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -78,6 +78,37 @@ class Controller: self.first_login = False self.cached_login = self.management.get_login_image() self.support_scheduler.start() + try: + with open( + os.path.join(os.path.curdir, "logs", "auth_tracker.log"), + "r", + encoding="utf-8", + ) as f: + self.auth_tracker = json.load(f) + except: + self.auth_tracker = {} + + def log_attempt(self, remote_ip, username): + remote = self.auth_tracker.get(str(remote_ip), None) + if remote: + remote["names"].append(username) + remote["attempts"] += 1 + remote["times"].append(datetime.now().strftime("%d/%m/%Y %H:%M:%S")) + self.auth_tracker[str(remote_ip)] = remote + else: + self.auth_tracker[str(remote_ip)] = { + "names": [username], + "attempts": 1, + "times": [datetime.now().strftime("%d/%m/%Y %H:%M:%S")], + } + + def write_auth_tracker(self): + with open( + os.path.join(os.path.curdir, "logs", "auth_tracker.log"), + "w", + encoding="utf-8", + ) as f: + json.dump(self.auth_tracker, f, indent=4) @staticmethod def check_system_user(): diff --git a/app/classes/shared/tasks.py b/app/classes/shared/tasks.py index 0402c587..ff20e7ec 100644 --- a/app/classes/shared/tasks.py +++ b/app/classes/shared/tasks.py @@ -201,6 +201,13 @@ class TasksManager: id="update_watcher", start_date=datetime.datetime.now(), ) + self.scheduler.add_job( + self.controller.write_auth_tracker, + "interval", + minutes=5, + id="auth_tracker_write", + start_date=datetime.datetime.now(), + ) # self.scheduler.add_job( # self.scheduler.print_jobs, "interval", seconds=10, id="-1" # ) diff --git a/app/classes/web/api_handler.py b/app/classes/web/api_handler.py deleted file mode 100644 index 34b09ee8..00000000 --- a/app/classes/web/api_handler.py +++ /dev/null @@ -1,446 +0,0 @@ -from datetime import datetime -import logging -import re - -from app.classes.controllers.crafty_perms_controller import EnumPermissionsCrafty -from app.classes.controllers.server_perms_controller import EnumPermissionsServer -from app.classes.web.base_handler import BaseHandler -from app.classes.models.management import DatabaseShortcuts - -logger = logging.getLogger(__name__) -bearer_pattern = re.compile(r"^Bearer", flags=re.IGNORECASE) - - -class ApiHandler(BaseHandler): - def return_response(self, status: int, data: dict): - # Define a standardized response - self.set_status(status) - self.write(data) - - def check_xsrf_cookie(self): - # Disable CSRF protection on API routes - pass - - def access_denied(self, user, reason=""): - if reason: - reason = " because " + reason - logger.info( - "User %s from IP %s was denied access to the API route " - + self.request.path - + reason, - user, - self.get_remote_ip(), - ) - self.finish( - self.return_response( - 403, - { - "error": "ACCESS_DENIED", - "info": "You were denied access to the requested resource", - }, - ) - ) - - def authenticate_user(self) -> bool: - self.permissions = { - "Commands": EnumPermissionsServer.COMMANDS, - "Terminal": EnumPermissionsServer.TERMINAL, - "Logs": EnumPermissionsServer.LOGS, - "Schedule": EnumPermissionsServer.SCHEDULE, - "Backup": EnumPermissionsServer.BACKUP, - "Files": EnumPermissionsServer.FILES, - "Config": EnumPermissionsServer.CONFIG, - "Players": EnumPermissionsServer.PLAYERS, - "Server_Creation": EnumPermissionsCrafty.SERVER_CREATION, - "User_Config": EnumPermissionsCrafty.USER_CONFIG, - "Roles_Config": EnumPermissionsCrafty.ROLES_CONFIG, - } - try: - logger.debug("Searching for specified token") - - api_token = self.get_argument("token", "") - self.api_token = api_token - if api_token is None and self.request.headers.get("Authorization"): - api_token = bearer_pattern.sub( - "", self.request.headers.get("Authorization") - ) - elif api_token is None: - api_token = self.get_cookie("token") - user_data = self.controller.users.get_user_by_api_token(api_token) - - logger.debug("Checking results") - if user_data: - # Login successful! Check perms - logger.info(f"User {user_data['username']} has authenticated to API") - - return True # This is to set the "authenticated" - logging.debug("Auth unsuccessful") - self.access_denied("unknown", "the user provided an invalid token") - return False - except Exception as e: - logger.warning("An error occured while authenticating an API user: %s", e) - self.finish( - self.return_response( - 403, - { - "error": "ACCESS_DENIED", - "info": "An error occured while authenticating the user", - }, - ) - ) - return False - - -class ServersStats(ApiHandler): - def get(self): - """Get details about all servers""" - authenticated = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - if not authenticated: - return - if user_obj["superuser"]: - raw_stats = self.controller.servers.get_all_servers_stats() - else: - raw_stats = self.controller.servers.get_authorized_servers_stats( - user_obj["user_id"] - ) - stats = [] - for rs in raw_stats: - s = {} - for k, v in rs["server_data"].items(): - if isinstance(v, datetime): - s[k] = v.timestamp() - else: - s[k] = v - stats.append(s) - - # Get server stats - # TODO Check perms - self.finish(self.write({"servers": stats})) - - -class NodeStats(ApiHandler): - def get(self): - """Get stats for particular node""" - authenticated = self.authenticate_user() - if not authenticated: - return - - # Get node stats - node_stats = self.controller.servers.stats.get_node_stats() - self.return_response(200, {"code": node_stats["node_stats"]}) - - -class SendCommand(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - command = self.get_argument("command", default=None, strip=True) - server_id = self.get_argument("id") - if command: - server = self.controller.servers.get_server_instance_by_id(server_id) - if server.check_running: - server.send_command(command) - self.return_response(200, {"run": True}) - else: - self.return_response(200, {"error": "SER_NOT_RUNNING"}) - else: - self.return_response(200, {"error": "NO_COMMAND"}) - - -class ServerBackup(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if not self.permissions[ - "Backup" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - server.backup_server() - - self.return_response(200, {"code": "SER_BAK_CALLED"}) - - -class StartServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied("unknown") - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - if not server.check_running(): - self.controller.management.send_command( - user_obj["user_id"], server_id, remote_ip, "start_server" - ) - self.return_response(200, {"code": "SER_START_CALLED"}) - else: - self.return_response(500, {"error": "SER_RUNNING"}) - - -class StopServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if ( - not user_obj["user_id"] - in self.controller.server_perms.get_server_user_list(server_id) - and not user_obj["superuser"] - ): - self.access_denied("unknown") - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - return - - server = self.controller.servers.get_server_instance_by_id(server_id) - - if server.check_running(): - self.controller.management.send_command( - user, server_id, remote_ip, "stop_server" - ) - - self.return_response(200, {"code": "SER_STOP_CALLED"}) - else: - self.return_response(500, {"error": "SER_NOT_RUNNING"}) - - -class RestartServer(ApiHandler): - def post(self): - user = self.authenticate_user() - remote_ip = self.get_remote_ip() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - server_id = self.get_argument("id") - - if not user_obj["user_id"] in self.controller.server_perms.get_server_user_list( - server_id - ): - self.access_denied("unknown") - - if not self.permissions[ - "Commands" - ] in self.controller.server_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token), server_id - ): - self.access_denied(user) - - self.controller.management.send_command( - user, server_id, remote_ip, "restart_server" - ) - self.return_response(200, {"code": "SER_RESTART_CALLED"}) - - -class CreateUser(ApiHandler): - def post(self): - user = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - user_perms = self.controller.crafty_perms.get_crafty_permissions_list( - user_obj["user_id"] - ) - if ( - not self.permissions["User_Config"] in user_perms - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if user is None: - self.access_denied("unknown") - return - - if not self.permissions[ - "User_Config" - ] in self.controller.crafty_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token) - ): - self.access_denied(user) - return - - new_username = self.get_argument("username").lower() - new_pass = self.get_argument("password") - manager = int(user_obj["user_id"]) - - if new_username: - self.controller.users.add_user( - new_username, manager, new_pass, "default@example.com", True, False - ) - - self.return_response( - 200, - { - "code": "COMPLETE", - "username": new_username, - "password": new_pass, - }, - ) - else: - self.return_response( - 500, - { - "error": "MISSING_PARAMS", - "info": "Some paramaters failed validation", - }, - ) - - -class DeleteUser(ApiHandler): - def post(self): - user = self.authenticate_user() - - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - user_perms = self.controller.crafty_perms.get_crafty_permissions_list( - user_obj["user_id"] - ) - - if ( - not self.permissions["User_Config"] in user_perms - and not user_obj["superuser"] - ): - self.access_denied("unknown") - return - - if user is None: - self.access_denied("unknown") - return - - if not self.permissions[ - "User_Config" - ] in self.controller.crafty_perms.get_api_key_permissions_list( - self.controller.users.get_api_key_by_token(self.api_token) - ): - self.access_denied(user) - return - - user_id = self.get_argument("user_id", None, True) - user_to_del = self.controller.users.get_user_by_id(user_id) - - if user_to_del["superuser"]: - self.return_response( - 500, - {"error": "NOT_ALLOWED", "info": "You cannot delete a super user"}, - ) - else: - if user_id: - self.controller.users.remove_user(user_id) - self.return_response(200, {"code": "COMPLETED"}) - - -class ListServers(ApiHandler): - def get(self): - user = self.authenticate_user() - user_obj = self.controller.users.get_user_by_api_token(self.api_token) - - if user is None: - self.access_denied("unknown") - return - - if self.api_token is None: - self.access_denied("unknown") - return - - if user_obj["superuser"]: - servers = self.controller.servers.get_all_defined_servers() - servers = [str(i) for i in servers] - else: - servers = self.controller.servers.get_authorized_servers( - user_obj["user_id"] - ) - page_servers = [] - for server in servers: - if server not in page_servers: - page_servers.append( - DatabaseShortcuts.get_data_obj(server.server_object) - ) - servers = page_servers - servers = [str(i) for i in servers] - - self.return_response( - 200, - { - "code": "COMPLETED", - "servers": servers, - }, - ) diff --git a/app/classes/web/base_handler.py b/app/classes/web/base_handler.py index 2504bc13..d8181b94 100644 --- a/app/classes/web/base_handler.py +++ b/app/classes/web/base_handler.py @@ -14,6 +14,7 @@ from app.classes.shared.translation import Translation from app.classes.shared.main_models import DatabaseShortcuts logger = logging.getLogger(__name__) +auth_log = logging.getLogger("auth") bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE) @@ -231,9 +232,16 @@ class BaseHandler(tornado.web.RequestHandler): user, ) logging.debug("Auth unsuccessful") + auth_log.error( + f"Authentication attempted from {self.get_remote_ip()}. Invalid token" + ) self.access_denied(None, "the user provided an invalid token") return None except Exception as auth_exception: + auth_log.error( + f"Authentication attempted from {self.get_remote_ip()}." + f" Error: {auth_exception}" + ) logger.debug( "An error occured while authenticating an API user:", exc_info=auth_exception, diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index f12505eb..e285d4e5 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -481,6 +481,12 @@ class PanelHandler(BaseHandler): subpage = nh3.clean(self.get_argument("subpage", "")) server_id = self.check_server_id() + # load page the user was on last + server_subpage = self.controller.servers.server_subpage.get(server_id, "") + if subpage == "" and server_subpage != "": + subpage = self.controller.servers.server_subpage.get(server_id, "") + else: + self.controller.servers.server_subpage[server_id] = subpage if server_id is None: return if not self.failed_server: diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index b7d1be9b..017ea554 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -6,6 +6,7 @@ from app.classes.models.users import HelperUsers from app.classes.web.base_handler import BaseHandler logger = logging.getLogger(__name__) +auth_log = logging.getLogger("auth") class PublicHandler(BaseHandler): @@ -96,6 +97,9 @@ class PublicHandler(BaseHandler): page_data["query"] = self.request.query if page == "login": + auth_log.info( + f"User attempting to authenticate from {self.get_remote_ip()}" + ) next_page = "/login" if self.request.query: next_page = "/login?" + self.request.query @@ -108,6 +112,12 @@ class PublicHandler(BaseHandler): user_id = HelperUsers.get_user_id_by_name(entered_username.lower()) user_data = HelperUsers.get_user_model(user_id) except: + self.controller.log_attempt(self.get_remote_ip(), entered_username) + auth_log.error( + f"User attempted to log into {entered_username}." + f" Authentication failed from remote IP {self.get_remote_ip()}" + " Users does not exist." + ) error_msg = "Incorrect username or password. Please try again." # self.clear_cookie("user") # self.clear_cookie("user_data") @@ -120,6 +130,12 @@ class PublicHandler(BaseHandler): # if we don't have a user if not user_data: + auth_log.error( + f"User attempted to log into {entered_username}. Authentication" + f" failed from remote IP {self.get_remote_ip()}" + " User does not exist." + ) + self.controller.log_attempt(self.get_remote_ip(), entered_username) error_msg = "Incorrect username or password. Please try again." # self.clear_cookie("user") # self.clear_cookie("user_data") @@ -132,6 +148,12 @@ class PublicHandler(BaseHandler): # if they are disabled if not user_data.enabled: + auth_log.error( + f"User attempted to log into {entered_username}. " + f"Authentication failed from remote IP {self.get_remote_ip()}." + " User account disabled" + ) + self.controller.log_attempt(self.get_remote_ip(), entered_username) error_msg = ( "User account disabled. Please contact " "your system administrator for more info." @@ -159,7 +181,11 @@ class PublicHandler(BaseHandler): user_data.last_ip = self.get_remote_ip() user_data.last_login = Helpers.get_time_as_string() user_data.save() - + auth_log.info( + f"{entered_username} successfully" + " authenticated and logged" + f" into panel from remote IP {self.get_remote_ip()}" + ) # log this login self.controller.management.add_to_audit_log( user_data.user_id, "Logged in", 0, self.get_remote_ip() @@ -172,6 +198,11 @@ class PublicHandler(BaseHandler): self.redirect(next_page) else: + auth_log.error( + f"User attempted to log into {entered_username}." + f" Authentication failed from remote IP {self.get_remote_ip()}" + ) + self.controller.log_attempt(self.get_remote_ip(), entered_username) # self.clear_cookie("user") # self.clear_cookie("user_data") self.clear_cookie("token") diff --git a/app/classes/web/routes/api/auth/login.py b/app/classes/web/routes/api/auth/login.py index 84ae2815..b91b295d 100644 --- a/app/classes/web/routes/api/auth/login.py +++ b/app/classes/web/routes/api/auth/login.py @@ -7,7 +7,7 @@ from app.classes.shared.helpers import Helpers from app.classes.web.base_api_handler import BaseApiHandler logger = logging.getLogger(__name__) - +auth_log = logging.getLogger("auth") login_schema = { "type": "object", "properties": { @@ -29,6 +29,10 @@ class ApiAuthLoginHandler(BaseApiHandler): try: data = json.loads(self.request.body) except json.decoder.JSONDecodeError as e: + logger.error( + "Invalid JSON schema for API" + f" login attempt from {self.get_remote_ip()}" + ) return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) @@ -36,6 +40,10 @@ class ApiAuthLoginHandler(BaseApiHandler): try: validate(data, login_schema) except ValidationError as e: + logger.error( + "Invalid JSON schema for API" + f" login attempt from {self.get_remote_ip()}" + ) return self.finish_json( 400, { @@ -52,12 +60,23 @@ class ApiAuthLoginHandler(BaseApiHandler): user_data = Users.get_or_none(Users.username == username) if user_data is None: + self.controller.log_attempt(self.get_remote_ip(), username) + auth_log.error( + f"User attempted to log into {username}." + " Authentication failed from remote IP" + f" {self.get_remote_ip()}. User not found" + ) return self.finish_json( 401, {"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None}, ) if not user_data.enabled: + auth_log.error( + f"User attempted to log into {username}." + " Authentication failed from remote" + f" IP {self.get_remote_ip()} account disabled" + ) self.finish_json( 403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None} ) @@ -67,6 +86,11 @@ class ApiAuthLoginHandler(BaseApiHandler): # Valid Login if login_result: + auth_log.info( + f"{username} successfully" + " authenticated and logged" + f" into panel from remote IP {self.get_remote_ip()}" + ) logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}") # record this login diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py index 0d46e11b..b0c773a7 100644 --- a/app/classes/web/routes/api/roles/index.py +++ b/app/classes/web/routes/api/roles/index.py @@ -110,7 +110,7 @@ class ApiRolesIndexHandler(BaseApiHandler): try: data = orjson.loads(self.request.body) - except orjson.decoder.JSONDecodeError as e: + except orjson.JSONDecodeError as e: return self.finish_json( 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} ) diff --git a/app/classes/web/tornado_handler.py b/app/classes/web/tornado_handler.py index 621c930a..f5501d31 100644 --- a/app/classes/web/tornado_handler.py +++ b/app/classes/web/tornado_handler.py @@ -22,18 +22,6 @@ from app.classes.web.default_handler import DefaultHandler from app.classes.web.routes.api.api_handlers import api_handlers from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers from app.classes.web.server_handler import ServerHandler -from app.classes.web.api_handler import ( - ServersStats, - NodeStats, - ServerBackup, - StartServer, - StopServer, - RestartServer, - CreateUser, - DeleteUser, - ListServers, - SendCommand, -) from app.classes.web.websocket_handler import WebSocketHandler from app.classes.web.static_handler import CustomStaticHandler from app.classes.web.upload_handler import UploadHandler @@ -162,17 +150,6 @@ class Webserver: (r"/ws", WebSocketHandler, handler_args), (r"/upload", UploadHandler, handler_args), (r"/status", StatusHandler, handler_args), - # API Routes V1 - (r"/api/v1/stats/servers", ServersStats, handler_args), - (r"/api/v1/stats/node", NodeStats, handler_args), - (r"/api/v1/server/send_command", SendCommand, handler_args), - (r"/api/v1/server/backup", ServerBackup, handler_args), - (r"/api/v1/server/start", StartServer, handler_args), - (r"/api/v1/server/stop", StopServer, handler_args), - (r"/api/v1/server/restart", RestartServer, handler_args), - (r"/api/v1/list_servers", ListServers, handler_args), - (r"/api/v1/users/create_user", CreateUser, handler_args), - (r"/api/v1/users/delete_user", DeleteUser, handler_args), # API Routes V2 *api_handlers(handler_args), # API Routes OpenMetrics diff --git a/app/config/logging.json b/app/config/logging.json index 99de60e4..9dd09c81 100644 --- a/app/config/logging.json +++ b/app/config/logging.json @@ -10,16 +10,17 @@ }, "schedule": { "format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s" + }, + "auth": { + "format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s" } }, - "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "commander", "stream": "ext://sys.stdout" }, - "main_file_handler": { "class": "logging.handlers.RotatingFileHandler", "formatter": "commander", @@ -50,24 +51,45 @@ "maxBytes": 10485760, "backupCount": 20, "encoding": "utf8" + }, + "auth_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "auth", + "filename": "logs/auth.log", + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" } }, - "loggers": { "": { "level": "INFO", - "handlers": ["main_file_handler", "session_file_handler"], + "handlers": [ + "main_file_handler", + "session_file_handler" + ], "propagate": false }, "tornado.access": { "level": "INFO", - "handlers": ["tornado_access_file_handler"], + "handlers": [ + "tornado_access_file_handler" + ], "propagate": false }, "apscheduler": { "level": "INFO", - "handlers": ["schedule_file_handler"], + "handlers": [ + "schedule_file_handler" + ], + "propagate": false + }, + "auth": { + "level": "INFO", + "handlers": [ + "auth_file_handler" + ], "propagate": false } } -} +} \ No newline at end of file diff --git a/app/frontend/static/assets/images/Crafty_4-0.png b/app/frontend/static/assets/images/Crafty_4-0.png index d873eedc..0671d421 100644 Binary files a/app/frontend/static/assets/images/Crafty_4-0.png and b/app/frontend/static/assets/images/Crafty_4-0.png differ diff --git a/app/frontend/static/assets/images/Crafty_4-0_Logo_square.ico b/app/frontend/static/assets/images/Crafty_4-0_Logo_square.ico index 68095bf6..7b354581 100644 Binary files a/app/frontend/static/assets/images/Crafty_4-0_Logo_square.ico and b/app/frontend/static/assets/images/Crafty_4-0_Logo_square.ico differ diff --git a/app/frontend/static/assets/images/crafty-logo-square-1024.png b/app/frontend/static/assets/images/crafty-logo-square-1024.png index 65dd0671..cec34236 100644 Binary files a/app/frontend/static/assets/images/crafty-logo-square-1024.png and b/app/frontend/static/assets/images/crafty-logo-square-1024.png differ diff --git a/app/frontend/static/assets/images/crafty-logo-square-96.png b/app/frontend/static/assets/images/crafty-logo-square-96.png index 6928e039..8c3a34b2 100644 Binary files a/app/frontend/static/assets/images/crafty-logo-square-96.png and b/app/frontend/static/assets/images/crafty-logo-square-96.png differ diff --git a/app/frontend/static/assets/images/crafty-logo-square.svg b/app/frontend/static/assets/images/crafty-logo-square.svg new file mode 100644 index 00000000..c0a686d3 --- /dev/null +++ b/app/frontend/static/assets/images/crafty-logo-square.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html index 40ee757b..cca5ef3b 100755 --- a/app/frontend/templates/base.html +++ b/app/frontend/templates/base.html @@ -82,6 +82,9 @@ +     + + {% include notify.html %} diff --git a/app/frontend/templates/panel/parts/details_stats.html b/app/frontend/templates/panel/parts/details_stats.html index 47fa501d..ad190bc3 100644 --- a/app/frontend/templates/panel/parts/details_stats.html +++ b/app/frontend/templates/panel/parts/details_stats.html @@ -248,12 +248,38 @@ $("#player-body").html(text); } - + //used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security + function getCookie(name) { + var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + return r ? r[1] : undefined; + } + const token = getCookie("_xsrf") $(window).ready(function () { console.log("ready!"); //if (webSocket) { webSocket.on('update_server_details', update_server_details); + add_server_name(); //} }); + async function add_server_name(){ + let res = await fetch(`/api/v2/servers/${serverId}`, { + method: 'GET', + headers: { + 'X-XSRFToken': token + }, + }); + let responseData = await res.json(); + if (responseData.status === "ok") { + console.log(responseData) + $("#server-name-nav").html(`${responseData.data['server_name']}`) + $("#server-name-nav").show(); + } else { + + bootbox.alert({ + title: responseData.error, + message: responseData.error_data + }); + } + } \ No newline at end of file diff --git a/app/frontend/templates/panel/parts/server_players.html b/app/frontend/templates/panel/parts/server_players.html index df059e95..ca81f39e 100644 --- a/app/frontend/templates/panel/parts/server_players.html +++ b/app/frontend/templates/panel/parts/server_players.html @@ -69,7 +69,7 @@ Banned on {{ player['banned_on'] }} Banned by : {{ player['source'] }}
Reason : {{ player['reason'] }} - + {% end %} diff --git a/app/frontend/templates/panel/server_webhook_edit.html b/app/frontend/templates/panel/server_webhook_edit.html index e7087771..fd556833 100644 --- a/app/frontend/templates/panel/server_webhook_edit.html +++ b/app/frontend/templates/panel/server_webhook_edit.html @@ -43,8 +43,10 @@
{% end %} - + {% if data['new_webhook'] == False %} + {% end %} {% for type in data['providers'] %} {% if type != data['webhook']['webhook_type'] %} diff --git a/main.py b/main.py index a1dee302..b997bba8 100644 --- a/main.py +++ b/main.py @@ -250,6 +250,14 @@ def setup_logging(debug=True): determines whether or not the logging level is "debug" or verbose. """ logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json") + if not helper.check_file_exists( + os.path.join(os.path.curdir, "logs", "auth_tracker.log") + ): + open( + os.path.join(os.path.curdir, "logs", "auth_tracker.log"), + "a", + encoding="utf-8", + ).close() if os.path.exists(logging_config_file): # open our logging config file