403Webshell
Server IP : 54.37.205.81  /  Your IP : 216.73.216.76
Web Server : nginx/1.22.1
System : Linux vps-249481fa 6.1.0-50-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.176-1 (2026-07-02) x86_64
User : debian ( 1000)
PHP Version : 8.2.32
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /opt/ccp-ai-gateway/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/ccp-ai-gateway/app.py.bak-profile-2026-03-16-170034
import os
import json
import time
import secrets
from datetime import datetime, timezone

import hashlib, secrets
from flask import Flask, request, jsonify, make_response, send_file
from flask_cors import CORS
import requests

# PDF deps
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm

# QR (optional but supported)
try:
    import qrcode
    HAS_QRCODE = True
except Exception:
    qrcode = None
    HAS_QRCODE = False

APP = Flask(__name__)
CORS(APP)

# ================== ENV ==================
ADMIN_TOKEN = os.environ.get("CCP_AI_ADMIN_TOKEN", "")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")

TOWER_PASSWORD = os.environ.get("CCP_TOWER_PASSWORD", "")
SESSION_SECRET = os.environ.get("CCP_SESSION_SECRET", "")

DATA_DIR = os.environ.get("CCP_AI_DATA_DIR", "/opt/ccp-ai-gateway/data")
USERS_FILE = os.path.join(DATA_DIR, "users.json")
USAGE_LOG = os.path.join(DATA_DIR, "ai_usage.jsonl")
CERT_USAGE_LOG = os.path.join(DATA_DIR, "cert_usage.jsonl")
BALANCES_FILE = os.path.join(DATA_DIR, "balances.json")
PRICING_FILE = os.path.join(DATA_DIR, "pricing.json")

# Public base URL (optional): e.g. https://copyrightchain.it/api/ai
PUBLIC_BASE_URL = os.environ.get("CCP_PUBLIC_BASE_URL", "").rstrip("/")

# Certificates folders
CERT_BASE_DIR = os.environ.get("CCP_CERT_BASE_DIR", os.path.join(DATA_DIR, "certificates", "base"))
CERT_BC_DIR   = os.environ.get("CCP_CERT_BLOCKCHAIN_DIR", os.path.join(DATA_DIR, "certificates", "blockchain"))

# Stima costo EUR (solo reporting, non billing)
COST_OPENAI_EUR = float(os.environ.get("CCP_COST_OPENAI_EUR_PER_REQ", "0.01"))
COST_GEMINI_EUR = float(os.environ.get("CCP_COST_GEMINI_EUR_PER_REQ", "0.04"))

os.makedirs(DATA_DIR, exist_ok=True)
os.makedirs(CERT_BASE_DIR, exist_ok=True)
os.makedirs(CERT_BC_DIR, exist_ok=True)

# ================== UTILS ==================
def _now_iso():
    return datetime.now(timezone.utc).isoformat()

def _json_load(path, default):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return default

def _json_save(path, obj):
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(obj, f, ensure_ascii=False, indent=2)
    os.replace(tmp, path)

def _append_jsonl(path, obj):
    with open(path, "a", encoding="utf-8") as f:
        f.write(json.dumps(obj, ensure_ascii=False) + "\n")

# ================== AUTH (admin header token) ==================
def require_admin_token(req):
    if not ADMIN_TOKEN:
        return True
    return req.headers.get("X-CCP-Token", "") == ADMIN_TOKEN

# ================== TOWER COOKIE SIGN ==================
def _sign(payload: dict) -> str:
    raw = json.dumps(payload, separators=(",", ":"), sort_keys=True)
    salt = secrets.token_urlsafe(12)
    import hashlib
    h = hashlib.sha256()
    h.update(SESSION_SECRET.encode("utf-8"))
    h.update(salt.encode("utf-8"))
    h.update(raw.encode("utf-8"))
    return f"{salt}.{h.hexdigest()}.{raw}"

