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.
106 lines
4.0 KiB
Python
106 lines
4.0 KiB
Python
"""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)
|