Merge branch 'dev' into enhancement/crafty-loading

This commit is contained in:
amcmanu3 2023-11-29 21:24:05 -05:00
commit 9e98c97eb8
23 changed files with 203 additions and 486 deletions

View File

@ -2,10 +2,18 @@
## --- [4.2.2] - 2023/TBD ## --- [4.2.2] - 2023/TBD
### New features ### New features
TBD TBD
### Refactor
- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670))
### Bug fixes ### 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 ### 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 ### Lang
TBD TBD
<br><br> <br><br>

View File

@ -38,6 +38,7 @@ class ServersController(metaclass=Singleton):
self.servers_list = [] self.servers_list = []
self.stats = Stats(self.helper, self) self.stats = Stats(self.helper, self)
self.ws = WebSocketManager() self.ws = WebSocketManager()
self.server_subpage = {}
# ********************************************************************************** # **********************************************************************************
# Generic Servers Methods # Generic Servers Methods

View File

@ -45,8 +45,7 @@ class UsersController:
}, },
"password": { "password": {
"type": "string", "type": "string",
"maxLength": 20, "minLength": 8,
"minLength": 6,
"examples": ["crafty"], "examples": ["crafty"],
"title": "Password", "title": "Password",
}, },

View File

@ -78,6 +78,37 @@ class Controller:
self.first_login = False self.first_login = False
self.cached_login = self.management.get_login_image() self.cached_login = self.management.get_login_image()
self.support_scheduler.start() 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 @staticmethod
def check_system_user(): def check_system_user():

View File

@ -201,6 +201,13 @@ class TasksManager:
id="update_watcher", id="update_watcher",
start_date=datetime.datetime.now(), 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.add_job(
# self.scheduler.print_jobs, "interval", seconds=10, id="-1" # self.scheduler.print_jobs, "interval", seconds=10, id="-1"
# ) # )

View File

@ -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,
},
)

View File