def _verify(token: str):
    try:
        salt, digest, raw = token.split(".", 2)
        import hashlib
        h = hashlib.sha256()
        h.update(SESSION_SECRET.encode("utf-8"))
        h.update(salt.encode("utf-8"))
        h.update(raw.encode("utf-8"))
        if h.hexdigest() != digest:
            return None
        payload = json.loads(raw)
        if payload.get("exp") and time.time() > float(payload["exp"]):
            return None
        return payload
    except Exception:
        return None

def tower_session():
    tok = request.cookies.get("ccp_tower", "")
    # cookie jar può salvarlo tra doppi apici e con virgole escaped (\054)
    tok = (tok or "").strip('"').replace("\\054", ",")
    if not tok or not SESSION_SECRET:
        return None
    payload = _verify(tok)
    if not payload or payload.get("role") != "tower_admin":
        return None
    return payload

def _tower_guard():
    if not (TOWER_PASSWORD and SESSION_SECRET and tower_session()):
        return jsonify({"error": "unauthorized"}), 401
    return None

# ================== USERS ==================
def ensure_users_file():
    if not os.path.exists(USERS_FILE):
        _json_save(USERS_FILE, {"users": []})

def find_user_by_token(tok: str):
    tok = (tok or "").strip()
    if not tok:
        return None
    ensure_users_file()
    data = _json_load(USERS_FILE, {"users": []})
    for u in data.get("users", []):
        if (u.get("token") or "").strip() == tok:
            return u
    return None

def upsert_user(user: dict):
    ensure_users_file()
    data = _json_load(USERS_FILE, {"users": []})
    users = data.get("users", [])
    uid = str(user.get("id"))
    found = False
    for i, u in enumerate(users):
        if str(u.get("id")) == uid:
            users[i] = {**u, **user}
            found = True
            break
    if not found:
        users.append(user)
    data["users"] = users
    _json_save(USERS_FILE, data)

# ================== BALANCES (ABO) ==================
def ensure_balances_file():
    if not os.path.exists(BALANCES_FILE):
        _json_save(BALANCES_FILE, {"balances": {}})

def get_balance(uid: str) -> float:
    ensure_balances_file()
    data = _json_load(BALANCES_FILE, {"balances": {}})
    b = data.get("balances", {}).get(str(uid), 0.0)
    try:
        return float(b)
    except Exception:
        return 0.0

def set_balance(uid: str, amount: float):
    ensure_balances_file()
    data = _json_load(BALANCES_FILE, {"balances": {}})
    data.setdefault("balances", {})[str(uid)] = float(amount)
    _json_save(BALANCES_FILE, data)

def add_balance(uid: str, delta: float) -> float:
    cur = get_balance(uid)
    newv = float(cur) + float(delta)
    set_balance(uid, newv)
    return newv

# ================== PRICING (ABO per operazione) ==================
def ensure_pricing_file():
    if not os.path.exists(PRICING_FILE):
        _json_save(PRICING_FILE, {"default_op_cost_abo": 1.0, "operations": {"generic": 1.0}})

def get_pricing():
    ensure_pricing_file()
    data = _json_load(PRICING_FILE, {"default_op_cost_abo": 1.0, "operations": {"generic": 1.0}})
    data.setdefault("default_op_cost_abo", 1.0)
    data.setdefault("operations", {"generic": data["default_op_cost_abo"]})
    return data

def op_cost_abo(op: str) -> float:
    p = get_pricing()
    op = (op or "generic").strip().lower()
    ops = p.get("operations", {}) or {}
    if op in ops:
        try:
            return float(ops[op])
        except Exception:
            return float(p.get("default_op_cost_abo", 1.0))
    return float(p.get("default_op_cost_abo", 1.0))

def debit_or_reject(uid: str, op: str):
    cost = op_cost_abo(op)
    bal = get_balance(uid)
    if bal < cost:
        return False, bal, cost
    newb = add_balance(uid, -cost)
    return True, newb, cost

