feat(5.20): US-013 retragere accounts.rar_creds_enc -> per-env + DROP cu garda
Toate citirile pe coloana legacy accounts.rar_creds_enc mutate pe sloturile per-env (rar_creds_test_enc/rar_creds_prod_enc): worker fallback+keepalive, are_creds (web) si are_creds_rar (integrare, +are_creds_test/_prod), write-back API la reactivare, purjare la stergere cont, _get_acasa_context/_fetch_cont_env_state. Contract API (aditiv): POST /v1/conturi/rar-creds primeste rar_target optional (test/prod), scrie in slotul corect + activeaza mediul; DELETE primeste ?env (sterge un slot sau ambele). Documentat in docs/api-rar-contract.md. DROP cu garda in db.py (schema.sql fara coloana pe DB fresh): - 6a: eliminat ADD COLUMN rar_creds_enc (fara ping-pong re-ADD dupa DROP) - 6b: try/except fail-safe (nu crapa boot-ul) + garda sqlite_version >= 3.35 - 6c: re-backfill old->new imediat inainte de assert (ancora globala) - garda orfane: DROP anulat daca vreun creds legacy nu a aterizat in slot per-env - backup criptat accounts_rar_creds_enc_backup inainte de DROP - 6d: verificare prin PRAGMA table_info (NU grep — submissions are aceeasi coloana) Garda one-way, idempotenta la boot repetat (verificat). submissions.rar_creds_enc ramane neatinsa. tests/test_retragere_creds_enc.py: niciun read pe coloana veche, conturi rar-creds env-aware, are_creds per-env, DROP blocat de garda la lipsa copiere. 9 teste existente actualizate pe sloturi per-env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -238,7 +238,8 @@ def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
|
||||
NU acest tombstone — de aceea purjam PII aici, la momentul stergerii."""
|
||||
set_status(conn, account_id, "deleted") # valideaza existenta + protejeaza id=1; seteaza status+active=0
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_enc=NULL, cui=NULL WHERE id=?", (account_id,)
|
||||
"UPDATE accounts SET rar_creds_test_enc=NULL, rar_creds_prod_enc=NULL, cui=NULL WHERE id=?",
|
||||
(account_id,),
|
||||
)
|
||||
conn.execute(
|
||||
"UPDATE api_keys SET active=0, revoked_at=datetime('now') WHERE account_id=? AND active=1",
|
||||
@@ -247,7 +248,7 @@ def delete_account(conn: sqlite3.Connection, account_id: int) -> None:
|
||||
|
||||
|
||||
def list_accounts(conn: sqlite3.Connection) -> list[dict]:
|
||||
"""Metadate conturi (FARA `rar_creds_enc`), ordonate dupa id. Exclude conturile 'deleted'
|
||||
"""Metadate conturi (FARA creds RAR criptate), ordonate dupa id. Exclude conturile 'deleted'
|
||||
(stergere soft -> invizibile in panou)."""
|
||||
rows = conn.execute(
|
||||
"SELECT id, name, cui, email, active, status, tier, trial_until, "
|
||||
|
||||
@@ -37,7 +37,9 @@ def ping(
|
||||
account_id — contul rezolvat din cheie (sau 1 in dev fara cheie)
|
||||
mediu — "test" / "prod" (settings.rar_env)
|
||||
autentificat_cu_cheie — True daca cererea a venit cu o cheie API reala valida
|
||||
are_creds_rar — True daca contul are rar_creds_enc stocat
|
||||
are_creds_rar — True daca contul are creds RAR stocate pe cel putin un mediu (test sau prod)
|
||||
are_creds_test — True daca contul are creds RAR pentru mediul Testare
|
||||
are_creds_prod — True daca contul are creds RAR pentru mediul Productie
|
||||
ts — timestamp ISO UTC al cererii
|
||||
"""
|
||||
settings = get_settings()
|
||||
@@ -55,23 +57,27 @@ def ping(
|
||||
conn.close()
|
||||
autentificat_cu_cheie = acct is not None
|
||||
|
||||
# Verificam daca contul are creds RAR stocate.
|
||||
# Verificam daca contul are creds RAR stocate (per-env, US-013).
|
||||
aid = account_or_default(account_id)
|
||||
conn = get_connection()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT rar_creds_enc FROM accounts WHERE id=?", (aid,)
|
||||
"SELECT rar_creds_test_enc, rar_creds_prod_enc FROM accounts WHERE id=?", (aid,)
|
||||
).fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
are_creds_rar = bool(row and row["rar_creds_enc"])
|
||||
are_creds_test = bool(row and row["rar_creds_test_enc"])
|
||||
are_creds_prod = bool(row and row["rar_creds_prod_enc"])
|
||||
are_creds_rar = are_creds_test or are_creds_prod
|
||||
|
||||
return JSONResponse({
|
||||
"account_id": aid,
|
||||
"mediu": settings.rar_env,
|
||||
"autentificat_cu_cheie": autentificat_cu_cheie,
|
||||
"are_creds_rar": are_creds_rar,
|
||||
"are_creds_test": are_creds_test,
|
||||
"are_creds_prod": are_creds_prod,
|
||||
"ts": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ def create_prezentari(
|
||||
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.
|
||||
Cand rar_credentials lipseste, submission-ul intra fara creds efemere: worker-ul
|
||||
cade pe creds-urile durabile ale contului (`accounts.rar_creds_enc`).
|
||||
cade pe creds-urile durabile ale contului (per-env: `accounts.rar_creds_{env}_enc`).
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
# Creds RAR efemere: criptate si lipite de fiecare submission nou pana la
|
||||
@@ -276,12 +276,11 @@ def create_prezentari(
|
||||
cl["rar_error"], creds_enc, env, existing["id"]),
|
||||
)
|
||||
if cur.rowcount == 1:
|
||||
# Creds noi se propaga si in canalul durabil (accounts.rar_creds_enc)
|
||||
# — ambele canale converg pe parola corectata.
|
||||
# US-013: muta pe slot env dupa login (write-back conservator).
|
||||
# Creds noi se propaga si in slotul durabil per-env al contului
|
||||
# — ambele canale converg pe parola corectata (US-013, env-aware).
|
||||
if req.rar_credentials is not None:
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_enc=? WHERE id=?",
|
||||
f"UPDATE accounts SET rar_creds_{env}_enc=?, rar_{env}_enabled=1 WHERE id=?",
|
||||
(encrypt_creds(req.rar_credentials.model_dump()), acct),
|
||||
)
|
||||
_emite_text_rule_hits(conn, acct, existing["id"], cl["resolved"])
|
||||
@@ -742,10 +741,16 @@ def create_mapare(
|
||||
|
||||
|
||||
class RarCredsIn(BaseModel):
|
||||
"""Creds RAR durabile per-cont. Stocate criptate (Fernet) in accounts.rar_creds_enc."""
|
||||
"""Creds RAR durabile per-cont, stocate criptat (Fernet) in slotul per-mediu.
|
||||
|
||||
`rar_target` selecteaza mediul: 'test' | 'prod'. Absent -> mediul ancorei globale
|
||||
(AUTOPASS_RAR_ENV), implicit 'test'. Schimbare aditiva — clientii vechi care nu trimit
|
||||
`rar_target` continua sa functioneze (comportament consistent cu ancora globala).
|
||||
"""
|
||||
|
||||
email: str = Field(..., min_length=1)
|
||||
password: str = Field(..., min_length=1, repr=False)
|
||||
rar_target: str | None = None # 'test' | 'prod' | None -> ancora globala
|
||||
|
||||
|
||||
@router.post("/conturi/rar-creds")
|
||||
@@ -753,21 +758,26 @@ def set_rar_creds(
|
||||
req: RarCredsIn,
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> dict:
|
||||
"""Seteaza creds RAR durabile per-cont.
|
||||
"""Seteaza creds RAR durabile per-cont, in slotul per-mediu (US-013, env-aware).
|
||||
|
||||
Criptate Fernet in accounts.rar_creds_enc. Worker-ul le foloseste ca fallback
|
||||
cand submission-ul nu mai are creds (canal web fara re-pusher, restart worker).
|
||||
Contul vine din cheia API.
|
||||
Slotul tinta: `req.rar_target` ('test'/'prod') sau ancora globala (AUTOPASS_RAR_ENV).
|
||||
Activeaza mediul selectat (`rar_{env}_enabled=1`). Worker-ul le foloseste ca
|
||||
fallback cand submission-ul nu mai are creds efemere. Contul vine din cheia API.
|
||||
"""
|
||||
from ...config import get_settings as _gs
|
||||
_s = _gs()
|
||||
env = req.rar_target if req.rar_target in ("test", "prod") else (
|
||||
_s.rar_env if _s.rar_env in ("test", "prod") else "test"
|
||||
)
|
||||
acct = account_or_default(account_id)
|
||||
enc = encrypt_creds({"email": req.email, "password": req.password})
|
||||
conn = get_connection()
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_enc=? WHERE id=?",
|
||||
f"UPDATE accounts SET rar_creds_{env}_enc=?, rar_{env}_enabled=1 WHERE id=?",
|
||||
(enc, acct),
|
||||
)
|
||||
return {"ok": True, "account_id": acct}
|
||||
return {"ok": True, "account_id": acct, "rar_env": env}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -775,12 +785,27 @@ def set_rar_creds(
|
||||
@router.delete("/conturi/rar-creds")
|
||||
def delete_rar_creds(
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
env: str | None = None,
|
||||
) -> dict:
|
||||
"""Sterge creds RAR durabile per-cont (revenire la modelul efemer Treapta 1)."""
|
||||
"""Sterge creds RAR durabile per-cont (revenire la modelul efemer Treapta 1).
|
||||
|
||||
`env` (query param): 'test' | 'prod' -> sterge DOAR slotul acelui mediu + dezactiveaza-l.
|
||||
Absent -> sterge AMBELE sloturi (revenire completa). Schimbare aditiva (back-compat).
|
||||
"""
|
||||
acct = account_or_default(account_id)
|
||||
conn = get_connection()
|
||||
try:
|
||||
conn.execute("UPDATE accounts SET rar_creds_enc=NULL WHERE id=?", (acct,))
|
||||
if env in ("test", "prod"):
|
||||
conn.execute(
|
||||
f"UPDATE accounts SET rar_creds_{env}_enc=NULL, rar_{env}_enabled=0 WHERE id=?",
|
||||
(acct,),
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_test_enc=NULL, rar_test_enabled=0, "
|
||||
"rar_creds_prod_enc=NULL, rar_prod_enabled=0 WHERE id=?",
|
||||
(acct,),
|
||||
)
|
||||
return {"ok": True, "account_id": acct}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
99
app/db.py
99
app/db.py
@@ -86,11 +86,12 @@ def _migrate(conn: sqlite3.Connection) -> None:
|
||||
|
||||
# Coloane accounts
|
||||
acc_cols = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()}
|
||||
if "rar_creds_enc" not in acc_cols:
|
||||
conn.execute("ALTER TABLE accounts ADD COLUMN rar_creds_enc TEXT")
|
||||
acc_cols.add("rar_creds_enc")
|
||||
# AUTO-FIX 6a ELIMINAT (US-013): NU mai adaugam accounts.rar_creds_enc — coloana e dropata.
|
||||
# _migrate_accounts_medii gestioneaza absenta coloana (guard la ~219: if "rar_creds_enc" not in acc_cols: return).
|
||||
# Medii RAR per cont (PRD 5.20 US-001): activare + slot creds + default, per mediu.
|
||||
_migrate_accounts_medii(conn, acc_cols)
|
||||
# US-013: DROP coloana legacy accounts.rar_creds_enc dupa backfill complet.
|
||||
_drop_legacy_accounts_rar_creds(conn, acc_cols)
|
||||
if "active" not in acc_cols:
|
||||
# Conturi existente raman active (default 1).
|
||||
conn.execute("ALTER TABLE accounts ADD COLUMN active INTEGER NOT NULL DEFAULT 1")
|
||||
@@ -229,6 +230,98 @@ def _migrate_accounts_medii(conn: sqlite3.Connection, acc_cols: set[str]) -> Non
|
||||
)
|
||||
|
||||
|
||||
def _drop_legacy_accounts_rar_creds(conn: sqlite3.Connection, acc_cols: set[str]) -> None:
|
||||
"""PRD 5.20 US-013: DROP coloana legacy `accounts.rar_creds_enc` dupa backfill complet.
|
||||
|
||||
Idempotent si sigur la fiecare boot (garda one-way: coloana absenta = nimic de facut).
|
||||
La eroare LOGHEAZA si lasa coloana pe loc (fail-safe — nu crapa boot-ul ambelor procese).
|
||||
Structura separata pentru testabilitate: `_garda_si_drop(conn)` expune pasul de
|
||||
assert+backup+DROP izolat (fara re-backfill 6c), apelabil direct din teste.
|
||||
"""
|
||||
if "rar_creds_enc" not in acc_cols:
|
||||
return # garda one-way: coloana deja dropata sau DB fresh
|
||||
try:
|
||||
_drop_legacy_rar_creds_impl(conn)
|
||||
except Exception as exc:
|
||||
print(
|
||||
f"[db] AVERTISMENT: DROP coloana legacy accounts.rar_creds_enc esuat: {exc}. "
|
||||
"Coloana ramane pe loc (fail-safe).",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
def _drop_legacy_rar_creds_impl(conn: sqlite3.Connection) -> None:
|
||||
"""Re-backfill (AUTO-FIX 6c) + delegate la _garda_si_drop.
|
||||
|
||||
Re-backfill-ul 6c acopera creds setate via POST /v1/conturi/rar-creds intre US-001
|
||||
si US-013 (pot fi DOAR in coloana veche). Ancora globala: AUTOPASS_RAR_ENV.
|
||||
"""
|
||||
if sqlite3.sqlite_version_info < (3, 35, 0):
|
||||
print(
|
||||
f"[db] SQLite {sqlite3.sqlite_version} < 3.35.0 — DROP COLUMN nesuportat; "
|
||||
"coloana legacy accounts.rar_creds_enc ramane.",
|
||||
flush=True,
|
||||
)
|
||||
return
|
||||
|
||||
# AUTO-FIX 6c: re-backfill creds din coloana veche in slotul per-env (ancora globala).
|
||||
# Independent de _migrate_accounts_medii (care sare pe DB deja migrat cu guard newly_added).
|
||||
env = get_settings().rar_env if get_settings().rar_env in ("test", "prod") else "test"
|
||||
slot = f"rar_creds_{env}_enc"
|
||||
conn.execute(
|
||||
f"UPDATE accounts SET {slot}=rar_creds_enc, rar_{env}_enabled=1 "
|
||||
f"WHERE rar_creds_enc IS NOT NULL AND TRIM(rar_creds_enc)<>'' "
|
||||
f"AND ({slot} IS NULL OR TRIM({slot})='')"
|
||||
)
|
||||
|
||||
_garda_si_drop(conn)
|
||||
|
||||
|
||||
def _garda_si_drop(conn: sqlite3.Connection) -> None:
|
||||
"""Garda de siguranta + backup + DROP accounts.rar_creds_enc. Testabila izolat.
|
||||
|
||||
Verifica ca niciun cont nu are creds DOAR in coloana veche (ambele sloturi per-env goale).
|
||||
Daca exista orfane -> NU dropa (fail-safe: fara pierdere de date).
|
||||
Altfel: backup criptat, DROP, verificare PRAGMA (AUTO-FIX 6d).
|
||||
"""
|
||||
# Garda: orfane = cont cu creds in coloana veche DAR ambele sloturi per-env goale.
|
||||
orphan_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM accounts "
|
||||
"WHERE rar_creds_enc IS NOT NULL AND TRIM(rar_creds_enc)<>'' "
|
||||
"AND (rar_creds_test_enc IS NULL OR TRIM(rar_creds_test_enc)='') "
|
||||
"AND (rar_creds_prod_enc IS NULL OR TRIM(rar_creds_prod_enc)='')"
|
||||
).fetchone()[0]
|
||||
|
||||
if orphan_count > 0:
|
||||
print(
|
||||
f"[db] AVERTISMENT: {orphan_count} cont(uri) cu rar_creds_enc ne-copiat in niciun slot "
|
||||
"per-env. DROP anulat (fail-safe: fara pierdere de date).",
|
||||
flush=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Backup criptat inainte de DROP (blob-urile sunt deja criptate Fernet).
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS accounts_rar_creds_enc_backup "
|
||||
"(account_id INTEGER, rar_creds_enc TEXT, backed_up_at TEXT)"
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO accounts_rar_creds_enc_backup "
|
||||
"SELECT id, rar_creds_enc, datetime('now') FROM accounts "
|
||||
"WHERE rar_creds_enc IS NOT NULL"
|
||||
)
|
||||
|
||||
# DROP coloana legacy.
|
||||
conn.execute("ALTER TABLE accounts DROP COLUMN rar_creds_enc")
|
||||
|
||||
# AUTO-FIX 6d: verifica prin PRAGMA (pe tabela accounts, NU grep — submissions are aceeasi coloana).
|
||||
cols_after = {r["name"] for r in conn.execute("PRAGMA table_info(accounts)").fetchall()}
|
||||
if "rar_creds_enc" in cols_after:
|
||||
raise RuntimeError("DROP COLUMN rar_creds_enc esuat: coloana inca prezenta dupa ALTER TABLE")
|
||||
|
||||
print("[db] DROP coloana legacy accounts.rar_creds_enc: OK", flush=True)
|
||||
|
||||
|
||||
def _backfill_submissions_rar_env(conn: sqlite3.Connection) -> None:
|
||||
"""PRD 5.20 US-001 (AUTO-FIX G + E4/3): backfill rar_env + recompute idempotency_key.
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class PrezentareRequest(BaseModel):
|
||||
"""Body pentru POST /v1/prezentari — una sau mai multe prezentari + creds RAR.
|
||||
|
||||
`rar_credentials` e OPTIONAL: daca lipseste, worker-ul foloseste creds-urile RAR
|
||||
durabile salvate pe cont (`accounts.rar_creds_enc`, via POST /v1/conturi/rar-creds).
|
||||
durabile salvate pe cont (per-env: `accounts.rar_creds_{env}_enc`, via POST /v1/conturi/rar-creds).
|
||||
Trimite-le explicit doar cand vrei sa suprascrii creds-urile contului pe acea cerere.
|
||||
"""
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ CREATE TABLE IF NOT EXISTS accounts (
|
||||
-- vezi accounts.delete_account — randul ramane doar pentru audit).
|
||||
status TEXT NOT NULL DEFAULT 'active'
|
||||
CHECK (status IN ('pending','active','blocked','archived','deleted')),
|
||||
rar_creds_enc TEXT, -- LEGACY (PRD 5.20 US-013 dropeaza coloana): creds RAR durabile env-less
|
||||
-- Medii RAR per cont (PRD 5.20 US-001). Fiecare mediu = bifa de activare + slot creds.
|
||||
-- accounts.rar_creds_enc (legacy env-less) a fost dropata in US-013 (SQLite DROP COLUMN).
|
||||
-- medii_disponibile = enabled AND creds prezente (app/rar_env.py). Cont client nou =
|
||||
-- Productie on / Testare off (clientii declara real); contul operator se pune manual pe Testare.
|
||||
rar_test_enabled INTEGER NOT NULL DEFAULT 0 CHECK (rar_test_enabled IN (0, 1)),
|
||||
|
||||
@@ -294,13 +294,13 @@ def _get_acasa_context(request: Request, conn, account_id: int) -> dict:
|
||||
acct = account_or_default(account_id)
|
||||
|
||||
# Pas 1: are credentiale RAR configurate? + metadate cont (pentru banner incomplet)
|
||||
# Verifica atat coloana legacy rar_creds_enc cat si sloturile per-env (US-008, PRD 5.20).
|
||||
# US-013: citim exclusiv sloturile per-env (legacy accounts.rar_creds_enc a fost dropat).
|
||||
row = conn.execute(
|
||||
"SELECT id, name, cui, email, rar_creds_enc, rar_creds_test_enc, rar_creds_prod_enc "
|
||||
"SELECT id, name, cui, email, rar_creds_test_enc, rar_creds_prod_enc "
|
||||
"FROM accounts WHERE id=?", (acct,)
|
||||
).fetchone()
|
||||
are_creds = bool(row and (
|
||||
row["rar_creds_enc"] or row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
|
||||
row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
|
||||
))
|
||||
# Banner cont incomplet (US-002): contul nu are companie + email + CUI complete
|
||||
cont_incomplet = not _acct_is_complete(row) if row else False
|
||||
@@ -438,9 +438,9 @@ def _render_integrare(request: Request, conn, account_id: int) -> str:
|
||||
|
||||
acct = account_or_default(account_id)
|
||||
row_creds = conn.execute(
|
||||
"SELECT rar_creds_enc FROM accounts WHERE id=?", (acct,)
|
||||
"SELECT rar_creds_test_enc, rar_creds_prod_enc FROM accounts WHERE id=?", (acct,)
|
||||
).fetchone()
|
||||
are_creds = bool(row_creds and row_creds["rar_creds_enc"])
|
||||
are_creds = bool(row_creds and (row_creds["rar_creds_test_enc"] or row_creds["rar_creds_prod_enc"]))
|
||||
|
||||
row_key = conn.execute(
|
||||
"SELECT 1 FROM api_keys WHERE account_id=? AND active=1 LIMIT 1", (acct,)
|
||||
@@ -4241,7 +4241,7 @@ def _fetch_cont_env_state(conn, acct: int) -> dict:
|
||||
"""
|
||||
row = conn.execute(
|
||||
"SELECT rar_test_enabled, rar_prod_enabled, "
|
||||
"rar_creds_test_enc, rar_creds_prod_enc, rar_env_default, rar_creds_enc "
|
||||
"rar_creds_test_enc, rar_creds_prod_enc, rar_env_default "
|
||||
"FROM accounts WHERE id=?", (acct,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
@@ -4263,8 +4263,9 @@ def _fetch_cont_env_state(conn, acct: int) -> dict:
|
||||
medii.append("test")
|
||||
if prod_disponibil:
|
||||
medii.append("prod")
|
||||
# US-013: are_creds bazat EXCLUSIV pe sloturile per-env (legacy rar_creds_enc dropat).
|
||||
are_creds = bool(
|
||||
row["rar_creds_enc"] or row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
|
||||
row["rar_creds_test_enc"] or row["rar_creds_prod_enc"]
|
||||
)
|
||||
return {
|
||||
"are_creds": are_creds,
|
||||
@@ -4529,10 +4530,12 @@ def cont_rar_creds(
|
||||
)
|
||||
|
||||
enc = encrypt_creds({"email": email, "password": parola})
|
||||
# US-013: scrie in slotul per-env al ancorei globale (nu mai exista coloana legacy).
|
||||
_env_w = get_settings().rar_env if get_settings().rar_env in ("test", "prod") else "test"
|
||||
conn = get_connection()
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE accounts SET rar_creds_enc=? WHERE id=?",
|
||||
f"UPDATE accounts SET rar_creds_{_env_w}_enc=?, rar_{_env_w}_enabled=1 WHERE id=?",
|
||||
(enc, acct),
|
||||
)
|
||||
account_meta = _fetch_account_meta(conn, acct)
|
||||
|
||||
@@ -11,11 +11,12 @@ Ruleaza ca proces separat sub `restart: always` (docker compose).
|
||||
- lease/timeout pe randuri 'sending' orfane.
|
||||
- re-login la token expirat (401 mid-sesiune) — JWT 30h, retry NU plafonat la 30h.
|
||||
|
||||
Creds per-cerere: fiecare submission poarta creds RAR CRIPTATE (rar_creds_enc).
|
||||
Creds per-cerere: fiecare submission poarta creds RAR CRIPTATE (submissions.rar_creds_enc).
|
||||
Worker-ul face login per CONT cu acele creds, cache-uieste JWT (30h) in memorie si
|
||||
STERGE creds-urile contului dupa primul login reusit. Token-ul in memorie acopera
|
||||
STERGE creds-urile efemere dupa primul login reusit. Token-ul in memorie acopera
|
||||
restul trimiterilor; la restart token-ul se pierde si contul re-logheaza la urmatorul
|
||||
submission care aduce creds proaspete (degradare acceptata).
|
||||
submission care aduce creds proaspete (degradare acceptata). Fallback durabil: slotul
|
||||
per-env al contului (accounts.rar_creds_{env}_enc, US-013; coloana legacy dropata).
|
||||
Dev: `worker_use_test_creds` foloseste creds <test> cand submission-ul nu are enc.
|
||||
|
||||
Pornire: python -m app.worker
|
||||
@@ -161,8 +162,8 @@ def requeue_with_backoff(conn, settings: Settings, submission_id: int, *, reason
|
||||
def claim_one(conn) -> dict | None:
|
||||
"""Claim atomic 'queued' -> 'sending', respectand next_attempt_at. Intoarce randul sau None.
|
||||
|
||||
Randul include `account_id` si `rar_creds_enc` (creds RAR criptate) pentru
|
||||
login-ul per-cont din `run`.
|
||||
Randul include `account_id` si `submissions.rar_creds_enc` (creds RAR criptate efemere)
|
||||
pentru login-ul per-cont din `run`.
|
||||
"""
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
@@ -431,18 +432,17 @@ def _creds_for(claimed: dict, settings: Settings) -> dict | None:
|
||||
|
||||
|
||||
def _creds_from_account(conn, account_id: int, rar_env: str = "test") -> dict | None:
|
||||
"""Creds RAR durabile per-cont din slotul per-env, cu fallback la coloana legacy.
|
||||
"""Creds RAR durabile per-cont din slotul per-env (US-013 — coloana legacy dropata).
|
||||
|
||||
Canal web: creds in accounts.rar_creds_{rar_env}_enc (per-env). Fallback la
|
||||
accounts.rar_creds_enc (legacy, back-compat inainte de US-013 care dropa coloana veche).
|
||||
Canal web: creds in accounts.rar_creds_{rar_env}_enc (per-env, singurul slot valid).
|
||||
"""
|
||||
env_slot = f"rar_creds_{rar_env}_enc"
|
||||
row = conn.execute(
|
||||
f"SELECT {env_slot}, rar_creds_enc FROM accounts WHERE id=?", (account_id,)
|
||||
f"SELECT {env_slot} FROM accounts WHERE id=?", (account_id,)
|
||||
).fetchone()
|
||||
if not row:
|
||||
return None
|
||||
enc = row[env_slot] or row["rar_creds_enc"] # per-env intai, legacy fallback
|
||||
enc = row[env_slot]
|
||||
return decrypt_creds(enc) if enc else None
|
||||
|
||||
|
||||
@@ -450,16 +450,16 @@ def _keepalive_target(conn, settings: Settings) -> tuple[int | None, dict | None
|
||||
"""Un cont cu creds durabile pentru login-ul de proba (sau creds <test> in dev).
|
||||
|
||||
Ancora M2: cauta in slotul per-env al mediului `settings.rar_env` (ancora globala).
|
||||
Fallback la coloana legacy `rar_creds_enc` (back-compat inainte de US-013).
|
||||
Sare conturile ale caror creds NU se decripteaza sub cheia curenta — in dev
|
||||
`start.sh both` genereaza o cheie efemera noua la fiecare pornire.
|
||||
US-013: coloana legacy accounts.rar_creds_enc a fost dropata — se foloseste EXCLUSIV
|
||||
slotul per-env. Sare conturile ale caror creds NU se decripteaza sub cheia curenta
|
||||
(in dev `start.sh both` genereaza o cheie efemera noua la fiecare pornire).
|
||||
"""
|
||||
env_slot = f"rar_creds_{settings.rar_env}_enc"
|
||||
rows = conn.execute(
|
||||
f"SELECT id, {env_slot}, rar_creds_enc FROM accounts ORDER BY id"
|
||||
f"SELECT id, {env_slot} FROM accounts ORDER BY id"
|
||||
).fetchall()
|
||||
for row in rows:
|
||||
enc = row[env_slot] or row["rar_creds_enc"] # per-env intai, legacy fallback
|
||||
enc = row[env_slot]
|
||||
if not enc:
|
||||
continue
|
||||
creds = decrypt_creds(enc)
|
||||
|
||||
Reference in New Issue
Block a user