Files
pleiades/db.py

121 lines
3.3 KiB
Python

#!/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("=")