# ================== USAGE (log) ==================
def cert_find_seen(uid: str, request_id: str):
    """
    Ritorna dict log se già visto (con eventuale certificate_path), altrimenti None.
    """
    request_id = (request_id or "").strip()
    if not request_id:
        return None
    try:
        with open(CERT_USAGE_LOG, "r", encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    if str(row.get("user_id")) == str(uid) and str(row.get("request_id")) == request_id:
                        return row
                except Exception:
                    continue
    except Exception:
        return None
    return None

def cert_log_request(**kw):
    _append_jsonl(CERT_USAGE_LOG, {"ts": _now_iso(), **kw})

def log_ai_usage(**kw):
    _append_jsonl(USAGE_LOG, {"ts": _now_iso(), **kw})

def summarize_usage(days=30):
    since = time.time() - int(days) * 86400
    res = {"by_user": {}, "total_requests": 0, "total_ok": 0, "total_cost_eur": 0.0}
    if not os.path.exists(USAGE_LOG):
        return {**res, "days": int(days), "from": datetime.fromtimestamp(since, timezone.utc).isoformat(), "to": _now_iso()}

    with open(USAGE_LOG, "r", encoding="utf-8") as f:
        for line in f:
            try:
                row = json.loads(line)
                ts = datetime.fromisoformat(row["ts"]).timestamp()
                if ts < since:
                    continue
                uid = str(row.get("user_id", "unknown"))
                u = res["by_user"].setdefault(uid, {"requests": 0, "ok": 0, "cost_eur": 0.0, "providers": {}})
                u["requests"] += 1
                res["total_requests"] += 1
                if row.get("ok"):
                    u["ok"] += 1
                    res["total_ok"] += 1
                cost = float(row.get("cost_eur", 0.0) or 0.0)
                u["cost_eur"] += cost
                res["total_cost_eur"] += cost
                p = row.get("provider", "unknown")
                u["providers"][p] = u["providers"].get(p, 0) + 1
            except Exception:
                pass

    return {**res, "days": int(days), "from": datetime.fromtimestamp(since, timezone.utc).isoformat(), "to": _now_iso()}

# ================== PDF CERTIFICATE ==================
def _qr_to_temp_png(data: str, out_dir: str, filename: str) -> str:
    if not HAS_QRCODE:
        return ""
    try:
        img = qrcode.make(data)
        path = os.path.join(out_dir, filename)
        img.save(path)
        return path
    except Exception:
        return ""

def generate_base_certificate(payload: dict, uid: str, request_id: str) -> str:
    """
    Crea PDF base con campi:
    cert_id, work_id, deposit_date, author, original_filename, verify_url, file_hash
    """
    os.makedirs(CERT_BASE_DIR, exist_ok=True)

    cert_id = str(payload.get("cert_id", "")).strip()
    work_id = str(payload.get("work_id", "")).strip()
    deposit_date = str(payload.get("deposit_date", "")).strip()
    author = str(payload.get("author", "")).strip()
    original_filename = str(payload.get("original_filename", "")).strip()
    verify_url = str(payload.get("verify_url", "")).strip()
    file_hash = str(payload.get("file_hash", "")).strip()

    filename = f"cert_base_{uid}_{request_id}.pdf"
    fullpath = os.path.join(CERT_BASE_DIR, filename)

    # QR code temp
    qr_path = ""
    if verify_url:
        qr_path = _qr_to_temp_png(verify_url, CERT_BASE_DIR, f"qr_{uid}_{request_id}.png")

    c = canvas.Canvas(fullpath, pagesize=A4)
    w, h = A4

    # Header
    c.setFont("Helvetica-Bold", 20)
    c.drawString(2*cm, h - 2.5*cm, "COPYRIGHTCHAIN")
    c.setFont("Helvetica", 11)
    c.drawString(2*cm, h - 3.2*cm, "Certificato di Copyright - Deposito con Data Certa (BASE)")

    # Box dati
    y = h - 5.0*cm
    c.setFont("Helvetica-Bold", 12)
    c.drawString(2*cm, y, "Dati Certificato")
    y -= 0.8*cm

    c.setFont("Helvetica", 11)
    def line(label, value):
        nonlocal y
        if value:
            c.setFont("Helvetica-Bold", 10)
            c.drawString(2*cm, y, f"{label}:")
            c.setFont("Helvetica", 10)
            c.drawString(6.2*cm, y, value)
            y -= 0.6*cm

    line("Cert ID", cert_id or f"{uid}-{request_id}")
    line("Work ID", work_id)
    line("Deposit Date", deposit_date)
    line("Author / Owner", author or uid)
    line("Original filename", original_filename)
    line("File hash (SHA256)", file_hash)
    if verify_url:
        line("Verify URL", verify_url)

    # QR
    if qr_path and os.path.exists(qr_path):
        try:
            c.drawImage(qr_path, w - 6.5*cm, h - 7.5*cm, width=4.5*cm, height=4.5*cm, preserveAspectRatio=True, mask='auto')
            c.setFont("Helvetica", 8)
            c.drawRightString(w - 2*cm, h - 7.8*cm, "Scansiona per verificare")
        except Exception:
            pass

    # Footer
    c.setFont("Helvetica-Oblique", 9)
    c.drawString(2*cm, 2.0*cm, f"Generated at (UTC): {datetime.now(timezone.utc).isoformat()}")
    c.setFont("Helvetica", 8)
    c.drawString(2*cm, 1.4*cm, "Questo certificato attesta il deposito del contenuto e l'associazione univoca ai dati sopra indicati.")

    c.showPage()
    c.save()

    # cleanup QR temp (non obbligatorio)
    try:
        if qr_path and os.path.exists(qr_path):
            os.remove(qr_path)
    except Exception:
        pass

    return fullpath

# ================== CERT CORE ==================
def _resolve_uid_from_request(payload):
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok) if tok else None
    if user:
        return str(user["id"]), user, None
    uid = payload.get("user_id")
    if uid:
        return str(uid), None, None
    return None, None, (jsonify({"error": "unauthorized"}), 401)

