Add support for perm=foo+bar to specify multiple scopes that are required. Multiple perm args are acceptable too. Pretty logging of permission denied errors.

This commit is contained in:
2026-01-29 21:11:29 +00:00
parent 58b56a09a4
commit 3d2151fed7
3 changed files with 38 additions and 10 deletions
+30
View File
@@ -4,8 +4,12 @@ import logging
import sys
import time
from ipaddress import IPv6Address
from typing import TYPE_CHECKING
from starlette.middleware.base import BaseHTTPMiddleware
if TYPE_CHECKING:
from paskia.db.structs import SessionContext
from starlette.requests import Request
from starlette.responses import Response
@@ -25,6 +29,12 @@ _TIMING = "\033[38;5;242m" # timing/devmode (dark grey)
_WS_OPEN = "\033[1;93m" # WebSocket connect (bold bright yellow)
_WS_CLOSE = "\033[33m" # WebSocket disconnect (yellow)
_WS_STATUS = "\033[38;5;242m" # WebSocket close status (dark grey)
_AUTHZ_DENIED = "\033[0;31m" # Permission denied (red)
_AUTHZ_USER = "\033[1;34m" # User info (light blue)
_AUTHZ_ORG = "\033[34m" # User info (blue)
_AUTHZ_NEEDS = "\033[1;38;5;231m" # Needs (brightest white)
_AUTHZ_MISSING = "\033[1;31m" # Missing scope (bold red)
_AUTHZ_GRANTED = "\033[0;32m" # Granted scope (green)
def format_ipv6_network(ip: str) -> str:
@@ -198,6 +208,26 @@ def log_ws_close(ws_id: int, close_code: int | None, duration: float) -> None:
logger.info(f"{' ' * 15} {prefix} {status_str} {timing_str}")
def log_permission_denied(
ctx: "SessionContext", required: list[str], missing: list[str], *, require_all: bool
) -> None:
"""Log permission denied with org, role, user and highlighted missing scopes."""
missing_set = set(missing)
scopes = " ".join(
f"{_AUTHZ_MISSING}{s}{_RESET}"
if s in missing_set
else f"{_AUTHZ_GRANTED}{s}{_RESET}"
for s in required
)
n = "" if len(required) == 1 else " all" if require_all else " any"
logger.warning(
f"{_AUTHZ_DENIED}Permission denied{_RESET} "
f"{_AUTHZ_USER}{ctx.user.display_name}{_RESET} "
f"{_AUTHZ_ORG}({ctx.org.display_name} {ctx.role.display_name}){_RESET} "
f"{_AUTHZ_NEEDS}needs{n}:{_RESET} {scopes}"
)
class AccessLogMiddleware(BaseHTTPMiddleware):
"""Middleware that logs HTTP requests with custom format."""