Module

security.decorators

Route protection decorators — @login_required and @requires.

Content-negotiated responses:

  • Browser requests → redirect to login URL (302)
  • API requests → JSON error (401/403)

Detection heuristic: a request is considered an API request if it has anAuthorization header or its Acceptheader prefers JSON over HTML.

Usage::

from chirp.security import login_required, requires

@app.route("/dashboard")
@login_required
def dashboard():
    return Template("dashboard.html")

@app.route("/admin")
@requires("admin")
def admin_panel():
    return Template("admin.html")

Functions

_build_login_redirect 2 str
Build a login redirect URL with a ``next`` parameter.
def _build_login_redirect(login_url: str, request_url: str) -> str
Parameters
Name Type Description
login_url str
request_url str
Returns
str
_is_api_request 1 bool
Detect whether the request is from an API client (not a browser). Heuristic: -…
def _is_api_request(request: Any) -> bool

Detect whether the request is from an API client (not a browser).

Heuristic:

  • HasAuthorizationheader → API client
  • Acceptprefers JSON over HTML → API client
  • Otherwise → browser
Parameters
Name Type Description
request Any
Returns
bool
login_required 1 Callable
Require an authenticated user to access this route. Browser requests are redir…
def login_required(handler: Callable) -> Callable

Require an authenticated user to access this route.

Browser requests are redirected to the login URL (fromAuthConfig). API requests receive a 401 response.

Usage::

@app.route("/dashboard")
@login_required
def dashboard():
    return Template("dashboard.html")
Parameters
Name Type Description
handler Callable
Returns
Callable
requires 2 Callable
Require specific permissions to access this route. Returns 401 if not authenti…
def requires(*permissions: str, policy: Callable[[Any, Any], bool | Awaitable[bool]] | None = None) -> Callable

Require specific permissions to access this route.

Returns 401 if not authenticated, 403 if missing permissions.

Usage::

@app.route("/admin")
@requires("admin")
def admin_panel():
    return Template("admin.html")

@app.route("/edit")
@requires("editor", "moderator")  # needs ALL listed permissions
def edit_post():
    return Template("edit.html")
Parameters
Name Type Description
*permissions str
policy Callable[[Any, Any], bool | Awaitable[bool]] | None Default:None
Returns
Callable