def _build_public_cert_url(fullpath: str) -> str:
    if not PUBLIC_BASE_URL:
        return ""
    # esponiamo via endpoint /cert/file?path=... (più sotto)
    return f"{PUBLIC_BASE_URL}/cert/file?path={fullpath}"

def _cert_debit_core(payload):
    cert_type = str(payload.get("cert_type", "base")).strip().lower()
    request_id = str(payload.get("request_id", "")).strip()

    if cert_type not in ("base", "blockchain"):
        return jsonify({"error": "invalid_cert_type", "allowed": ["base", "blockchain"]}), 400
    if not request_id:
        return jsonify({"error": "missing_request_id"}), 400

    uid, user, err = _resolve_uid_from_request(payload)
    if err:
        return err

    op = "cert_blockchain" if cert_type == "blockchain" else "cert_base"

    # idempotenza robusta: se già visto MA file non esiste → rigenera senza addebito
    seen = cert_find_seen(uid, request_id)
    if seen:
        seen_path = str(seen.get("certificate_path", "") or "")
        expected = seen_path
        if not expected:
            # fallback: ricostruiamo path standard
            base_dir = CERT_BC_DIR if cert_type == "blockchain" else CERT_BASE_DIR
            expected = os.path.join(base_dir, f"cert_{cert_type}_{uid}_{request_id}.pdf") if cert_type == "blockchain" else os.path.join(base_dir, f"cert_base_{uid}_{request_id}.pdf")

        if expected and os.path.exists(expected):
            return jsonify({
                "ok": True,
                "user_id": uid,
                "cert_type": cert_type,
                "op": op,
                "idempotent_replay": True,
                "request_id": request_id,
                "balance_abo": get_balance(uid),
                "certificate_path": expected,
                "certificate_url": _build_public_cert_url(expected)
            })
        # se file manca: rigenera sotto senza addebito

    # addebito
    ok_debit, bal_before, cost_abo = debit_or_reject(uid, op)
    if not ok_debit:
        return jsonify({
            "error": "insufficient_balance",
            "op": op,
            "cert_type": cert_type,
            "cost_abo": cost_abo,
            "balance_abo": bal_before
        }), 402

    # genera PDF (solo base qui; blockchain potrai agganciare alla tua logica txid/ots)
    try:
        if cert_type == "base":
            certificate_path = generate_base_certificate(payload, uid, request_id)
        else:
            # placeholder: per ora creiamo un pdf "semplice" in dir blockchain
            os.makedirs(CERT_BC_DIR, exist_ok=True)
            certificate_path = os.path.join(CERT_BC_DIR, f"cert_blockchain_{uid}_{request_id}.pdf")
            c = canvas.Canvas(certificate_path, pagesize=A4)
            w, h = A4
            c.setFont("Helvetica-Bold", 18)
            c.drawString(2*cm, h - 3*cm, "CERTIFICATO BLOCKCHAIN (placeholder)")
            c.setFont("Helvetica", 11)
            c.drawString(2*cm, h - 4.2*cm, f"User ID: {uid}")
            c.drawString(2*cm, h - 5.0*cm, f"Request ID: {request_id}")
            c.drawString(2*cm, h - 5.8*cm, f"File Hash: {str(payload.get('file_hash','')).strip()}")
            c.drawString(2*cm, h - 6.6*cm, f"Verify URL: {str(payload.get('verify_url','')).strip()}")
            c.setFont("Helvetica-Oblique", 9)
            c.drawString(2*cm, 2.0*cm, f"Generated at (UTC): {_now_iso()}")
            c.showPage()
            c.save()
    except Exception as e:
        # se fallisce la generazione: rimborsa ABO
        add_balance(uid, cost_abo)
        log_ai_usage(
            user_id=uid, provider="gateway", ok=False, status=500, ms=0, cost_eur=0.0,
            op=op, abo_cost=cost_abo, cert_type=cert_type, request_id=request_id,
            error="certificate_generation_failed"
        )
        return jsonify({"error": "certificate_generation_failed", "details": str(e)}), 500

    # log certificato (idempotenza) SOLO dopo successo generazione
    cert_log_request(
        user_id=uid,
        request_id=request_id,
        op=op,
        cert_type=cert_type,
        abo_cost=cost_abo,
        certificate_path=certificate_path
    )

    # log generale
    log_ai_usage(
        user_id=uid, provider="gateway", ok=True, status=200, ms=0, cost_eur=0.0,
        op=op, abo_cost=cost_abo, cert_type=cert_type, request_id=request_id
    )

    return jsonify({
        "ok": True,
        "user_id": uid,
        "cert_type": cert_type,
        "op": op,
        "debited_abo": cost_abo,
        "idempotent_replay": False,
        "request_id": request_id,
        "balance_abo": get_balance(uid),
        "certificate_path": certificate_path,
        "certificate_url": _build_public_cert_url(certificate_path)
    })

