feat(5.16+5.17): tipografie/antet branded + tipuri cont, planuri si enforcement
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>
This commit is contained in:
59
app/auth.py
59
app/auth.py
@@ -18,8 +18,9 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import secrets
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import Header, HTTPException, Request
|
||||
from fastapi import Depends, Header, HTTPException, Request
|
||||
|
||||
from .config import get_settings
|
||||
from .db import get_connection
|
||||
@@ -162,3 +163,59 @@ def resolve_account_id(
|
||||
_log_auth_esuat(request, plaintext, "cheie API invalida sau revocata")
|
||||
raise HTTPException(status_code=401, detail="cheie API invalida sau revocata")
|
||||
return account_id
|
||||
|
||||
|
||||
def require_api_access(
|
||||
account_id: int = Depends(resolve_account_id),
|
||||
) -> int:
|
||||
"""Dependency FastAPI (T4, PRD 5.17): verifica ca tier-ul efectiv permite accesul la API.
|
||||
|
||||
Reguli:
|
||||
- enforce_plans=False (kill-switch): sare verificarea.
|
||||
- dev id=1 cu require_api_key=False: bypass (dogfooding, testele existente nu pica).
|
||||
- Pro/Premium sau trial Pro activ: permit.
|
||||
- Free/Standard fara trial: 403 PLAN_FARA_API cu eroare 3 niveluri.
|
||||
|
||||
Refoloseste resolve_account_id (account_id deja rezolvat din cheie API).
|
||||
Se ataseaza ca Depends() pe rutele de ingestie API (POST /v1/prezentari,
|
||||
POST /v1/import, POST /v1/import/{id}/commit). valideaza + nomenclator raman libere.
|
||||
"""
|
||||
from .plans import PLANS, effective_tier
|
||||
from .errors import eroare as _eroare
|
||||
|
||||
settings = get_settings()
|
||||
# Kill-switch operare: sare toate gate-urile de plan.
|
||||
if not settings.enforce_plans:
|
||||
return account_id
|
||||
# Bypass pentru contul implicit dev (id=1) in modul fara cheie API obligatorie.
|
||||
# In prod (require_api_key=True), id=1 nu are bypass implicit (cheie = obligatorie).
|
||||
if not settings.require_api_key and account_id == DEFAULT_ACCOUNT_ID:
|
||||
return account_id
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT tier, trial_until FROM accounts WHERE id=?", (account_id,)
|
||||
).fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
et = effective_tier(row, now)
|
||||
if not PLANS[et].get("api_access"):
|
||||
from .observ import log_event
|
||||
log_event(
|
||||
"plan_api_refuzat",
|
||||
account_id=account_id,
|
||||
nivel="WARNING",
|
||||
mesaj=f"Acces API refuzat: tier efectiv={et}",
|
||||
context={"tier_efectiv": et},
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=_eroare(
|
||||
"PLAN_FARA_API",
|
||||
cauza=f"Tier efectiv: {et}. API disponibil pe Pro/Premium.",
|
||||
),
|
||||
)
|
||||
return account_id
|
||||
|
||||
Reference in New Issue
Block a user