Files
Unraid-AppOps-Gateway-Public/backend/app/core/security.py
T

83 lines
3.2 KiB
Python

"""Human requests use user identities; tokens are reserved for scoped machines."""
import hmac
import os
from ipaddress import ip_address, ip_network
from fastapi import Header, HTTPException, Request
from .config import settings
def token_is_strong(value: str | None, *, minimum_length: int = 24) -> bool:
return bool(value and len(value) >= minimum_length and value.strip().lower() not in {"change-me", "changeme", "dev", "test", "dev-voice-token"})
def _extract_bearer_token(authorization: str | None) -> str:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(401, "Authorization header missing")
return authorization[len("Bearer "):]
def require_auth_token(provided_token: str | None, expected_token: str | None, scope: str):
if not expected_token:
raise HTTPException(503, f"{scope} authentication is not configured")
if not hmac.compare_digest(_extract_bearer_token(provided_token), expected_token):
raise HTTPException(403, f"Invalid {scope} token")
def _request_from_trusted_operator_proxy(request: Request) -> bool:
if not settings.operator_proxy_auth_enabled or not settings.operator_proxy_trusted_networks:
return False
try:
client_ip = ip_address(request.client.host if request.client else "")
except ValueError:
return False
for configured_network in settings.operator_proxy_trusted_networks:
try:
if client_ip in ip_network(configured_network, strict=False):
return True
except ValueError:
continue
return False
def require_operator_request(request: Request) -> str:
# Compatibility for existing governed routes; no shared token or unlock.
from ..services.user_access import authorize_request
user = authorize_request(request)
return f"{user.provider}:{user.subject}"
def require_service_request(request: Request, service: str) -> str:
if service == "mcp":
enabled, expected = settings.mcp_enabled, settings.mcp_token
elif service == "n8n":
enabled, expected = settings.n8n_enabled, settings.n8n_token
elif service == "voice":
enabled, expected = True, settings.voice_token or os.getenv("APP_OPS_VOICE_TOKEN")
else:
raise HTTPException(403, "Unknown service identity")
if not enabled:
raise HTTPException(404, f"{service} integration is disabled")
if not expected or ((service != 'voice' or settings.environment == 'production') and not token_is_strong(expected)):
raise HTTPException(503, f"{service} authentication is not safely configured")
require_auth_token(request.headers.get("Authorization"), expected, service)
return service
def n8n_auth(request: Request) -> str:
return require_service_request(request, "n8n")
def operator_auth(request: Request) -> str:
return require_operator_request(request)
def voice_auth(request: Request, authorization: str | None = Header(default=None, alias="Authorization")):
if authorization:
expected = settings.voice_token or os.getenv("APP_OPS_VOICE_TOKEN")
require_auth_token(authorization, expected, "voice")
else:
require_operator_request(request)
return True