# ================== ROUTES ==================
@APP.get("/health")
def health():
    return jsonify({
        "ok": True,
        "has_openai_key": bool(OPENAI_API_KEY),
        "has_gemini_key": bool(GEMINI_API_KEY),
        "has_qrcode": HAS_QRCODE,
        "tower_configured": bool(TOWER_PASSWORD and SESSION_SECRET)
    })

@APP.get("/openapi.json")
def openapi_json():
    return jsonify({
        "openapi": "3.0.0",
        "info": {"title": "CCP AI Gateway", "version": "1.0.0"},
        "paths": {
            "/health": {"get": {"summary": "Health"}},
            "/openapi.json": {"get": {"summary": "OpenAPI spec"}},
            "/docs": {"get": {"summary": "HTML docs"}},
            "/tower/login": {"post": {"summary": "Tower login"}},
            "/tower/users/upsert": {"post": {"summary": "Upsert user"}},
            "/tower/users/list": {"get": {"summary": "List users"}},
            "/tower/ai/stats": {"get": {"summary": "AI stats"}},
            "/tower/balances": {"get": {"summary": "Balances dump"}},
            "/tower/balances/topup": {"post": {"summary": "Topup"}},
            "/tower/balances/set": {"post": {"summary": "Set balance"}},
            "/tower/pricing": {"get": {"summary": "Get pricing"}},
            "/tower/pricing/set": {"post": {"summary": "Set pricing"}},
            "/me/balance": {"get": {"summary": "My balance"}},
            "/certify": {"post": {"summary": "Create & debit certificate (base/blockchain)"}},
            "/cert/debit": {"post": {"summary": "Debit certificate (compat)"}},
            "/cert/file": {"get": {"summary": "Download certificate by server path (needs tower or admin token)"}},
            "/text": {"post": {"summary": "AI text"}}
        }
    })

