141 lines
3.9 KiB
Python
141 lines
3.9 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
|
|
);
|
|
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("=")
|