aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2025-07-09 11:51:37 -0500
committerFrederick Muriuki Muriithi2025-07-09 11:57:25 -0500
commit1740ccbe30946aa6693a6a9ed8211a2ff7cfbf3d (patch)
treeaf5b89ba28a39cd859ae943e8882f39f5ae54069
parente7a89061cba689268daf4991038d9bec763a06d3 (diff)
downloadgn-auth-1740ccbe30946aa6693a6a9ed8211a2ff7cfbf3d.tar.gz
Improve error handling and reporting.HEADmain
- Refactor out common functionality into reusable utilities - Handle errors from the Authlib library/package - Handle 4xx errors generically.
-rw-r--r--gn_auth/__init__.py3
-rw-r--r--gn_auth/errors/__init__.py46
-rw-r--r--gn_auth/errors/authlib.py34
-rw-r--r--gn_auth/errors/common.py41
-rw-r--r--gn_auth/errors/http/http_4xx_errors.py26
-rw-r--r--gn_auth/errors/tracing.py18
-rw-r--r--gn_auth/templates/404.html13
-rw-r--r--gn_auth/templates/http-error-4xx.html20
-rw-r--r--gn_auth/templates/http-error-5xx.html (renamed from gn_auth/templates/50x.html)0
9 files changed, 122 insertions, 79 deletions
diff --git a/gn_auth/__init__.py b/gn_auth/__init__.py
index cf8989d..aebc63b 100644
--- a/gn_auth/__init__.py
+++ b/gn_auth/__init__.py
@@ -72,7 +72,8 @@ def gunicorn_loggers(appl: Flask) -> None:
_LOGGABLE_MODULES_ = (
"gn_auth.errors",
- "gn_auth.errors.tracing",
+ "gn_auth.errors.common",
+ "gn_auth.errors.authlib",
"gn_auth.errors.http.http_4xx_errors",
"gn_auth.errors.http.http_5xx_errors"
)
diff --git a/gn_auth/errors/__init__.py b/gn_auth/errors/__init__.py
index 533a842..97d1e9e 100644
--- a/gn_auth/errors/__init__.py
+++ b/gn_auth/errors/__init__.py
@@ -10,8 +10,9 @@ from flask import (Flask,
from gn_auth.auth.errors import AuthorisationError
-from .tracing import add_trace
from .http import http_error_handlers
+from .authlib import authlib_error_handlers
+from .common import add_trace, build_handler
logger = logging.getLogger(__name__)
@@ -20,43 +21,28 @@ __all__ = ["register_error_handlers"]
def handle_general_exception(exc: Exception):
"""Handle generic unhandled exceptions."""
- logger.error("Error occurred!", exc_info=True)
- content_type = request.content_type
- if bool(content_type) and content_type.lower() == "application/json":
- exc_args = [str(x) for x in exc.args]
- msg = ("The following exception was raised while attempting to access "
- f"{request.url}: {' '.join(exc_args)}")
- return jsonify(add_trace(exc, {
- "error": type(exc).__name__,
- "error_description": msg
- })), 500
-
- return render_template("50x.html",
- page=request.url,
- error=exc,
- trace=traceback.format_exception(exc)), 500
+ exc_args = [str(x) for x in exc.args]
+ _handle = build_handler("A generic exception occurred: "
+ " ".join(exc_args))
+ return _handle(exc)
def handle_authorisation_error(exc: AuthorisationError):
"""Handle AuthorisationError if not handled anywhere else."""
- logger.error("Error occurred!", exc_info=True)
- logger.error(exc)
- return jsonify(add_trace(exc, {
- "error": type(exc).__name__,
- "error_description": " :: ".join(exc.args)
- })), exc.error_code
-
-__error_handlers__ = {
- Exception: handle_general_exception,
- AuthorisationError: handle_authorisation_error
-}
+ exc_args = [str(x) for x in exc.args]
+ _handle = build_handler("A generic authorisation error occurred: "
+ " ".join(exc_args))
+ return _handle(exc)
def register_error_handlers(app: Flask):
"""Register ALL defined error handlers"""
_handlers = {
- **__error_handlers__,
- **http_error_handlers()
+ **authlib_error_handlers(),
+ **http_error_handlers(),
+ Exception: handle_general_exception,
+ AuthorisationError: handle_authorisation_error
}
- for class_, error_handler in __error_handlers__.items():
+ for class_, error_handler in _handlers.items():
+ logger.debug("Register handler for %s", class_.__name__)
app.register_error_handler(class_, error_handler)
diff --git a/gn_auth/errors/authlib.py b/gn_auth/errors/authlib.py
new file mode 100644
index 0000000..09862e3
--- /dev/null
+++ b/gn_auth/errors/authlib.py
@@ -0,0 +1,34 @@
+"""Handle authlib errors."""
+import json
+import logging
+
+from authlib.integrations.flask_oauth2.errors import _HTTPException
+
+from gn_auth.errors.common import build_handler
+
+logger = logging.getLogger(__name__)
+
+
+def __description__(body):
+ """Improve description for errors in authlib.oauth2.rfc6749.errors"""
+ _desc = body["error_description"]
+ match body["error"]:
+ case "missing_authorization":
+ return (
+ 'The expected "Authorization: Bearer ..." token was not found '
+ 'in the headers. Do please try again with the token provided.')
+ case _:
+ return _desc
+
+
+def _http_exception_handler_(exc: _HTTPException):
+ """Handle Authlib's `_HTTPException` errors."""
+ _handle = build_handler(__description__(json.loads(exc.body)))
+ return _handle(exc)
+
+
+def authlib_error_handlers() -> dict:
+ """Return handlers for Authlib errors"""
+ return {
+ _HTTPException: _http_exception_handler_
+ }
diff --git a/gn_auth/errors/common.py b/gn_auth/errors/common.py
new file mode 100644
index 0000000..7ef18cf
--- /dev/null
+++ b/gn_auth/errors/common.py
@@ -0,0 +1,41 @@
+"""Common utilities."""
+import logging
+import traceback
+
+from flask import request, jsonify, render_template
+
+logger = logging.getLogger(__name__)
+
+
+def add_trace(exc: Exception, errobj: dict) -> dict:
+ """Add the traceback to the error handling object."""
+ return {
+ **errobj,
+ "error-trace": "".join(traceback.format_exception(exc))
+ }
+
+
+def build_handler(description: str):
+ """Generic utility to build error handlers."""
+ def __handler__(exc: Exception):
+ error = (exc.name if hasattr(exc, "name") else exc.__class__.__name__)
+ status_code = exc.code if hasattr(exc, "code") else 500
+ content_type = request.content_type
+ if bool(content_type) and content_type.lower() == "application/json":
+ return (
+ jsonify(add_trace(exc,
+ {
+ "requested-uri": request.url,
+ "error": error,
+ "error_description": description
+ })),
+ status_code)
+
+ return (render_template(f"http-error-{str(status_code)[0:-2]}xx.html",
+ error=exc,
+ page=request.url,
+ description=description,
+ trace=traceback.format_exception(exc)),
+ status_code)
+
+ return __handler__
diff --git a/gn_auth/errors/http/http_4xx_errors.py b/gn_auth/errors/http/http_4xx_errors.py
index 704f11b..3a2ed88 100644
--- a/gn_auth/errors/http/http_4xx_errors.py
+++ b/gn_auth/errors/http/http_4xx_errors.py
@@ -1,31 +1,23 @@
"""Handlers for HTTP 4** errors"""
import logging
-from werkzeug.exceptions import NotFound
-from flask import request, jsonify, render_template
+from werkzeug.exceptions import NotFound, Forbidden, Unauthorized
-from gn_auth.errors.tracing import add_trace
+from gn_auth.errors.common import build_handler
__all__ = ["http_4xx_error_handlers"]
logger = logging.getLogger(__name__)
-def page_not_found(exc):
- """404 handler."""
- logger.error("Page '%s' was not found.", request.url, exc_info=True)
- content_type = request.content_type
- if bool(content_type) and content_type.lower() == "application/json":
- return jsonify(add_trace(exc, {
- "error": exc.name,
- "error_description": (f"The page '{request.url}' does not exist on "
- "this server.")
- })), exc.code
-
- return render_template("404.html", page=request.url), exc.code
-
def http_4xx_error_handlers() -> dict:
"""Return handlers for HTTP errors in the 400-499 range"""
return {
- NotFound: page_not_found
+ Forbidden: build_handler(
+ "You do not have the necessary privileges to access the requested "
+ "resource."),
+ NotFound: build_handler(
+ "The requested page does not exist on this server."),
+ Unauthorized: build_handler(
+ "You are not authorised to access the requested resource.")
}
diff --git a/gn_auth/errors/tracing.py b/gn_auth/errors/tracing.py
deleted file mode 100644
index 25b544f..0000000
--- a/gn_auth/errors/tracing.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Utilities for improving error tracing."""
-import logging
-import traceback
-
-from flask import request
-
-logger = logging.getLogger(__name__)
-
-
-def add_trace(exc: Exception, errobj: dict) -> dict:
- """Add the traceback to the error handling object."""
- logger.error("Endpoint: %s\n%s",
- request.url,
- traceback.format_exception(exc))
- return {
- **errobj,
- "error-trace": "".join(traceback.format_exception(exc))
- }
diff --git a/gn_auth/templates/404.html b/gn_auth/templates/404.html
deleted file mode 100644
index e17bfe8..0000000
--- a/gn_auth/templates/404.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{%extends "base.html"%}
-
-{%block title%}404: Page Not Found{%endblock%}
-
-{%block pagetitle%}404: Could Not Find the Requested Page{%endblock%}
-
-{%block content%}
-
-<p>
- The page "<strong>{{page}}</strong>" does not exist on this server.
-</p>
-
-{%endblock%}
diff --git a/gn_auth/templates/http-error-4xx.html b/gn_auth/templates/http-error-4xx.html
new file mode 100644
index 0000000..16c4581
--- /dev/null
+++ b/gn_auth/templates/http-error-4xx.html
@@ -0,0 +1,20 @@
+{%extends "base.html"%}
+
+{%block title%}{{error.code}}: {{error.name}}{%endblock%}
+
+{%block pagetitle%}{{error.code}}: {{error.name}}{%endblock%}
+
+{%block content%}
+
+<dl>
+ <dt>status code</dt>
+ <dd>{{error.code}}: {{error.name}}</dd>
+
+ <dt><strong>URI</strong></dt>
+ <dd>{{page}}</dd>
+
+ <dt>error description</dt>
+ <dd>{{description}}</dd>
+</dl>
+
+{%endblock%}
diff --git a/gn_auth/templates/50x.html b/gn_auth/templates/http-error-5xx.html
index 859a232..859a232 100644
--- a/gn_auth/templates/50x.html
+++ b/gn_auth/templates/http-error-5xx.html