@APP.get("/docs")
def docs():
    html = """<!doctype html><html><head><meta charset="utf-8"><title>CCP AI Gateway Docs</title></head>
<body style="font-family:Arial,sans-serif;padding:20px">
<h2>CCP AI Gateway</h2>
<ul>
<li><a href="/health">/health</a></li>
<li><a href="/openapi.json">/openapi.json</a></li>
</ul>
<p>Endpoints principali: /tower/*, /certify, /text</p>
</body></html>"""
    return make_response(html, 200, {"Content-Type": "text/html; charset=utf-8"})

@APP.post("/tower/login")
def tower_login():
    data = request.get_json(silent=True) or {}
    if data.get("password") != TOWER_PASSWORD:
        return jsonify({"error": "unauthorized"}), 401
    tok = _sign({"role": "tower_admin", "exp": time.time() + 86400})
    resp = make_response(jsonify({"ok": True}))
    # su localhost http: non mettere Secure, altrimenti curl cookie-jar ok ma browser no.
    resp.set_cookie("ccp_tower", tok, httponly=True, secure=False, samesite="Lax", path="/")
    return resp

@APP.post("/tower/users/upsert")
def tower_users_upsert():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    if not data.get("id"):
        return jsonify({"error": "missing_id"}), 400
    user = {
        "id": str(data["id"]),
        "token": data.get("token", ""),
        "email": data.get("email", ""),
        "name": data.get("name", ""),
        "wallet": data.get("wallet", ""),
        "plan": data.get("plan", "free"),
        "ai_limit_monthly": int(data.get("ai_limit_monthly", 0) or 0),
        "updated_at": _now_iso()
    }
    upsert_user(user)
    return jsonify({"ok": True})

@APP.get("/tower/users/list")
def tower_users_list():
    g = _tower_guard()
    if g:
        return g
    ensure_users_file()
    return jsonify(_json_load(USERS_FILE, {"users": []}))

@APP.get("/tower/ai/stats")
def tower_ai_stats():
    g = _tower_guard()
    if g:
        return g
    days = int(request.args.get("days", "30") or "30")
    days = max(1, min(days, 365))
    return jsonify(summarize_usage(days))

@APP.get("/tower/balances")
def tower_balances():
    g = _tower_guard()
    if g:
        return g
    ensure_balances_file()
    return jsonify(_json_load(BALANCES_FILE, {"balances": {}}))

@APP.post("/tower/balances/topup")
def tower_balances_topup():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    uid = str(data.get("user_id", "")).strip()
    amount = data.get("amount", None)
    if not uid or amount is None:
        return jsonify({"error": "missing_params"}), 400
    try:
        amount = float(amount)
    except Exception:
        return jsonify({"error": "invalid_amount"}), 400
    newb = add_balance(uid, amount)
    return jsonify({"ok": True, "user_id": uid, "balance_abo": newb})

@APP.post("/tower/balances/set")
def tower_balances_set():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    uid = str(data.get("user_id", "")).strip()
    balance = data.get("balance", None)
    if not uid or balance is None:
        return jsonify({"error": "missing_params"}), 400
    try:
        balance = float(balance)
    except Exception:
        return jsonify({"error": "invalid_balance"}), 400
    set_balance(uid, balance)
    return jsonify({"ok": True, "user_id": uid, "balance_abo": balance})

@APP.get("/tower/pricing")
def tower_pricing_get():
    g = _tower_guard()
    if g:
        return g
    return jsonify(get_pricing())

