Pleiades proxy store: SOCKS5 gateway + Flask storefront + BTCPay checkout
This commit is contained in:
120
db.py
Normal file
120
db.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/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
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
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("=")
|
||||
Reference in New Issue
Block a user