refactor: decompose monolith — app.py 10,600→143 lines, 30+ modules
Fase 0 complete: extract all business logic, auth, database, and 176 endpoints from monolithic app.py into dedicated modules. Structure: - app.py: FastAPI hub (CORS, routers, startup/shutdown) - models.py: 13 Pydantic request models - utils.py: shared utilities (embed status, upload validation, process registries) - config.py: constants, env vars, model catalogs - auth/: crypto, jwt_auth, oidc, rate_limit - database/: db(), init_db(), schema DDL - services/: genai, compliance, chat, terraform, embeddings, cis_reports, mcp - routes/: 13 APIRouter modules (auth, users, oci_config, oci_explorer, genai, mcp, adb, embeddings, reports, chat, terraform, settings, cis_engine) Also: README updated with OCIR auth instructions for manual docker run. 86 tests passing.
This commit is contained in:
93
backend/auth/oidc.py
Normal file
93
backend/auth/oidc.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""OIDC helpers — discovery, JWKS, state management, token validation."""
|
||||
import json
|
||||
import time
|
||||
import threading
|
||||
|
||||
import jwt as pyjwt
|
||||
from fastapi import HTTPException
|
||||
|
||||
from database import db
|
||||
from auth.crypto import _safe_dec
|
||||
|
||||
# ── Caches ───────────────────────────────────────────────────────────────────
|
||||
_oidc_discovery_cache: dict = {}
|
||||
_oidc_jwks_cache: dict = {}
|
||||
_OIDC_CACHE_TTL = 3600
|
||||
|
||||
_oidc_pending: dict = {}
|
||||
_oidc_pending_lock = threading.Lock()
|
||||
_OIDC_STATE_TTL = 300
|
||||
|
||||
|
||||
def _get_oidc_config() -> dict:
|
||||
keys = ("auth_mode", "oidc_issuer", "oidc_client_id", "oidc_client_secret",
|
||||
"oidc_redirect_uri", "oidc_group_admin", "oidc_group_user", "oidc_group_viewer")
|
||||
cfg = {}
|
||||
with db() as c:
|
||||
for k in keys:
|
||||
row = c.execute("SELECT value FROM app_settings WHERE key=?", (k,)).fetchone()
|
||||
cfg[k] = row["value"] if row else ""
|
||||
if cfg.get("oidc_client_secret"):
|
||||
cfg["oidc_client_secret"] = _safe_dec(cfg["oidc_client_secret"])
|
||||
return cfg
|
||||
|
||||
|
||||
def _oidc_discover(issuer: str) -> dict:
|
||||
import requests as req
|
||||
now = time.time()
|
||||
cached = _oidc_discovery_cache.get(issuer)
|
||||
if cached and now - cached[1] < _OIDC_CACHE_TTL:
|
||||
return cached[0]
|
||||
url = issuer.rstrip("/") + "/.well-known/openid-configuration"
|
||||
resp = req.get(url, timeout=10)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
_oidc_discovery_cache[issuer] = (data, now)
|
||||
return data
|
||||
|
||||
|
||||
def _oidc_get_jwks(jwks_uri: str) -> list:
|
||||
import requests as req
|
||||
now = time.time()
|
||||
cached = _oidc_jwks_cache.get(jwks_uri)
|
||||
if cached and now - cached[1] < _OIDC_CACHE_TTL:
|
||||
return cached[0]
|
||||
resp = req.get(jwks_uri, timeout=10)
|
||||
resp.raise_for_status()
|
||||
keys = resp.json().get("keys", [])
|
||||
_oidc_jwks_cache[jwks_uri] = (keys, now)
|
||||
return keys
|
||||
|
||||
|
||||
def _oidc_store_state(state: str, nonce: str):
|
||||
with _oidc_pending_lock:
|
||||
now = time.time()
|
||||
expired = [k for k, v in _oidc_pending.items() if now - v["created"] > _OIDC_STATE_TTL]
|
||||
for k in expired:
|
||||
del _oidc_pending[k]
|
||||
_oidc_pending[state] = {"nonce": nonce, "created": now}
|
||||
|
||||
|
||||
def _oidc_pop_state(state: str) -> dict | None:
|
||||
with _oidc_pending_lock:
|
||||
return _oidc_pending.pop(state, None)
|
||||
|
||||
|
||||
def _validate_id_token(id_token: str, issuer: str, client_id: str, nonce: str) -> dict:
|
||||
from jwt.algorithms import RSAAlgorithm
|
||||
disco = _oidc_discover(issuer)
|
||||
jwks = _oidc_get_jwks(disco["jwks_uri"])
|
||||
header = pyjwt.get_unverified_header(id_token)
|
||||
kid = header.get("kid")
|
||||
key_data = None
|
||||
for k in jwks:
|
||||
if k.get("kid") == kid:
|
||||
key_data = k
|
||||
break
|
||||
if not key_data:
|
||||
raise HTTPException(401, "OIDC: chave não encontrada no JWKS")
|
||||
public_key = RSAAlgorithm.from_jwk(json.dumps(key_data))
|
||||
claims = pyjwt.decode(id_token, public_key, algorithms=["RS256"], audience=client_id, issuer=issuer)
|
||||
if claims.get("nonce") != nonce:
|
||||
raise HTTPException(401, "OIDC: nonce inválido")
|
||||
return claims
|
||||
Reference in New Issue
Block a user