mirror of
https://github.com/Casvt/MIND.git
synced 2026-02-19 11:54:46 -05:00
Refactored Flask error handling setup
This commit is contained in:
@@ -7,6 +7,54 @@ from backend.base.definitions import (ApiResponse, InvalidUsernameReason,
|
||||
from backend.base.logging import LOGGER
|
||||
|
||||
|
||||
class LogUnauthMindException(MindException):
|
||||
"""
|
||||
MindExceptions that inherit from this one will trigger a log of the
|
||||
requester's IP address once raised.
|
||||
"""
|
||||
|
||||
|
||||
# region REST responses
|
||||
class NotFound(MindException):
|
||||
@property
|
||||
def api_response(self) -> ApiResponse:
|
||||
return {
|
||||
'code': 404,
|
||||
'error': self.__class__.__name__,
|
||||
'result': {}
|
||||
}
|
||||
|
||||
|
||||
class BadRequest(MindException):
|
||||
@property
|
||||
def api_response(self) -> ApiResponse:
|
||||
return {
|
||||
'code': 400,
|
||||
'error': self.__class__.__name__,
|
||||
'result': {}
|
||||
}
|
||||
|
||||
|
||||
class MethodNotAllowed(MindException):
|
||||
@property
|
||||
def api_response(self) -> ApiResponse:
|
||||
return {
|
||||
'code': 405,
|
||||
'error': self.__class__.__name__,
|
||||
'result': {}
|
||||
}
|
||||
|
||||
|
||||
class InternalError(MindException):
|
||||
@property
|
||||
def api_response(self) -> ApiResponse:
|
||||
return {
|
||||
'code': 500,
|
||||
'error': self.__class__.__name__,
|
||||
'result': {}
|
||||
}
|
||||
|
||||
|
||||
# region Input/Output
|
||||
class KeyNotFound(MindException):
|
||||
"A key was not found in the input that is required to be given"
|
||||
@@ -56,7 +104,7 @@ class InvalidKeyValue(MindException):
|
||||
|
||||
|
||||
# region Auth
|
||||
class AccessUnauthorized(MindException):
|
||||
class AccessUnauthorized(LogUnauthMindException):
|
||||
"The password given is not correct"
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -74,7 +122,7 @@ class AccessUnauthorized(MindException):
|
||||
}
|
||||
|
||||
|
||||
class APIKeyInvalid(MindException):
|
||||
class APIKeyInvalid(LogUnauthMindException):
|
||||
"The API key is not correct"
|
||||
|
||||
def __init__(self, api_key: str) -> None:
|
||||
@@ -92,7 +140,7 @@ class APIKeyInvalid(MindException):
|
||||
}
|
||||
|
||||
|
||||
class APIKeyExpired(MindException):
|
||||
class APIKeyExpired(LogUnauthMindException):
|
||||
"The API key has expired"
|
||||
|
||||
def __init__(self, api_key: str) -> None:
|
||||
@@ -129,7 +177,7 @@ class OperationNotAllowed(MindException):
|
||||
}
|
||||
|
||||
|
||||
class NewAccountsNotAllowed(MindException):
|
||||
class NewAccountsNotAllowed(LogUnauthMindException):
|
||||
"It's not allowed to create a new account except for the admin"
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -266,7 +314,7 @@ class UsernameInvalid(MindException):
|
||||
}
|
||||
|
||||
|
||||
class UserNotFound(MindException):
|
||||
class UserNotFound(LogUnauthMindException):
|
||||
"The user requested can not be found"
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -14,14 +14,16 @@ from secrets import token_bytes
|
||||
from shutil import copy2, move
|
||||
from sys import base_exec_prefix, executable, platform, version_info
|
||||
from threading import current_thread
|
||||
from typing import Callable, Iterable, List, Sequence, Set, Tuple, Union, cast
|
||||
from typing import (Any, Callable, Dict, Iterable, List,
|
||||
Sequence, Set, Tuple, Union, cast)
|
||||
|
||||
from apprise import Apprise, LogCapture
|
||||
from cron_converter import Cron
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from backend.base.definitions import (WEEKDAY_NUMBER, GeneralReminderData,
|
||||
RepeatQuantity, SendResult, T, U)
|
||||
RepeatQuantity, SendResult,
|
||||
Serialisable, T, U)
|
||||
from backend.base.logging import LOGGER
|
||||
|
||||
|
||||
@@ -199,6 +201,14 @@ def current_thread_id() -> int:
|
||||
return current_thread().native_id or -1
|
||||
|
||||
|
||||
def return_api(
|
||||
result: Serialisable,
|
||||
error: Union[str, None] = None,
|
||||
code: int = 200
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
return {'error': error, 'result': result}, code
|
||||
|
||||
|
||||
# region Security
|
||||
def get_hash(salt: bytes, data: str) -> bytes:
|
||||
"""Hash a string using the supplied salt.
|
||||
|
||||
@@ -17,8 +17,12 @@ from waitress.server import create_server
|
||||
from waitress.task import ThreadedTaskDispatcher as TTD
|
||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||
|
||||
from backend.base.definitions import Constants, StartType, StartTypeHandler
|
||||
from backend.base.helpers import Singleton, folder_path
|
||||
from backend.base.custom_exceptions import (BadRequest, InternalError,
|
||||
LogUnauthMindException,
|
||||
MethodNotAllowed, NotFound)
|
||||
from backend.base.definitions import (Constants, MindException,
|
||||
StartType, StartTypeHandler)
|
||||
from backend.base.helpers import Singleton, folder_path, return_api
|
||||
from backend.base.logging import LOGGER
|
||||
from backend.internals.db import DBConnectionManager, close_db
|
||||
from backend.internals.db_backup_import import revert_db_import
|
||||
@@ -84,23 +88,34 @@ class Server(metaclass=Singleton):
|
||||
# Add error handlers
|
||||
@app.errorhandler(400)
|
||||
def bad_request(e):
|
||||
return {'error': "BadRequest", "result": {}}, 400
|
||||
return return_api(**BadRequest().api_response)
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
if request.path.startswith(
|
||||
(Constants.API_PREFIX, Constants.ADMIN_PREFIX)
|
||||
):
|
||||
return {'error': "NotFound", "result": {}}, 404
|
||||
return return_api(**NotFound().api_response)
|
||||
return render("page_not_found.html")
|
||||
|
||||
@app.errorhandler(405)
|
||||
def method_not_allowed(e):
|
||||
return {'error': "MethodNotAllowed", "result": {}}, 405
|
||||
return return_api(**MethodNotAllowed().api_response)
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(e):
|
||||
return {'error': "InternalError", "result": {}}, 500
|
||||
return return_api(**InternalError().api_response)
|
||||
|
||||
@app.errorhandler(MindException)
|
||||
def mind_exception(e: MindException):
|
||||
if isinstance(e, LogUnauthMindException):
|
||||
ip = request.environ.get(
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
request.remote_addr
|
||||
)
|
||||
LOGGER.warning(f'Unauthorised request from {ip}')
|
||||
|
||||
return return_api(**e.api_response)
|
||||
|
||||
# Add endpoints
|
||||
app.register_blueprint(ui)
|
||||
|
||||
@@ -9,12 +9,11 @@ from typing import Any, Callable, Dict, Tuple, Union
|
||||
|
||||
from flask import Response, g, request, send_file
|
||||
|
||||
from backend.base.custom_exceptions import (AccessUnauthorized,
|
||||
APIKeyExpired, APIKeyInvalid,
|
||||
LogFileNotFound, UserNotFound)
|
||||
from backend.base.definitions import (ApiKeyEntry, Constants, MindException,
|
||||
SendResult, Serialisable, StartType)
|
||||
from backend.base.helpers import folder_path
|
||||
from backend.base.custom_exceptions import (APIKeyExpired, APIKeyInvalid,
|
||||
LogFileNotFound)
|
||||
from backend.base.definitions import (ApiKeyEntry, Constants,
|
||||
SendResult, StartType)
|
||||
from backend.base.helpers import folder_path, return_api
|
||||
from backend.base.logging import LOGGER, get_log_filepath
|
||||
from backend.features.reminders import Reminders
|
||||
from backend.features.static_reminders import StaticReminders
|
||||
@@ -54,14 +53,6 @@ users = Users()
|
||||
api_key_map: Dict[int, ApiKeyEntry] = {}
|
||||
|
||||
|
||||
def return_api(
|
||||
result: Serialisable,
|
||||
error: Union[str, None] = None,
|
||||
code: int = 200
|
||||
) -> Tuple[Dict[str, Any], int]:
|
||||
return {'error': error, 'result': result}, code
|
||||
|
||||
|
||||
def auth() -> None:
|
||||
"""Checks if the client is logged in.
|
||||
|
||||
@@ -120,28 +111,11 @@ def endpoint_wrapper(
|
||||
def wrapper(*args, **kwargs):
|
||||
requires_auth = get_api_docs(request).requires_auth
|
||||
|
||||
try:
|
||||
if requires_auth:
|
||||
auth()
|
||||
if requires_auth:
|
||||
auth()
|
||||
|
||||
inputs = input_validation()
|
||||
result = method(inputs, *args, **kwargs)
|
||||
|
||||
except MindException as e:
|
||||
if isinstance(
|
||||
e,
|
||||
(APIKeyInvalid, APIKeyExpired,
|
||||
UserNotFound, AccessUnauthorized)
|
||||
):
|
||||
ip = request.environ.get(
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
request.remote_addr
|
||||
)
|
||||
LOGGER.warning(f'Unauthorised request from {ip}')
|
||||
|
||||
result = return_api(**e.api_response)
|
||||
|
||||
return result
|
||||
inputs = input_validation()
|
||||
return method(inputs, *args, **kwargs)
|
||||
|
||||
wrapper.__name__ = method.__name__
|
||||
return wrapper
|
||||
|
||||
Reference in New Issue
Block a user