@APP.post("/tower/pricing/set")
def tower_pricing_set():
    g = _tower_guard()
    if g:
        return g
    data = request.get_json(silent=True) or {}
    default_cost = data.get("default_op_cost_abo", None)
    operations = data.get("operations", None)

    cur = get_pricing()
    if default_cost is not None:
        try:
            cur["default_op_cost_abo"] = float(default_cost)
        except Exception:
            return jsonify({"error": "invalid_default_cost"}), 400

    if operations is not None:
        if not isinstance(operations, dict):
            return jsonify({"error": "invalid_operations"}), 400
        cleaned = {}
        for k, v in operations.items():
            try:
                cleaned[str(k).strip().lower()] = float(v)
            except Exception:
                continue
        if cleaned:
            cur["operations"] = cleaned

    _json_save(PRICING_FILE, cur)
    return jsonify({"ok": True, "pricing": cur})


# === CLIENT AUTH START ===
def find_user_by_email(email):
    ensure_users_file()
    email = str(email or "").strip().lower()
    data = _json_load(USERS_FILE, {"users": []})
    for u in data.get("users", []):
        if str(u.get("email", "")).strip().lower() == email:
            return u
    return None

def _user_public(u):
    return {
        "id": str(u.get("id", "")),
        "email": u.get("email", ""),
        "name": u.get("name", ""),
        "wallet": u.get("wallet", ""),
        "plan": u.get("plan", "free"),
        "ai_limit_monthly": int(u.get("ai_limit_monthly", 0) or 0),
        "updated_at": u.get("updated_at", "")
    }

def _pw_hash(raw):
    return hashlib.sha256(str(raw or "").encode("utf-8")).hexdigest()

def _make_user_id():
    return "USR-" + secrets.token_hex(4).upper()

@APP.post("/client/register")
def client_register():
    data = request.get_json(silent=True) or {}
    name = str(data.get("name", "")).strip()
    email = str(data.get("email", "")).strip().lower()
    password = str(data.get("password", "")).strip()
    wallet = str(data.get("wallet", "")).strip()

    if not name or not email or not password:
        return jsonify({"error": "missing_fields"}), 400
    if "@" not in email:
        return jsonify({"error": "invalid_email"}), 400
    if len(password) < 8:
        return jsonify({"error": "weak_password"}), 400
    if find_user_by_email(email):
        return jsonify({"error": "email_exists"}), 409

    uid = _make_user_id()
    token = secrets.token_hex(24)
    now = _now_iso()
    user = {
        "id": uid,
        "token": token,
        "email": email,
        "name": name,
        "wallet": wallet,
        "plan": "free",
        "ai_limit_monthly": 0,
        "password_hash": _pw_hash(password),
        "created_at": now,
        "updated_at": now
    }
    upsert_user(user)
    set_balance(uid, get_balance(uid))

    return jsonify({
        "ok": True,
        "token": token,
        "user": _user_public(user),
        "balance_abo": get_balance(uid),
        "pricing": get_pricing()
    })

@APP.post("/client/login")
def client_login():
    data = request.get_json(silent=True) or {}
    email = str(data.get("email", "")).strip().lower()
    password = str(data.get("password", "")).strip()

    if not email or not password:
        return jsonify({"error": "missing_fields"}), 400

    user = find_user_by_email(email)
    if not user:
        return jsonify({"error": "unauthorized"}), 401

    if str(user.get("password_hash", "")) != _pw_hash(password):
        return jsonify({"error": "unauthorized"}), 401

    uid = str(user.get("id", ""))
    return jsonify({
        "ok": True,
        "token": user.get("token", ""),
        "user": _user_public(user),
        "balance_abo": get_balance(uid),
        "pricing": get_pricing()
    })

@APP.get("/client/me")
def client_me():
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok)
    if not user:
        return jsonify({"error": "unauthorized"}), 401

    uid = str(user.get("id", ""))
    return jsonify({
        "ok": True,
        "user": _user_public(user),
        "balance_abo": get_balance(uid),
        "pricing": get_pricing()
    })