@ -14,6 +14,7 @@ from app.classes.shared.translation import Translation
from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.main_models import DatabaseShortcuts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
auth_log = logging.getLogger("auth")
bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE) bearer_pattern = re.compile(r"^Bearer ", flags=re.IGNORECASE)
@ -231,9 +232,16 @@ class BaseHandler(tornado.web.RequestHandler):
user, user,
) )
logging.debug("Auth unsuccessful") 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") self.access_denied(None, "the user provided an invalid token")
return None return None
except Exception as auth_exception: except Exception as auth_exception:
auth_log.error(
f"Authentication attempted from {self.get_remote_ip()}."
f" Error: {auth_exception}"
)
logger.debug( logger.debug(
"An error occured while authenticating an API user:", "An error occured while authenticating an API user:",
exc_info=auth_exception, exc_info=auth_exception,

View File

@ -481,6 +481,12 @@ class PanelHandler(BaseHandler):
subpage = nh3.clean(self.get_argument("subpage", "")) subpage = nh3.clean(self.get_argument("subpage", ""))
server_id = self.check_server_id() 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: if server_id is None:
return return
if not self.failed_server: if not self.failed_server:

View File

@ -6,6 +6,7 @@ from app.classes.models.users import HelperUsers
from app.classes.web.base_handler import BaseHandler from app.classes.web.base_handler import BaseHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
auth_log = logging.getLogger("auth")
class PublicHandler(BaseHandler): class PublicHandler(BaseHandler):
@ -96,6 +97,9 @@ class PublicHandler(BaseHandler):
page_data["query"] = self.request.query page_data["query"] = self.request.query
if page == "login": if page == "login":
auth_log.info(
f"User attempting to authenticate from {self.get_remote_ip()}"
)
next_page = "/login" next_page = "/login"
if self.request.query: if self.request.query:
next_page = "/login?" + 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_id = HelperUsers.get_user_id_by_name(entered_username.lower())
user_data = HelperUsers.get_user_model(user_id) user_data = HelperUsers.get_user_model(user_id)
except: 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." error_msg = "Incorrect username or password. Please try again."
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
@ -120,6 +130,12 @@ class PublicHandler(BaseHandler):
# if we don't have a user # if we don't have a user
if not user_data: 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." error_msg = "Incorrect username or password. Please try again."
# self.clear_cookie("user") # self.clear_cookie("user")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
@ -132,6 +148,12 @@ class PublicHandler(BaseHandler):
# if they are disabled # if they are disabled
if not user_data.enabled: 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 = ( error_msg = (
"User account disabled. Please contact " "User account disabled. Please contact "
"your system administrator for more info." "your system administrator for more info."
@ -159,7 +181,11 @@ class PublicHandler(BaseHandler):
user_data.last_ip = self.get_remote_ip() user_data.last_ip = self.get_remote_ip()
user_data.last_login = Helpers.get_time_as_string() user_data.last_login = Helpers.get_time_as_string()
user_data.save() 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 # log this login
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(
user_data.user_id, "Logged in", 0, self.get_remote_ip() user_data.user_id, "Logged in", 0, self.get_remote_ip()
@ -172,6 +198,11 @@ class PublicHandler(BaseHandler):
self.redirect(next_page) self.redirect(next_page)
else: 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")
# self.clear_cookie("user_data") # self.clear_cookie("user_data")
self.clear_cookie("token") self.clear_cookie("token")

View File

@ -7,7 +7,7 @@ from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
auth_log = logging.getLogger("auth")
login_schema = { login_schema = {
"type": "object", "type": "object",
"properties": { "properties": {
@ -29,6 +29,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
try: try:
data = json.loads(self.request.body) data = json.loads(self.request.body)
except json.decoder.JSONDecodeError as e: 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( return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
) )
@ -36,6 +40,10 @@ class ApiAuthLoginHandler(BaseApiHandler):
try: try:
validate(data, login_schema) validate(data, login_schema)
except ValidationError as e: except ValidationError as e:
logger.error(
"Invalid JSON schema for API"
f" login attempt from {self.get_remote_ip()}"
)
return self.finish_json( return self.finish_json(
400, 400,
{ {
@ -52,12 +60,23 @@ class ApiAuthLoginHandler(BaseApiHandler):
user_data = Users.get_or_none(Users.username == username) user_data = Users.get_or_none(Users.username == username)
if user_data is None: 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( return self.finish_json(
401, 401,
{"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None}, {"status": "error", "error": "INCORRECT_CREDENTIALS", "token": None},
) )
if not user_data.enabled: 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( self.finish_json(
403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None} 403, {"status": "error", "error": "ACCOUNT_DISABLED", "token": None}
) )
@ -67,6 +86,11 @@ class ApiAuthLoginHandler(BaseApiHandler):
# Valid Login # Valid Login
if login_result: 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()}") logger.info(f"User: {user_data} Logged in from IP: {self.get_remote_ip()}")
# record this login # record this login

View File

@ -110,7 +110,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
try: try:
data = orjson.loads(self.request.body) data = orjson.loads(self.request.body)
except orjson.decoder.JSONDecodeError as e: except orjson.JSONDecodeError as e:
return self.finish_json( return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)} 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
) )

View File

@ -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.api.api_handlers import api_handlers
from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers from app.classes.web.routes.metrics.metrics_handlers import metrics_handlers
from app.classes.web.server_handler import ServerHandler 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.websocket_handler import WebSocketHandler
from app.classes.web.static_handler import CustomStaticHandler from app.classes.web.static_handler import CustomStaticHandler
from app.classes.web.upload_handler import UploadHandler from app.classes.web.upload_handler import UploadHandler
@ -162,17 +150,6 @@ class Webserver:
(r"/ws", WebSocketHandler, handler_args), (r"/ws", WebSocketHandler, handler_args),
(r"/upload", UploadHandler, handler_args), (r"/upload", UploadHandler, handler_args),
(r"/status", StatusHandler, 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 Routes V2
*api_handlers(handler_args), *api_handlers(handler_args),
# API Routes OpenMetrics # API Routes OpenMetrics

View File

@ -10,16 +10,17 @@
}, },
"schedule": { "schedule": {
"format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s" "format": "%(asctime)s - [Schedules] - %(levelname)s - %(message)s"
},
"auth": {
"format": "%(asctime)s - [AUTH] - %(levelname)s - %(message)s"
} }
}, },
"handlers": { "handlers": {
"console": { "console": {
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"formatter": "commander", "formatter": "commander",
"stream": "ext://sys.stdout" "stream": "ext://sys.stdout"
}, },
"main_file_handler": { "main_file_handler": {
"class": "logging.handlers.RotatingFileHandler", "class": "logging.handlers.RotatingFileHandler",
"formatter": "commander", "formatter": "commander",
@ -50,24 +51,45 @@
"maxBytes": 10485760, "maxBytes": 10485760,
"backupCount": 20, "backupCount": 20,
"encoding": "utf8" "encoding": "utf8"
},
"auth_file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "auth",
"filename": "logs/auth.log",
"maxBytes": 10485760,
"backupCount": 20,
"encoding": "utf8"
} }
}, },
"loggers": { "loggers": {
"": { "": {
"level": "INFO", "level": "INFO",
"handlers": ["main_file_handler", "session_file_handler"], "handlers": [
"main_file_handler",
"session_file_handler"
],
"propagate": false "propagate": false
}, },
"tornado.access": { "tornado.access": {
"level": "INFO", "level": "INFO",
"handlers": ["tornado_access_file_handler"], "handlers": [
"tornado_access_file_handler"
],
"propagate": false "propagate": false
}, },
"apscheduler": { "apscheduler": {
"level": "INFO", "level": "INFO",
"handlers": ["schedule_file_handler"], "handlers": [
"schedule_file_handler"
],
"propagate": false
},
"auth": {
"level": "INFO",
"handlers": [
"auth_file_handler"
],
"propagate": false "propagate": false
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 425 425">
<!-- Background -->
<rect x="0" y="0" rx="105" ry="105" width="425" height="425" style="fill: #22283A"/>
<!-- green C -->
<path fill="hsl(160, 59%, 45%)" d="M 162.5 182.5 l 38 -15 q 0 -40 -40 -40 l -95 0 q -40 0 -40 40 l 0 90 q 0 40 40 40 l 95 0 q 40 0 40 -40 l -38 -20 l 0 28 l -100 0 l 0 -108 l 100 0 z"/>
<!-- blue C -->
<path fill="hsl(192, 99%, 45%)" d="M 262.5 182.5 l -38 -15 q 0 -40 40 -40 l 95 0 q 40 0 40 40 l 0 90 q 0 40 -40 40 l -95 0 q -40 0 -40 -40 l 38 -20 l 0 28 l 100 0 l 0 -108 l -100 0 z"/>
<!-- line thing in middle of Cs -->
<path fill="hsl(266, 71%, 57%)" d="M 142.5 191.5 q -10 0 -10 10 l 0 19 q 0 10 10 10 l 140 0 q 10 0 10 -10 l 0 -19 q 0 -10 -10 -10 z"/>
</svg>

After

Width:  |  Height:  |  Size: 750 B

View File

@ -82,6 +82,9 @@
<span class="mdi mdi-chevron-double-left"></span> <span class="mdi mdi-chevron-double-left"></span>
<span class="mdi mdi-chevron-double-right"></span> <span class="mdi mdi-chevron-double-right"></span>
</button> </button>
&nbsp;&nbsp;&nbsp;
<span class="badge-pill badge-outline-primary" id="server-name-nav" style="display: none;"></span>
{% include notify.html %} {% include notify.html %}

View File

@ -248,12 +248,38 @@
$("#player-body").html(text); $("#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 () { $(window).ready(function () {
console.log("ready!"); console.log("ready!");
//if (webSocket) { //if (webSocket) {
webSocket.on('update_server_details', update_server_details); 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
});
}
}
</script> </script>

View File

@ -69,7 +69,7 @@
<td>Banned on {{ player['banned_on'] }}</td> <td>Banned on {{ player['banned_on'] }}</td>
<td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td> <td>Banned by : {{ player['source'] }} <br />Reason : {{ player['reason'] }}</td>
<td class="buttons"> <td class="buttons">
<button onclick="send_command_to_server(`pardon {{ player['name'] }} `)" type="button" class="btn btn-danger">Unban</button> <button onclick="send_command_to_server(`pardon {{ player['name'] }}`)" type="button" class="btn btn-danger">Unban</button>
</td> </td>
</tr> </tr>
{% end %} {% end %}

View File

@ -43,8 +43,10 @@
<form class="forms-sample" method="post" id="webhook_form" <form class="forms-sample" method="post" id="webhook_form"
action="/panel/edit_webhook?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['webhook']['id'] }}"> action="/panel/edit_webhook?id={{ data['server_stats']['server_id']['server_id'] }}&sch_id={{ data['webhook']['id'] }}">
{% end %} {% end %}
<select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type"> <select class="form-select form-control form-control-lg select-css" id="webhook_type" name="webhook_type">
{% if data['new_webhook'] == False %}
<option value="{{data['webhook']['webhook_type']}}">{{data['webhook']['webhook_type']}}</option> <option value="{{data['webhook']['webhook_type']}}">{{data['webhook']['webhook_type']}}</option>
{% end %}
{% for type in data['providers'] %} {% for type in data['providers'] %}
{% if type != data['webhook']['webhook_type'] %} {% if type != data['webhook']['webhook_type'] %}
<option value="{{type}}">{{type}}</option> <option value="{{type}}">{{type}}</option>

View File

@ -250,6 +250,14 @@ def setup_logging(debug=True):
determines whether or not the logging level is "debug" or verbose. determines whether or not the logging level is "debug" or verbose.
""" """
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json") 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): if os.path.exists(logging_config_file):
# open our logging config file # open our logging config file