PRD 5.16 — propagare design finalizata (system font stack, fara IBM Plex self-hostat): - US-001/002/008: tokeni --font-ui/--font-mono (system stack) + scala --fs-*; zero @font-face si zero /static/fonts/; landing aliniat la acelasi stack - US-003: RAR online = dot compact in antet + meniu burger; banda rosie DOAR pe blocat (invariant zero-silent-failures pastrat) - US-010: antet "ROMFAST AUTOPASS" + nume service + /login brandeit 2 coloane + badge plan; meniu burger cu separatoare; gate strict pe is_authenticated - US-011: selector tema pill icon+eticheta (reuse THEMES) - US-004/005/006/007: bug-fix editor prestatii (picker cod+denumire, add_extra in mod operatii, cod ales se salveaza fara "+", Renunta inchide via closest) - US-012/013: landing Autentificare->/login; wizard import colapsat + 4 pasi pe tokeni - fix VERIFY E2E: contoare duplicate pe 390px (inline display:flex batea @media) -> CSS + test-lock PRD 5.17 — tipuri de cont + trial Pro 30z + enforcement DUR: - US-001/002/008: accounts.tier + trial_until (migrare aditiva defensiva); app/plans.py sursa unica (PLANS, FREE_MONTHLY_LIMIT=60, effective_tier(now injectabil), monthly_usage, CONSUMED_STATUSES); create_account trial Pro 30z; CLI set-tier (protejat id=1, audit) - US-003/004/005: enforce volum 60/luna INAINTE de build_key pe ambele canale (PLAN_LIMITA_LUNARA, 3 niveluri + log_event); gate API Pro+ (PLAN_FARA_API 403 actionabil); valideaza/nomenclator raman permise; downgrade lazy; flag AUTOPASS_ENFORCE_PLANS (kill-switch) - US-006: badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO pluralizat + banner one-time trial->Gratuit + pagina Cont Regresie: 1380 passed, 0 failed, 1 deselected (live). E2E browser pe 390/1280 confirmat. Backend trimitere (worker/masina stari/idempotenta/contract RAR) NEATINS. Lucrul 5.18 (corpus kNN) ramane separat, necomis. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
152 lines
6.6 KiB
Python
152 lines
6.6 KiB
Python
"""Configurare gateway. Env vars (prefix AUTOPASS_) + valori implicite.
|
|
|
|
NU stocheaza parole RAR. Credentialele RAR vin per-cerere de la ROAAUTO.
|
|
Helper-ul `load_test_credentials` citeste blocul <test> din settings.xml DOAR
|
|
pentru dev local / probe pe mediul de test.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import xml.etree.ElementTree as ET
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(env_prefix="AUTOPASS_", env_file=".env", extra="ignore")
|
|
|
|
# --- Bază de date ---
|
|
db_path: Path = ROOT / "data" / "autopass.db"
|
|
|
|
# --- Observabilitate / jurnal aplicatie ---
|
|
# Nivel minim al evenimentelor scrise in app_events + log text. Sub el, evenimentul
|
|
# e ignorat (best-effort). DEBUG|INFO|WARNING|ERROR|CRITICAL.
|
|
log_level: str = "INFO"
|
|
log_retention_days: int = 90
|
|
# Director pentru log-ul text rotativ (RotatingFileHandler in aplicatie).
|
|
# Fisier per-proces (app-api.log / app-worker.log) — rotatia nu e multiproces-safe.
|
|
log_dir: Path = ROOT / ".run"
|
|
log_file_max_bytes: int = 5_000_000
|
|
log_file_backup_count: int = 5
|
|
# Retentie randuri blocate (error/needs_data/needs_mapping). Mai scurt decat 90z
|
|
# ale `sent` — un blocat n-are valoare de audit.
|
|
blocked_retention_days: int = 30
|
|
|
|
# --- Securitate ---
|
|
# Enforcement auth API-key pe /v1/* protejat. False (dev/test): fara cheie ->
|
|
# cont implicit id=1. True (prod): fara cheie valida -> 401. O cheie PREZENTA
|
|
# dar invalida da 401 indiferent de flag.
|
|
require_api_key: bool = False
|
|
|
|
# Cheie Fernet pentru criptarea creds RAR efemere in submissions (zero-storage
|
|
# at rest). Nesetata -> cheie efemera la runtime (creds nu supravietuiesc
|
|
# restartului). In productie seteaz-o persistent. Genereaza:
|
|
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
|
creds_key: str | None = None
|
|
|
|
# --- Sesiuni web ---
|
|
# Secret semnat cookie sesiune. None -> efemer la fiecare restart (dev ok;
|
|
# in prod seteaza persistent ca si creds_key, altfel cookieurile se invalideaza
|
|
# la restart). Genereaza: python -c "import secrets; print(secrets.token_hex(32))"
|
|
session_secret: str | None = None
|
|
# True (IMPLICIT, sigur pentru prod): rutele web fara sesiune -> redirect /login;
|
|
# CSRF enforce. Pentru dev rapid pe contul implicit id=1,
|
|
# seteaza explicit AUTOPASS_WEB_AUTH_REQUIRED=false.
|
|
web_auth_required: bool = True
|
|
# True (prod, in spatele Cloudflare Tunnel TLS): cookie cu Secure flag.
|
|
# False (dev): cookie fara Secure, functioneaza pe HTTP.
|
|
session_https_only: bool = False
|
|
|
|
# --- Contact suport (US-001, PRD 5.12) ---
|
|
# Email/canal de contact afisat in mesaje catre utilizatori (ex. CUI duplicat la signup).
|
|
# Nesetat -> fallback la formularea generica fara canal concret.
|
|
support_email: str | None = None
|
|
|
|
# --- Notificare email admin la signup ---
|
|
# Nesetat (smtp_host None) -> notificarea e DEGRADATA (doar log SIGNUP).
|
|
smtp_host: str | None = None
|
|
smtp_port: int = 587
|
|
smtp_user: str | None = None
|
|
smtp_password: str | None = None
|
|
smtp_from: str | None = None
|
|
|
|
# --- Rate-limit signup + login ---
|
|
# Max cereri POST /signup per IP in fereastra de timp (in-proces, fara dependinta noua).
|
|
signup_rate_max: int = 5
|
|
signup_rate_window_s: int = 3600
|
|
# Max incercari POST /login per IP (brute-force parole). Fereastra impartita cu signup.
|
|
login_rate_max: int = 10
|
|
|
|
# --- RAR ---
|
|
rar_env: str = "test" # "test" | "prod"
|
|
rar_base_url_test: str = "https://apps.rarom.ro/test-rar-autopass"
|
|
rar_base_url_prod: str = "https://apps.rarom.ro/rar-autopass"
|
|
|
|
# WAF-ul RAR da 403 fara User-Agent de browser. Toate apelurile httpx il trimit.
|
|
http_user_agent: str = "Mozilla/5.0"
|
|
http_timeout_s: float = 30.0
|
|
|
|
# --- Worker ---
|
|
worker_poll_interval_s: float = 5.0
|
|
worker_heartbeat_stale_s: int = 30 # /healthz considera worker-ul mort peste atat
|
|
# Send DEZACTIVAT implicit (nu trimite la RAR). Activeaza-l explicit pentru
|
|
# proba end-to-end.
|
|
worker_send_enabled: bool = False
|
|
# Dev: foloseste creds <test> din settings.xml pt login worker. In productie
|
|
# creds vin per-cerere de la ROAAUTO — lasa False.
|
|
worker_use_test_creds: bool = False
|
|
worker_sending_lease_s: int = 120 # rand 'sending' mai vechi de atat = orfan (worker mort mid-POST)
|
|
worker_retry_base_s: int = 5 # backoff = base * 2^retry (plafonat la max)
|
|
worker_retry_max_s: int = 300
|
|
worker_max_retries: int = 8 # peste atat -> error + banner
|
|
|
|
# --- Planuri de cont (PRD 5.17) ---
|
|
# Enforcement DUR al limitelor de plan (volum + acces API). True (implicit) = activ.
|
|
# False = kill-switch de operare: sare toate gate-urile de plan (util pentru debugging
|
|
# sau rollback rapid fara revert de cod). Enforcement DUR e activ implicit de la deploy
|
|
# (decizie user 2026-06-28, decizia #22 autoplan): nu exista conturi legacy, produs in TESTE.
|
|
enforce_plans: bool = True
|
|
|
|
# --- Embeddings (sugestie mapare, Stratul 2 PRD 5.14) ---
|
|
# DEZACTIVAT implicit: prima folosire lazy-load-eaza modelul fastembed/ONNX
|
|
# (~230MB pe disc) sincron in thread-ul de cerere -> hang la prima cerere /mapari.
|
|
# Activeaza explicit in productie (start.sh/Docker/.env) cand vrei sugestii semantice.
|
|
# OFF pastreaza suita de teste rapida si /mapari instant (cade pe GOLD/SILVER+fuzzy).
|
|
embeddings_enabled: bool = False
|
|
|
|
@property
|
|
def rar_base_url(self) -> str:
|
|
return self.rar_base_url_prod if self.rar_env == "prod" else self.rar_base_url_test
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
return Settings()
|
|
|
|
|
|
def load_test_credentials(settings_xml: Path | None = None) -> dict | None:
|
|
"""Citeste credentialele <test> din settings.xml (dev local / probe test).
|
|
|
|
Intoarce {"email", "password"} sau None daca fisierul lipseste / e template.
|
|
NU se foloseste in productie — acolo creds vin per-cerere de la ROAAUTO.
|
|
"""
|
|
path = settings_xml or (ROOT / "settings.xml")
|
|
if not path.exists():
|
|
return None
|
|
try:
|
|
root = ET.parse(path).getroot()
|
|
node = root.find("./test/credentials")
|
|
if node is None:
|
|
return None
|
|
email = (node.findtext("email") or "").strip()
|
|
password = (node.findtext("password") or "").strip()
|
|
if not email or not password or email.startswith("EMAIL_"):
|
|
return None
|
|
return {"email": email, "password": password}
|
|
except ET.ParseError:
|
|
return None
|