platform-core/modules/shared/requestFrontendUrl.py
Ida 30567bc38b
Some checks failed
Deploy Plattform-Core INT / test (push) Failing after 4s
Deploy Plattform-Core INT / deploy (push) Has been skipped
Deploy Plattform-Core (Int) / test (push) Successful in 1m9s
Deploy Plattform-Core (Int) / deploy (push) Successful in 9s
merge conflicts
2026-05-28 09:56:32 +02:00

83 lines
2.6 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Per-request frontend base URL (same idea as auth emails: frontendUrl from the client).
Set from the incoming HTTP request (X-Frontend-Url, Origin, or Referer) in app middleware.
Read when building billing alert links during that request (e.g. pool exhausted email).
"""
from __future__ import annotations
import logging
import re
from contextvars import ContextVar
from typing import Optional
from urllib.parse import urlparse
logger = logging.getLogger(__name__)
_CURRENT_FRONTEND_URL: ContextVar[str] = ContextVar("request_frontend_url", default="")
_CORS_ORIGIN_REGEX = re.compile(
r"^https://.*\.(poweron\.swiss|poweron-center\.net)$",
re.IGNORECASE,
)
def setRequestFrontendUrl(url: str) -> None:
"""Store the frontend base URL for the current request (no trailing slash)."""
_CURRENT_FRONTEND_URL.set((url or "").strip().rstrip("/"))
def getRequestFrontendUrl() -> str:
"""Return the frontend base URL for the current request, or empty if unset."""
return _CURRENT_FRONTEND_URL.get()
def _allowedOrigins() -> list[str]:
try:
from modules.shared.configuration import APP_CONFIG
raw = APP_CONFIG.get("APP_ALLOWED_ORIGINS") or ""
return [o.strip().rstrip("/") for o in raw.split(",") if o.strip()]
except Exception:
return []
def _isAllowedFrontendOrigin(origin: str) -> bool:
if not origin:
return False
normalized = origin.strip().rstrip("/")
if normalized in _allowedOrigins():
return True
return bool(_CORS_ORIGIN_REGEX.match(normalized))
def resolveFrontendUrlFromRequest(request) -> str:
"""
Resolve frontend base URL from the current HTTP request.
Priority: X-Frontend-Url (explicit, like auth body frontendUrl) → Origin → Referer origin.
Only returns origins that match APP_ALLOWED_ORIGINS or the CORS subdomain regex.
"""
explicit = (request.headers.get("X-Frontend-Url") or "").strip().rstrip("/")
if explicit and _isAllowedFrontendOrigin(explicit):
return explicit
if explicit:
logger.debug("Ignoring X-Frontend-Url not in allowed origins: %s", explicit)
origin = (request.headers.get("Origin") or "").strip().rstrip("/")
if origin and _isAllowedFrontendOrigin(origin):
return origin
referer = (request.headers.get("Referer") or "").strip()
if referer:
try:
ref_origin = f"{urlparse(referer).scheme}://{urlparse(referer).netloc}".rstrip("/")
if ref_origin and _isAllowedFrontendOrigin(ref_origin):
return ref_origin
except Exception:
pass
return ""