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:
105
backend/auth/jwt_auth.py
Normal file
105
backend/auth/jwt_auth.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""JWT authentication — current_user dependency, require roles, audit/log helpers."""
|
||||
import uuid
|
||||
|
||||
import jwt as pyjwt
|
||||
from fastapi import HTTPException, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
from config import APP_SECRET, JWT_ALG
|
||||
from database import db
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
async def current_user(cred: HTTPAuthorizationCredentials = Depends(security)):
|
||||
try:
|
||||
p = pyjwt.decode(cred.credentials, APP_SECRET, algorithms=[JWT_ALG])
|
||||
except pyjwt.ExpiredSignatureError:
|
||||
raise HTTPException(401, "Token expirado")
|
||||
except pyjwt.InvalidTokenError:
|
||||
raise HTTPException(401, "Token inválido")
|
||||
with db() as c:
|
||||
u = c.execute("SELECT * FROM users WHERE id=? AND is_active=1", (p["sub"],)).fetchone()
|
||||
s = c.execute("SELECT * FROM sessions WHERE id=? AND is_active=1", (p["sid"],)).fetchone()
|
||||
if not u or not s:
|
||||
raise HTTPException(401, "Sessão inválida")
|
||||
return dict(u)
|
||||
|
||||
|
||||
def _user_from_token(token: str):
|
||||
try:
|
||||
p = pyjwt.decode(token, APP_SECRET, algorithms=[JWT_ALG])
|
||||
except Exception:
|
||||
raise HTTPException(401, "Token inválido")
|
||||
with db() as c:
|
||||
u = c.execute("SELECT * FROM users WHERE id=? AND is_active=1", (p["sub"],)).fetchone()
|
||||
s = c.execute("SELECT * FROM sessions WHERE id=? AND is_active=1", (p["sid"],)).fetchone()
|
||||
if not u or not s:
|
||||
raise HTTPException(401, "Sessão inválida")
|
||||
return dict(u)
|
||||
|
||||
|
||||
def require(*roles):
|
||||
async def dep(u=Depends(current_user)):
|
||||
if u["role"] not in roles:
|
||||
raise HTTPException(403, f"Requer role: {', '.join(roles)}")
|
||||
return u
|
||||
return dep
|
||||
|
||||
|
||||
def _audit(uid, uname, action, resource=None, details=None, ip=None):
|
||||
with db() as c:
|
||||
c.execute(
|
||||
"INSERT INTO audit_log (id,user_id,username,action,resource,details,ip) VALUES (?,?,?,?,?,?,?)",
|
||||
(str(uuid.uuid4()), uid, uname, action, resource, details, ip))
|
||||
|
||||
|
||||
def _config_log(config_type, config_id, config_name, severity, action, message, uid=None, uname=None):
|
||||
with db() as c:
|
||||
c.execute(
|
||||
"INSERT INTO config_logs (id,config_type,config_id,config_name,severity,action,message,user_id,username) VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
(str(uuid.uuid4()), config_type, config_id, config_name or "", severity, action, str(message)[:2000], uid, uname))
|
||||
|
||||
|
||||
def _chat_log(sid, mid, uid, severity, source, action, message):
|
||||
with db() as c:
|
||||
c.execute(
|
||||
"INSERT INTO chat_logs (id,session_id,message_id,user_id,severity,source,action,message) VALUES (?,?,?,?,?,?,?,?)",
|
||||
(str(uuid.uuid4()), sid, mid, uid, severity, source, action, str(message)[:2000]))
|
||||
|
||||
|
||||
# ── Access Control ───────────────────────────────────────────────────────────
|
||||
_CONFIG_TABLES = {
|
||||
"oci": "oci_configs",
|
||||
"genai": "genai_configs",
|
||||
"adb": "adb_vector_configs",
|
||||
"mcp": "mcp_servers",
|
||||
}
|
||||
|
||||
|
||||
def _verify_config_access(config_type: str, config_id: str, user: dict, *, require_owner: bool = False) -> dict:
|
||||
table = _CONFIG_TABLES.get(config_type)
|
||||
if not table:
|
||||
raise HTTPException(400, f"Tipo de config inválido: {config_type}")
|
||||
with db() as c:
|
||||
row = c.execute(f"SELECT * FROM {table} WHERE id=?", (config_id,)).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(404)
|
||||
row = dict(row)
|
||||
if user["role"] == "admin":
|
||||
return row
|
||||
is_owner = row.get("user_id") == user["id"]
|
||||
is_global = row.get("is_global", 0)
|
||||
if not is_owner and not is_global:
|
||||
raise HTTPException(404)
|
||||
if require_owner and not is_owner:
|
||||
raise HTTPException(403, "Apenas o dono pode modificar este recurso")
|
||||
return row
|
||||
|
||||
|
||||
def _verify_report_access(report_id: str, user: dict) -> dict:
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM reports WHERE id=? AND user_id=?", (report_id, user["id"])).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(404)
|
||||
return dict(row)
|
||||
Reference in New Issue
Block a user