feat(securitate-CORE): redactare creds + auth API-key per cont
Redactare: - handler RequestValidationError dropeaza input/ctx din 422 (vectorul de scurgere a rar_credentials.password pe /v1/prezentari); pastreaza type/loc/msg - app/security.py: scrub/scrub_text + CredentialRedactingFilter pe root+uvicorn - models.py: password cu repr=False Auth API-key: - app/auth.py: hash SHA-256 in api_keys (cheia in clar emisa o singura data), header X-API-Key / Authorization: Bearer, dependency resolve_account_id - enforcement pe flag AUTOPASS_require_api_key (prod on->401, dev off->cont default id=1; cheie prezenta invalida->401 mereu) - account_id real curge din cheie in ingestie + mapare - tools/apikey.py: CLI create/rotate/revoke/list (fara endpoint HTTP admin) 16 teste noi (tests/test_security.py). 85 pass total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...auth import resolve_account_id
|
||||
from ...db import get_connection
|
||||
from ...idempotency import idempotency_key
|
||||
from ...mapping import (
|
||||
@@ -33,17 +34,20 @@ router = APIRouter(prefix="/v1", tags=["v1"])
|
||||
|
||||
|
||||
@router.post("/prezentari", response_model=PrezentariResponse)
|
||||
def create_prezentari(req: PrezentareRequest) -> PrezentariResponse:
|
||||
def create_prezentari(
|
||||
req: PrezentareRequest,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> PrezentariResponse:
|
||||
"""Enqueue una/mai multe prezentari. Idempotent: continut identic -> acelasi submission.
|
||||
|
||||
Validarea de continut (T3, app.validation) ruleaza inainte de enqueue:
|
||||
esecurile NU resping cererea, ci enqueue-aza cu status `needs_data` + motiv
|
||||
(plan.md sect. 3). JSON malformat -> 422 din Pydantic (validare de shape).
|
||||
TODO(auth): rezolva account_id din API key (acum None).
|
||||
account_id vine din cheia API (resolve_account_id): cont real cu cheie,
|
||||
implicit id=1 in dev fara cheie, 401 fara cheie valida in prod.
|
||||
Nota: rar_credentials NU se persista (zero-storage) — worker-ul le va primi
|
||||
pe alt canal (T2); in schelet enqueue-ul doar stocheaza prezentarea.
|
||||
"""
|
||||
account_id = None # TODO(auth): din API key
|
||||
acct = account_or_default(account_id)
|
||||
conn = get_connection()
|
||||
results: list[SubmissionResult] = []
|
||||
@@ -174,18 +178,21 @@ def get_mapari_pending() -> dict:
|
||||
|
||||
|
||||
class MapareIn(BaseModel):
|
||||
account_id: int | None = None
|
||||
cod_op_service: str = Field(..., min_length=1)
|
||||
cod_prestatie: str = Field(..., min_length=1)
|
||||
auto_send: bool = True
|
||||
|
||||
|
||||
@router.post("/mapari")
|
||||
def create_mapare(req: MapareIn) -> dict:
|
||||
def create_mapare(
|
||||
req: MapareIn,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> dict:
|
||||
"""Salveaza/actualizeaza o mapare op->cod si re-rezolva submission-urile blocate.
|
||||
|
||||
Verifica intai ca `cod_prestatie` exista in nomenclator (nu lasam mapari catre
|
||||
coduri inexistente). Apoi upsert + re-rezolvare automata a `needs_mapping`.
|
||||
Contul vine din cheia API (NU din body) — un cont nu poate edita maparile
|
||||
altuia. Verifica intai ca `cod_prestatie` exista in nomenclator (nu lasam
|
||||
mapari catre coduri inexistente). Apoi upsert + re-rezolvare `needs_mapping`.
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
@@ -195,8 +202,8 @@ def create_mapare(req: MapareIn) -> dict:
|
||||
).fetchone()
|
||||
if not exists:
|
||||
raise HTTPException(status_code=422, detail=f"cod_prestatie '{cod}' nu exista in nomenclator")
|
||||
save_mapping(conn, req.account_id, req.cod_op_service, cod, req.auto_send)
|
||||
stats = reresolve_account(conn, req.account_id)
|
||||
save_mapping(conn, account_id, req.cod_op_service, cod, req.auto_send)
|
||||
stats = reresolve_account(conn, account_id)
|
||||
return {"saved": {"cod_op_service": req.cod_op_service.strip(), "cod_prestatie": cod}, "reresolve": stats}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user