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 @@