feat(security): add CSRF header constants and security-headers endpoint

Move X-BanGUI-Request header name/value to backend/app/utils/constants.py as single source of truth. Add GET /api/v1/config/security-headers endpoint. Update csrf middleware, frontend api client, and docs to use shared constants.
This commit is contained in:
2026-05-03 22:06:43 +02:00
parent cee3daffc1
commit dafe8d61e2
7 changed files with 109 additions and 11 deletions

View File

@@ -16,6 +16,7 @@ from app.dependencies import (
SettingsServiceContextDep,
)
from app.exceptions import OperationError
from app.mappers import config_mappers
from app.models.config import (
Fail2BanLogResponse,
GlobalConfigResponse,
@@ -26,15 +27,15 @@ from app.models.config import (
MapColorThresholdsUpdate,
RegexTestRequest,
RegexTestResponse,
SecurityHeadersResponse,
ServiceStatusResponse,
)
from app.mappers import config_mappers
from app.services import (
config_service,
jail_service,
log_service,
)
from app.utils.constants import RATE_LIMIT_CONFIG_UPDATE_REQUESTS
from app.utils.constants import CSRF_HEADER_NAME, CSRF_HEADER_VALUE, RATE_LIMIT_CONFIG_UPDATE_REQUESTS
log: structlog.stdlib.BoundLogger = structlog.get_logger()
@@ -59,9 +60,10 @@ def _check_config_update_rate_limit(
_CONFIG_UPDATE_BUCKET, client_ip, RATE_LIMIT_CONFIG_UPDATE_REQUESTS, _MINUTE
)
if not is_allowed:
from app.exceptions import RateLimitError
import structlog
from app.exceptions import RateLimitError
log = structlog.get_logger()
log.warning(
"config_update_rate_limit_exceeded",
@@ -501,3 +503,37 @@ async def get_service_status(
probe_fn=health_service.probe,
)
return config_mappers.map_domain_service_status_to_response(domain_result)
@router.get(
"/security-headers",
response_model=SecurityHeadersResponse,
summary="Return security-relevant header configuration",
responses={
200: {"description": "Security header names and values returned", "model": SecurityHeadersResponse},
401: {"description": "Session missing, expired, or invalid"},
},
)
async def get_security_headers(
_request: Request,
_auth: AuthDep,
) -> SecurityHeadersResponse:
"""Return the header name and value used for CSRF protection.
This endpoint allows the frontend to discover the required CSRF header
name and value at runtime rather than hard-coding them. The response
is derived from the same constants used by the backend CSRF middleware,
ensuring a single source of truth.
Args:
request: Incoming request.
_auth: Validated session — enforces authentication.
Returns:
:class:`~app.models.config.SecurityHeadersResponse` with
``csrf_header_name`` and ``csrf_header_value``.
"""
return SecurityHeadersResponse(
csrf_header_name=CSRF_HEADER_NAME,
csrf_header_value=CSRF_HEADER_VALUE,
)