feat(creds): livrare creds per-cerere la worker (criptat efemer + sesiuni per-cont)

Plan sect.5: parola RAR vine per-cerere, stocata CRIPTATA in submission pana la
primul login reusit pe cont, apoi stearsa; JWT 30h acopera restul.

- app/crypto.py: Fernet, cheie din AUTOPASS_creds_key (nesetata -> efemera la
  runtime, creds nu supravietuiesc restartului). encrypt/decrypt_creds.
- schema + migrare: submissions.rar_creds_enc (creds criptate).
- ingestie: cripteaza rar_credentials, le lipeste de fiecare submission nou.
  Niciodata in clar in DB.
- worker: AccountSessions (login per-cont cu creds decriptate, cache JWT in
  memorie, sterge creds-urile contului dupa primul login + refresh nomenclator).
  401 creds gresite -> error fara retry; token expirat -> invalidare + requeue;
  fara creds (restart) -> requeue "indisponibile" (ROAAUTO re-trimite).
  claim_one intoarce account_id + creds_enc; recover_orphans filtrabil pe cont.
- requirements: cryptography==46.0.5.

Nota: refresh nomenclator e acum lazy la primul login per-cont (nu la pornire);
seed-ul fallback acopera editorul offline.

10 teste noi (tests/test_creds_delivery.py). 95 pass total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-15 20:16:16 +00:00
parent c17c1aa4f4
commit fbb2695336
9 changed files with 472 additions and 41 deletions

View File

@@ -17,6 +17,7 @@ from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from ...auth import resolve_account_id
from ...crypto import encrypt_creds
from ...db import get_connection
from ...idempotency import idempotency_key
from ...mapping import (
@@ -49,6 +50,10 @@ def create_prezentari(
pe alt canal (T2); in schelet enqueue-ul doar stocheaza prezentarea.
"""
acct = account_or_default(account_id)
# Creds RAR efemere: criptate si lipite de fiecare submission nou pana la
# primul login reusit pentru cont (worker le sterge atunci). Zero-storage at
# rest — niciodata in clar in DB/loguri (plan sect. 5).
creds_enc = encrypt_creds(req.rar_credentials.model_dump())
conn = get_connection()
results: list[SubmissionResult] = []
try:
@@ -90,9 +95,9 @@ def create_prezentari(
status, rar_error = "queued", None
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, rar_error) "
"VALUES (?, ?, ?, ?, ?)",
(key, acct, status, json.dumps(content, ensure_ascii=False), rar_error),
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, rar_error, rar_creds_enc) "
"VALUES (?, ?, ?, ?, ?, ?)",
(key, acct, status, json.dumps(content, ensure_ascii=False), rar_error, creds_enc),
)
results.append(SubmissionResult(submission_id=int(cur.lastrowid), status=status))
finally: