#!/usr/bin/env python3 """Pleiades shared DB + auth helpers. Used by both gateway.py and app.py.""" import base64 import hashlib import json import os import sqlite3 import time CFG_PATH = os.environ.get("PLEIADES_CONFIG", "/opt/pleiades/config.json") CFG = json.load(open(CFG_PATH)) DB_PATH = CFG["db_path"] SCHEMA = """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, -- web login (scrypt) proxy_password_hash TEXT NOT NULL, -- proxy auth (scrypt) proxy_password_plain TEXT, -- shown to customer (once / dashboard) active INTEGER DEFAULT 0, -- 0 until first payment region TEXT DEFAULT '', country TEXT DEFAULT '', city TEXT DEFAULT '', sticky INTEGER DEFAULT 1, balance_gb REAL DEFAULT 0, created_at INTEGER ); CREATE TABLE IF NOT EXISTS plans ( id INTEGER PRIMARY KEY, name TEXT, gb REAL, price_sats INTEGER, active INTEGER DEFAULT 1 ); CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, plan_id INTEGER, invoice_id TEXT, amount_sats INTEGER, status TEXT DEFAULT 'pending', created_at INTEGER, paid_at INTEGER ); CREATE TABLE IF NOT EXISTS usage_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, bytes INTEGER, ts INTEGER ); CREATE TABLE IF NOT EXISTS dedicated_ips ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, product_id INTEGER, -- 3=DC, 9=ISP product_name TEXT, plan_days INTEGER, location_id INTEGER, location_name TEXT, iproyal_order_id INTEGER, invoice_id TEXT, status TEXT DEFAULT 'pending_payment', -- pending_payment/provisioning/active/expired/failed proxy_host TEXT, proxy_port TEXT, http_port TEXT, proxy_user TEXT, proxy_pass TEXT, price_sats INTEGER, created_at INTEGER, expires_at INTEGER ); """ def get_db(): db = sqlite3.connect(DB_PATH, check_same_thread=False) db.row_factory = sqlite3.Row return db def init_db(): db = get_db() db.executescript(SCHEMA) # seed default plans if empty cur = db.execute("SELECT COUNT(*) c FROM plans") if cur.fetchone()["c"] == 0: for p in [ ("Starter", 5, 25000), # 5 GB ~ 25k sats ("Standard", 25, 90000), # 25 GB ~ 90k sats ("Pro", 100, 250000), # 100 GB ~ 250k sats ]: db.execute("INSERT INTO plans (name, gb, price_sats) VALUES (?,?,?)", p) db.commit() db.close() def scrypt_hash(pw, salt=None): salt = salt or os.urandom(16) h = hashlib.scrypt(pw.encode(), salt=salt, n=16384, r=8, p=1) return base64.b64encode(salt).decode() + ":" + base64.b64encode(h).decode() def verify_hash(pw, stored): try: salt_b64, hash_b64 = stored.split(":") salt = base64.b64decode(salt_b64) h = base64.b64encode(hashlib.scrypt(pw.encode(), salt=salt, n=16384, r=8, p=1)).decode() return h == hash_b64 except Exception: return False def lookup_user_by_proxy(username, proxy_password): """Validate proxy auth. Returns user dict or None.""" db = get_db() row = db.execute( "SELECT * FROM users WHERE username=? AND active=1", (username,) ).fetchone() db.close() if not row: return None if not verify_hash(proxy_password, row["proxy_password_hash"]): return None if row["balance_gb"] <= 0: return None return dict(row) def add_usage(user_id, nbytes): db = get_db() db.execute("INSERT INTO usage_log (user_id, bytes, ts) VALUES (?,?,?)", (user_id, nbytes, int(time.time()))) gb = nbytes / (1024 * 1024 * 1024) db.execute("UPDATE users SET balance_gb = MAX(0, balance_gb - ?) WHERE id=?", (gb, user_id)) db.commit() db.close() def random_token(n=24): return base64.urlsafe_b64encode(os.urandom(n)).decode().rstrip("=")