cryptorugmuncher
feat(alerts): vertical slice β€” pure-Python domain + thin v1 route
d767d3e
Raw
History Blame Contribute Delete
3.37 kB
"""RMI Backend β€” Auth middleware, API key verification, and JWT user identity.
This module is the single source of truth for auth. Routes import
`get_current_user` / `get_optional_user` from here (re-exported via
app.api.deps for convenience).
"""
from __future__ import annotations
import os
from typing import Any
from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
RMI_AUTH_TOKEN = os.getenv("RMI_AUTH_TOKEN", "")
PUBLIC_WRITE_PREFIXES = [
"/api/v1/auth/",
"/api/v1/x402/",
"/api/v1/x402-tools/",
"/api/v1/x402-databus/",
"/api/v1/databus/",
"/api/v1/alerts/",
"/api/v1/admin/",
"/api/v1/content/",
"/api/v1/bulletin/",
"/api/v1/rag/permanence/",
"/api/v1/token/",
"/api/v1/ai/",
"/api/v1/premium/",
"/api/v1/rag/",
"/api/v1/protect/",
"/api/v1/wallet-manager/",
]
PUBLIC_GET_PREFIXES = [
"/api/v1/token/",
"/api/v1/databus/",
"/api/v1/alerts/",
"/api/v1/x402-databus/",
"/api/v1/x402-tools/",
]
def is_public_path(path: str, method: str) -> bool:
"""Check if a path is publicly accessible without auth."""
if path in ("/health", "/ready", "/docs", "/openapi.json", "/redoc", "/", "/favicon.ico"):
return True
if path.startswith("/ws/") or not path.startswith("/api/"):
return True
if method in ("POST", "PUT", "DELETE", "PATCH"):
return any(path.startswith(p) for p in PUBLIC_WRITE_PREFIXES)
return True # GET/HEAD always public
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
path = request.url.path
method = request.method
if is_public_path(path, method):
return await call_next(request)
api_key = request.headers.get("X-API-Key", "")
if RMI_AUTH_TOKEN and api_key != RMI_AUTH_TOKEN:
return JSONResponse(
status_code=401,
content={"detail": "Unauthorized - valid X-API-Key header required for write operations"},
)
return await call_next(request)
# ── JWT user identity (FastAPI dependencies) ─────────────────────────────
# Delegates to the legacy app.auth JWT logic during strangelfig migration.
# Once legacy auth.py is migrated to the new pattern, these become the
# canonical implementation. Until then they reuse the working logic so
# new routes (like app/api/v1/auth/alerts.py) can use modern Depends().
async def get_optional_user(request: Request) -> dict[str, Any] | None:
"""Return the authenticated user dict, or None if not authenticated.
Reads the Authorization: Bearer <jwt> header. Returns the user dict
(id, email, tier, role) on success, None if no/invalid token.
Use this for endpoints that work with OR without auth.
"""
from app.auth import get_current_user as _legacy_get_user # local import to avoid cycles
return await _legacy_get_user(request)
async def get_current_user(request: Request) -> dict[str, Any]:
"""Require an authenticated user. Raises 401 if missing.
Use this for endpoints that REQUIRE auth.
"""
from app.auth import require_auth as _legacy_require # local import to avoid cycles
return await _legacy_require(request)