Files
B0rbor4d cc4c3fcecb Hub-and-Spoke Umbau: Multi-Tenant Zentrale + Satellite-Agent
- Backend: Customer/Satellite Models, customer_id auf Server/Job/Audit
- Satellite-API: heartbeat, poll (atomares Claiming), logs, result,
  scan-result, health-report - Auth via X-Api-Key (SHA-256 gehasht)
- Job-Queue: pending/claimed/running/success/failed + Stale-Janitor
- Batch-Trigger: ein Job pro Server, Satellite arbeitet sequenziell ab
- Credentials bleiben lokal: nur symbolische credential_ref zentral
- Neues Paket satellite/: Pull-Loop, WinRM/SSH/CAU/Scanner, PyInstaller-tauglich
- Frontend: Kunden-Switcher, Satelliten-View, Polling statt WebSocket
- Entfernt: WebSocket/Socket.io, Redis, zentrale Credentials, JobRunner
- Docs: README/AGENTS/PROMPT auf neue Architektur aktualisiert
2026-08-07 03:42:06 +00:00

59 lines
2.1 KiB
Python

"""Shared API dependencies: dashboard user auth (JWT) and satellite auth (API key)."""
from fastapi import Depends, Header, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.exceptions import ForbiddenError, UnauthorizedError
from app.core.security import decode_token
from app.models.satellite import Satellite, hash_api_key
from app.models.user import User
bearer_scheme = HTTPBearer(auto_error=False)
async def get_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
if credentials is None:
raise UnauthorizedError("Authorization header fehlt")
payload = decode_token(credentials.credentials)
username = payload.get("sub")
if not username:
raise UnauthorizedError("Token enthält keinen Benutzer")
result = await db.execute(select(User).where(User.username == username))
user = result.scalar_one_or_none()
if not user or not user.is_active:
raise UnauthorizedError("Benutzer unbekannt oder deaktiviert")
return user
async def require_admin(user: User = Depends(get_current_user)) -> User:
if not user.is_admin:
raise ForbiddenError("Administratorrechte erforderlich")
return user
async def get_current_satellite(
x_api_key: str | None = Header(default=None),
db: AsyncSession = Depends(get_db),
) -> Satellite:
"""Authenticate a satellite by its API key (X-Api-Key header)."""
if not x_api_key:
raise UnauthorizedError("X-Api-Key header fehlt")
result = await db.execute(
select(Satellite).where(Satellite.api_key_hash == hash_api_key(x_api_key))
)
satellite = result.scalar_one_or_none()
if not satellite or not satellite.is_active:
raise UnauthorizedError("Satellite unbekannt oder deaktiviert")
return satellite
def client_ip(request: Request) -> str | None:
return request.client.host if request.client else None