Refactor router dependencies to use explicit fail2ban socket and HTTP session injection
This commit is contained in:
@@ -12,15 +12,17 @@ Also provides ``GET /api/dashboard/bans`` for the dashboard ban-list table,
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import aiohttp
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
|
||||
from app import __version__
|
||||
from app.dependencies import AuthDep, DbDep
|
||||
from app.dependencies import (
|
||||
AuthDep,
|
||||
DbDep,
|
||||
Fail2BanSocketDep,
|
||||
HttpSessionDep,
|
||||
)
|
||||
from app.models.ban import (
|
||||
BanOrigin,
|
||||
BansByCountryResponse,
|
||||
@@ -80,9 +82,10 @@ async def get_server_status(
|
||||
summary="Return a paginated list of recent bans",
|
||||
)
|
||||
async def get_dashboard_bans(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."),
|
||||
source: Literal["fail2ban", "archive"] = Query(
|
||||
default="fail2ban",
|
||||
@@ -104,7 +107,6 @@ async def get_dashboard_bans(
|
||||
GET request.
|
||||
|
||||
Args:
|
||||
request: The incoming request (used to access ``app.state``).
|
||||
_auth: Validated session dependency.
|
||||
range: Time-range preset — ``"24h"``, ``"7d"``, ``"30d"``, or
|
||||
``"365d"``.
|
||||
@@ -116,9 +118,6 @@ async def get_dashboard_bans(
|
||||
:class:`~app.models.ban.DashboardBanListResponse` with paginated
|
||||
ban items and the total count for the selected window.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
http_session: aiohttp.ClientSession = request.app.state.http_session
|
||||
|
||||
return await ban_service.list_bans(
|
||||
socket_path,
|
||||
range,
|
||||
@@ -138,9 +137,10 @@ async def get_dashboard_bans(
|
||||
summary="Return ban counts aggregated by country",
|
||||
)
|
||||
async def get_bans_by_country(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."),
|
||||
source: Literal["fail2ban", "archive"] = Query(
|
||||
default="fail2ban",
|
||||
@@ -164,7 +164,6 @@ async def get_bans_by_country(
|
||||
during this GET request.
|
||||
|
||||
Args:
|
||||
request: The incoming request.
|
||||
_auth: Validated session dependency.
|
||||
range: Time-range preset.
|
||||
origin: Optional filter by ban origin.
|
||||
@@ -173,9 +172,6 @@ async def get_bans_by_country(
|
||||
:class:`~app.models.ban.BansByCountryResponse` with per-country
|
||||
aggregation and the companion ban list.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
http_session: aiohttp.ClientSession = request.app.state.http_session
|
||||
|
||||
return await ban_service.bans_by_country(
|
||||
socket_path,
|
||||
range,
|
||||
@@ -195,9 +191,9 @@ async def get_bans_by_country(
|
||||
summary="Return ban counts aggregated into time buckets",
|
||||
)
|
||||
async def get_ban_trend(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."),
|
||||
source: Literal["fail2ban", "archive"] = Query(
|
||||
default="fail2ban",
|
||||
@@ -223,7 +219,6 @@ async def get_ban_trend(
|
||||
* ``365d`` → 7-day buckets (~53 total)
|
||||
|
||||
Args:
|
||||
request: The incoming request (used to access ``app.state``).
|
||||
_auth: Validated session dependency.
|
||||
range: Time-range preset.
|
||||
origin: Optional filter by ban origin.
|
||||
@@ -232,8 +227,6 @@ async def get_ban_trend(
|
||||
:class:`~app.models.ban.BanTrendResponse` with the ordered bucket
|
||||
list and the bucket-size label.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
|
||||
return await ban_service.ban_trend(
|
||||
socket_path,
|
||||
range,
|
||||
@@ -249,9 +242,9 @@ async def get_ban_trend(
|
||||
summary="Return ban counts aggregated by jail",
|
||||
)
|
||||
async def get_bans_by_jail(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
range: TimeRange = Query(default=_DEFAULT_RANGE, description="Time-range preset."),
|
||||
source: Literal["fail2ban", "archive"] = Query(
|
||||
default="fail2ban",
|
||||
@@ -269,7 +262,6 @@ async def get_bans_by_jail(
|
||||
distribution bar chart.
|
||||
|
||||
Args:
|
||||
request: The incoming request (used to access ``app.state``).
|
||||
_auth: Validated session dependency.
|
||||
range: Time-range preset — ``"24h"``, ``"7d"``, ``"30d"``, or
|
||||
``"365d"``.
|
||||
@@ -279,8 +271,6 @@ async def get_bans_by_jail(
|
||||
:class:`~app.models.ban.BansByJailResponse` with per-jail counts
|
||||
sorted descending and the total for the selected window.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
|
||||
return await ban_service.bans_by_jail(
|
||||
socket_path,
|
||||
range,
|
||||
|
||||
@@ -21,9 +21,15 @@ from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, Path, Request, status
|
||||
from fastapi import APIRouter, Body, HTTPException, Path, status
|
||||
|
||||
from app.dependencies import AuthDep, DbDep
|
||||
from app.dependencies import (
|
||||
AuthDep,
|
||||
DbDep,
|
||||
Fail2BanSocketDep,
|
||||
HttpSessionDep,
|
||||
)
|
||||
from app.exceptions import JailNotFoundError, JailOperationError
|
||||
from app.models.ban import JailBannedIpsResponse
|
||||
from app.models.jail import (
|
||||
IgnoreIpRequest,
|
||||
@@ -32,7 +38,6 @@ from app.models.jail import (
|
||||
JailListResponse,
|
||||
)
|
||||
from app.services import geo_service, jail_service
|
||||
from app.exceptions import JailNotFoundError, JailOperationError
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/jails", tags=["Jails"])
|
||||
@@ -100,8 +105,8 @@ def _conflict(message: str) -> HTTPException:
|
||||
summary="List all active fail2ban jails",
|
||||
)
|
||||
async def get_jails(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailListResponse:
|
||||
"""Return a summary of every active fail2ban jail.
|
||||
|
||||
@@ -110,13 +115,11 @@ async def get_jails(
|
||||
for each jail.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
|
||||
Returns:
|
||||
:class:`~app.models.jail.JailListResponse` with all active jails.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
return await jail_service.list_jails(socket_path)
|
||||
except Fail2BanConnectionError as exc:
|
||||
@@ -129,9 +132,9 @@ async def get_jails(
|
||||
summary="Return full detail for a single jail",
|
||||
)
|
||||
async def get_jail(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailDetailResponse:
|
||||
"""Return the complete configuration and runtime state for one jail.
|
||||
|
||||
@@ -140,7 +143,6 @@ async def get_jail(
|
||||
counters.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
|
||||
@@ -151,7 +153,6 @@ async def get_jail(
|
||||
HTTPException: 404 when the jail does not exist.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
return await jail_service.get_jail(socket_path, name)
|
||||
except JailNotFoundError:
|
||||
@@ -171,8 +172,8 @@ async def get_jail(
|
||||
summary="Reload all fail2ban jails",
|
||||
)
|
||||
async def reload_all_jails(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Reload every fail2ban jail to apply configuration changes.
|
||||
|
||||
@@ -180,7 +181,6 @@ async def reload_all_jails(
|
||||
jails simultaneously.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
|
||||
Returns:
|
||||
@@ -190,7 +190,6 @@ async def reload_all_jails(
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.reload_all(socket_path)
|
||||
return JailCommandResponse(message="All jails reloaded successfully.", jail="*")
|
||||
@@ -206,14 +205,13 @@ async def reload_all_jails(
|
||||
summary="Start a stopped jail",
|
||||
)
|
||||
async def start_jail(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Start a fail2ban jail that is currently stopped.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
|
||||
@@ -225,7 +223,6 @@ async def start_jail(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.start_jail(socket_path, name)
|
||||
return JailCommandResponse(message=f"Jail {name!r} started.", jail=name)
|
||||
@@ -243,9 +240,9 @@ async def start_jail(
|
||||
summary="Stop a running jail",
|
||||
)
|
||||
async def stop_jail(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Stop a running fail2ban jail.
|
||||
|
||||
@@ -254,7 +251,6 @@ async def stop_jail(
|
||||
jail is already stopped the request succeeds silently (idempotent).
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
|
||||
@@ -265,7 +261,6 @@ async def stop_jail(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.stop_jail(socket_path, name)
|
||||
return JailCommandResponse(message=f"Jail {name!r} stopped.", jail=name)
|
||||
@@ -281,9 +276,9 @@ async def stop_jail(
|
||||
summary="Toggle idle mode for a jail",
|
||||
)
|
||||
async def toggle_idle(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
on: bool = Body(..., description="``true`` to enable idle, ``false`` to disable."),
|
||||
) -> JailCommandResponse:
|
||||
"""Enable or disable idle mode for a fail2ban jail.
|
||||
@@ -292,7 +287,6 @@ async def toggle_idle(
|
||||
preserving all existing bans.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
on: ``true`` to enable idle, ``false`` to disable.
|
||||
@@ -305,7 +299,6 @@ async def toggle_idle(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
state_str = "on" if on else "off"
|
||||
try:
|
||||
await jail_service.set_idle(socket_path, name, on=on)
|
||||
@@ -327,14 +320,13 @@ async def toggle_idle(
|
||||
summary="Reload a single jail",
|
||||
)
|
||||
async def reload_jail(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Reload a single fail2ban jail to pick up configuration changes.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
|
||||
@@ -346,7 +338,6 @@ async def reload_jail(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.reload_jail(socket_path, name)
|
||||
return JailCommandResponse(message=f"Jail {name!r} reloaded.", jail=name)
|
||||
@@ -377,14 +368,13 @@ class _IgnoreSelfRequest(IgnoreIpRequest):
|
||||
summary="List the ignore IPs for a jail",
|
||||
)
|
||||
async def get_ignore_list(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> list[str]:
|
||||
"""Return the current ignore list (IP whitelist) for a fail2ban jail.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
|
||||
@@ -395,7 +385,6 @@ async def get_ignore_list(
|
||||
HTTPException: 404 when the jail does not exist.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
return await jail_service.get_ignore_list(socket_path, name)
|
||||
except JailNotFoundError:
|
||||
@@ -411,10 +400,10 @@ async def get_ignore_list(
|
||||
summary="Add an IP or network to the ignore list",
|
||||
)
|
||||
async def add_ignore_ip(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
body: IgnoreIpRequest,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Add an IP address or CIDR network to a jail's ignore list.
|
||||
|
||||
@@ -422,7 +411,6 @@ async def add_ignore_ip(
|
||||
trigger the configured fail regex.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
body: Payload containing the IP or CIDR to add.
|
||||
@@ -436,7 +424,6 @@ async def add_ignore_ip(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.add_ignore_ip(socket_path, name, body.ip)
|
||||
return JailCommandResponse(
|
||||
@@ -462,15 +449,14 @@ async def add_ignore_ip(
|
||||
summary="Remove an IP or network from the ignore list",
|
||||
)
|
||||
async def del_ignore_ip(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
body: IgnoreIpRequest,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
) -> JailCommandResponse:
|
||||
"""Remove an IP address or CIDR network from a jail's ignore list.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
body: Payload containing the IP or CIDR to remove.
|
||||
@@ -483,7 +469,6 @@ async def del_ignore_ip(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
try:
|
||||
await jail_service.del_ignore_ip(socket_path, name, body.ip)
|
||||
return JailCommandResponse(
|
||||
@@ -504,9 +489,9 @@ async def del_ignore_ip(
|
||||
summary="Toggle the ignoreself option for a jail",
|
||||
)
|
||||
async def toggle_ignore_self(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
on: bool = Body(..., description="``true`` to enable ignoreself, ``false`` to disable."),
|
||||
) -> JailCommandResponse:
|
||||
"""Toggle the ``ignoreself`` flag for a fail2ban jail.
|
||||
@@ -515,7 +500,6 @@ async def toggle_ignore_self(
|
||||
own IP addresses to the ignore list so the host can never ban itself.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
on: ``true`` to enable, ``false`` to disable.
|
||||
@@ -528,7 +512,6 @@ async def toggle_ignore_self(
|
||||
HTTPException: 409 when fail2ban reports the operation failed.
|
||||
HTTPException: 502 when fail2ban is unreachable.
|
||||
"""
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
state_str = "enabled" if on else "disabled"
|
||||
try:
|
||||
await jail_service.set_ignore_self(socket_path, name, on=on)
|
||||
@@ -555,10 +538,11 @@ async def toggle_ignore_self(
|
||||
summary="Return paginated currently-banned IPs for a single jail",
|
||||
)
|
||||
async def get_jail_banned_ips(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
db: DbDep,
|
||||
name: _NamePath,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
page: int = 1,
|
||||
page_size: int = 25,
|
||||
search: str | None = None,
|
||||
@@ -570,7 +554,6 @@ async def get_jail_banned_ips(
|
||||
geo-enriched exclusively for that page slice.
|
||||
|
||||
Args:
|
||||
request: Incoming request (used to access ``app.state``).
|
||||
_auth: Validated session — enforces authentication.
|
||||
name: Jail name.
|
||||
page: 1-based page number (default 1, min 1).
|
||||
@@ -596,9 +579,6 @@ async def get_jail_banned_ips(
|
||||
detail="page_size must be between 1 and 100.",
|
||||
)
|
||||
|
||||
socket_path: str = request.app.state.settings.fail2ban_socket
|
||||
http_session = getattr(request.app.state, "http_session", None)
|
||||
|
||||
try:
|
||||
return await jail_service.get_jail_banned_ips(
|
||||
socket_path=socket_path,
|
||||
|
||||
Reference in New Issue
Block a user