# === CLIENT AUTH END ===

@APP.get("/me/balance")
def me_balance():
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok)
    if not user:
        return jsonify({"error": "unauthorized"}), 401
    uid = str(user.get("id"))
    return jsonify({"ok": True, "user_id": uid, "balance_abo": get_balance(uid), "pricing": get_pricing()})

@APP.get("/cert/file")
def cert_file():
    """
    Download certificato dal server path.
    Sicurezza: consentito se tower session valida oppure header X-CCP-Token == ADMIN_TOKEN (se impostato).
    """
    allowed = False
    if tower_session():
        allowed = True
    elif ADMIN_TOKEN and request.headers.get("X-CCP-Token", "") == ADMIN_TOKEN:
        allowed = True

    if not allowed:
        return jsonify({"error": "unauthorized"}), 401

    path = (request.args.get("path", "") or "").strip()
    if not path:
        return jsonify({"error": "missing_path"}), 400
    # limita ai cert dir
    ap = os.path.abspath(path)
    if not (ap.startswith(os.path.abspath(CERT_BASE_DIR)) or ap.startswith(os.path.abspath(CERT_BC_DIR))):
        return jsonify({"error": "forbidden_path"}), 403
    if not os.path.exists(ap):
        return jsonify({"error": "not_found"}), 404
    return send_file(ap, mimetype="application/pdf", as_attachment=True, download_name=os.path.basename(ap))

@APP.post("/certify")
def certify():
    payload = request.get_json(silent=True) or {}
    return _cert_debit_core(payload)

@APP.post("/cert/debit")
def cert_debit():
    payload = request.get_json(silent=True) or {}
    return _cert_debit_core(payload)

@APP.post("/text")
def ai_text():
    payload = request.get_json(silent=True) or {}
    tok = request.headers.get("X-CCP-Token", "")
    user = find_user_by_token(tok)
    if not user:
        return jsonify({"error": "unauthorized"}), 401

    prompt = str(payload.get("prompt", "")).strip()
    if not prompt:
        return jsonify({"error": "missing_prompt"}), 400

    op = str(payload.get("op", "generic")).strip().lower()
    uid = str(user.get("id"))

    ok_debit, bal_before, cost_abo = debit_or_reject(uid, op)
    if not ok_debit:
        return jsonify({"error": "insufficient_balance", "op": op, "cost_abo": cost_abo, "balance_abo": bal_before}), 402

    t0 = time.time()
    r = requests.post(
        "https://api.openai.com/v1/responses",
        headers={"Authorization": f"Bearer {OPENAI_API_KEY}"},
        json={"model": "gpt-4.1-mini", "input": [{"role": "user", "content": [{"type": "input_text", "text": prompt}]}]},
        timeout=120
    )
    ms = int((time.time() - t0) * 1000)

    if r.status_code >= 400:
        add_balance(uid, cost_abo)
        log_ai_usage(user_id=uid, provider="openai", ok=False, status=r.status_code, ms=ms, cost_eur=0.0, op=op, abo_cost=cost_abo, error="openai_error")
        return jsonify({"error": "openai_error", "details": r.text}), r.status_code

    log_ai_usage(user_id=uid, provider="openai", ok=True, status=200, ms=ms, cost_eur=COST_OPENAI_EUR, op=op, abo_cost=cost_abo)

    try:
        j = r.json()
    except Exception:
        j = {}

    text = (j.get("output_text") or "").strip()
    if not text:
        out = j.get("output") or []
        if isinstance(out, list):
            parts = []
            for it in out:
                if isinstance(it, dict):
                    for c in (it.get("content") or []):
                        if isinstance(c, dict) and (c.get("text") or ""):
                            parts.append(c.get("text"))
            text = ("".join(parts)).strip()

    return jsonify({"ok": True, "text": text})

if __name__ == "__main__":
    APP.run("127.0.0.1", 5060)


Youez - 2016 - github.com/yon3zu
LinuXploit