808 lines
32 KiB
Python
808 lines
32 KiB
Python
#!/usr/bin/env python3
|
|
"""Pleiades storefront — Flask app: signup/login, plans, BTCPay checkout, dashboard."""
|
|
import base64
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import time
|
|
from functools import wraps
|
|
|
|
import requests
|
|
from flask import (Flask, abort, flash, jsonify, redirect, render_template,
|
|
request, session, url_for)
|
|
|
|
import db
|
|
import iproyal
|
|
import hydraproxy
|
|
|
|
CFG = db.CFG
|
|
app = Flask(__name__)
|
|
app.secret_key = CFG["secret_key"]
|
|
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # mitigate CSRF on modern browsers
|
|
app.config["SESSION_COOKIE_HTTPONLY"] = True # block JS session theft (XSS)
|
|
|
|
# simple SQLite-backed rate limiter (shared across gunicorn workers)
|
|
def _client_ip():
|
|
xff = request.headers.get("X-Forwarded-For", "")
|
|
return xff.split(",")[0].strip() if xff else (request.remote_addr or "?")
|
|
|
|
|
|
def _rate_ok(ip, limit=10, window=60):
|
|
now = time.time()
|
|
d = db.get_db()
|
|
d.execute("DELETE FROM rate_limits WHERE ts < ?", (now - window,))
|
|
count = d.execute("SELECT COUNT(*) c FROM rate_limits WHERE ip=?", (ip,)).fetchone()["c"]
|
|
if count >= limit:
|
|
d.close()
|
|
return False
|
|
d.execute("INSERT INTO rate_limits (ip, ts) VALUES (?,?)", (ip, now))
|
|
d.commit()
|
|
d.close()
|
|
return True
|
|
|
|
BTCPAY = CFG["btcpay"]
|
|
PROXY = CFG["proxy_public"]
|
|
SITE_HOST = CFG.get("site_host", "clean-proxys.thetempleofdoom.com")
|
|
|
|
PRICE_USD_PER_GB = CFG.get("price_usd_per_gb", 8.0) # display only
|
|
|
|
# Dedicated IP catalog: product_id -> {name, desc, plans: {days: (iproyal_plan_id, price_sats)}}
|
|
# Overridable via config.json "dedicated_products" (JSON keys are strings -> converted).
|
|
_DEDICATED_DEFAULT = {
|
|
3: {"name": "Datacenter Dedicated", "desc": "Static datacenter IP — budget tier",
|
|
"plans": {30: (5, 10000), 60: (26, 20000), 90: (27, 30000)}},
|
|
9: {"name": "ISP Dedicated", "desc": "Clean residential-ISP IP — premium tier",
|
|
"plans": {30: (22, 22000), 60: (24, 44000), 90: (25, 66000)}},
|
|
}
|
|
|
|
|
|
def _load_dedicated_products():
|
|
raw = CFG.get("dedicated_products")
|
|
if not raw:
|
|
return _DEDICATED_DEFAULT
|
|
out = {}
|
|
for pid, p in raw.items():
|
|
plans = {int(d): (int(v[0]), int(v[1])) for d, v in p.get("plans", {}).items()}
|
|
out[int(pid)] = {"name": p.get("name", ""), "desc": p.get("desc", ""), "plans": plans}
|
|
return out or _DEDICATED_DEFAULT
|
|
|
|
|
|
DEDICATED_PRODUCTS = _load_dedicated_products()
|
|
|
|
|
|
# ---------- auth ----------
|
|
def current_user():
|
|
uid = session.get("uid")
|
|
if not uid:
|
|
return None
|
|
d = db.get_db()
|
|
row = d.execute("SELECT * FROM users WHERE id=?", (uid,)).fetchone()
|
|
d.close()
|
|
return dict(row) if row else None
|
|
|
|
|
|
def login_required(f):
|
|
@wraps(f)
|
|
def wrapper(*a, **kw):
|
|
if not current_user():
|
|
flash("Log in first.", "warn")
|
|
return redirect(url_for("login"))
|
|
return f(*a, **kw)
|
|
return wrapper
|
|
|
|
|
|
# ---------- BTCPay ----------
|
|
def create_invoice(amount_sats, order_id, meta=None):
|
|
data = {
|
|
"amount": amount_sats / 1e8,
|
|
"currency": "BTC",
|
|
"checkout": {
|
|
"redirectURL": f"https://{SITE_HOST}/order/{order_id}",
|
|
"redirectAutomatically": True,
|
|
},
|
|
"metadata": {"orderId": order_id},
|
|
}
|
|
if meta:
|
|
data["metadata"].update(meta)
|
|
r = requests.post(
|
|
f"{BTCPAY['url']}/api/v1/stores/{BTCPAY['store_id']}/invoices",
|
|
headers={"Authorization": f"token {BTCPAY['api_key']}"},
|
|
json=data, timeout=20, verify=False,
|
|
)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
|
|
def verify_webhook():
|
|
sig = request.headers.get("BTCPay-Sig", "")
|
|
if not sig.startswith("sha256="):
|
|
return False
|
|
expected = sig.split("=", 1)[1]
|
|
calc = hmac.new(BTCPAY["webhook_secret"].encode(), request.get_data(),
|
|
hashlib.sha256).hexdigest()
|
|
return hmac.compare_digest(calc, expected)
|
|
|
|
|
|
# ---------- routes ----------
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html", user=current_user())
|
|
|
|
|
|
@app.route("/signup", methods=["GET", "POST"])
|
|
def signup():
|
|
if request.method == "POST":
|
|
if not _rate_ok(_client_ip(), limit=20, window=300):
|
|
flash("Too many attempts. Slow down.", "warn")
|
|
return render_template("signup.html")
|
|
username = request.form.get("username", "").strip()
|
|
password = request.form.get("password", "")
|
|
if len(username) < 3 or len(password) < 6:
|
|
flash("Username 3+ chars, password 6+ chars.", "warn")
|
|
return render_template("signup.html")
|
|
d = db.get_db()
|
|
exists = d.execute("SELECT 1 FROM users WHERE username=?", (username,)).fetchone()
|
|
if exists:
|
|
d.close()
|
|
flash("Username taken.", "warn")
|
|
return render_template("signup.html")
|
|
proxy_pass = db.random_token(18)
|
|
d.execute(
|
|
"INSERT INTO users (username,password_hash,proxy_password_hash,proxy_password_plain,"
|
|
"active,region,country,city,sticky,balance_gb,created_at) VALUES (?,?,?,?,0,'','US','',1,0,?)",
|
|
(username, db.scrypt_hash(password), db.scrypt_hash(proxy_pass), proxy_pass,
|
|
int(time.time())))
|
|
d.commit()
|
|
uid = d.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()["id"]
|
|
d.close()
|
|
session["uid"] = uid
|
|
flash(f"Account created. Proxy password: {proxy_pass} (save it)", "ok")
|
|
return redirect(url_for("dashboard"))
|
|
return render_template("signup.html")
|
|
|
|
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
def login():
|
|
if request.method == "POST":
|
|
if not _rate_ok(_client_ip(), limit=20, window=300):
|
|
flash("Too many attempts. Slow down.", "warn")
|
|
return render_template("login.html")
|
|
username = request.form.get("username", "").strip()
|
|
password = request.form.get("password", "")
|
|
d = db.get_db()
|
|
row = d.execute("SELECT * FROM users WHERE username=?", (username,)).fetchone()
|
|
d.close()
|
|
if row and db.verify_hash(password, row["password_hash"]):
|
|
session["uid"] = row["id"]
|
|
return redirect(url_for("dashboard"))
|
|
flash("Bad credentials.", "warn")
|
|
return render_template("login.html")
|
|
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
session.clear()
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
@app.route("/plans")
|
|
def plans():
|
|
d = db.get_db()
|
|
ps = [dict(r) for r in d.execute("SELECT * FROM plans WHERE active=1 ORDER BY gb")]
|
|
d.close()
|
|
return render_template("plans.html", plans=ps, user=current_user())
|
|
|
|
|
|
@app.route("/buy/<int:plan_id>", methods=["POST"])
|
|
@login_required
|
|
def buy(plan_id):
|
|
d = db.get_db()
|
|
plan = d.execute("SELECT * FROM plans WHERE id=?", (plan_id,)).fetchone()
|
|
d.close()
|
|
if not plan:
|
|
abort(404)
|
|
order_id = db.random_token(12)
|
|
d = db.get_db()
|
|
d.execute("INSERT INTO orders (user_id,plan_id,invoice_id,amount_sats,status,created_at) "
|
|
"VALUES (?,?,?,?,'pending',?)",
|
|
(current_user()["id"], plan_id, order_id, plan["price_sats"], int(time.time())))
|
|
d.commit()
|
|
d.close()
|
|
inv = create_invoice(plan["price_sats"], order_id)
|
|
return redirect(inv["checkoutLink"])
|
|
|
|
|
|
@app.route("/order/<order_id>")
|
|
@login_required
|
|
def order(order_id):
|
|
d = db.get_db()
|
|
o = d.execute("SELECT * FROM orders WHERE invoice_id=? AND user_id=?",
|
|
(order_id, current_user()["id"])).fetchone()
|
|
d.close()
|
|
if not o:
|
|
abort(404)
|
|
return render_template("order.html", order=dict(o), user=current_user())
|
|
|
|
|
|
@app.route("/webhook/btcpay", methods=["POST"])
|
|
def webhook_btcpay():
|
|
if not verify_webhook():
|
|
abort(401)
|
|
payload = request.get_json(force=True)
|
|
if payload.get("type") not in ("InvoiceSettled", "InvoiceProcessing"):
|
|
return "ok"
|
|
order_id = (payload.get("metadata") or {}).get("orderId")
|
|
kind = (payload.get("metadata") or {}).get("kind")
|
|
if not order_id:
|
|
return "ok"
|
|
d = db.get_db()
|
|
if kind == "dedicated":
|
|
row = d.execute("SELECT * FROM dedicated_ips WHERE invoice_id=? AND status='pending_payment'",
|
|
(order_id,)).fetchone()
|
|
if row:
|
|
try:
|
|
plan_id = DEDICATED_PRODUCTS[row["product_id"]]["plans"][row["plan_days"]][0]
|
|
o = iproyal.create_order(row["product_id"], plan_id, row["location_id"], 1)
|
|
oid = o.get("id") or o.get("order_id") or (o.get("data") or {}).get("id")
|
|
d.execute("UPDATE dedicated_ips SET status='provisioning', iproyal_order_id=? WHERE id=?",
|
|
(oid, row["id"]))
|
|
d.commit()
|
|
app.logger.info("dedicated IPRoyal order %s created for dedicated_ips #%s", oid, row["id"])
|
|
except Exception as e:
|
|
app.logger.error("dedicated order failed: %s", e)
|
|
d.execute("UPDATE dedicated_ips SET status='failed' WHERE id=?", (row["id"],))
|
|
d.commit()
|
|
d.close()
|
|
return "ok"
|
|
o = d.execute("SELECT * FROM orders WHERE invoice_id=?", (order_id,)).fetchone()
|
|
if o and o["status"] != "paid":
|
|
plan = d.execute("SELECT * FROM plans WHERE id=?", (o["plan_id"],)).fetchone()
|
|
d.execute("UPDATE orders SET status='paid', paid_at=? WHERE id=?", (int(time.time()), o["id"]))
|
|
d.execute("UPDATE users SET balance_gb=balance_gb+?, active=1 WHERE id=?",
|
|
(plan["gb"], o["user_id"]))
|
|
d.commit()
|
|
d.close()
|
|
return "ok"
|
|
|
|
|
|
@app.route("/dashboard")
|
|
@login_required
|
|
def dashboard():
|
|
u = current_user()
|
|
d = db.get_db()
|
|
used = d.execute("SELECT COALESCE(SUM(bytes),0) s FROM usage_log WHERE user_id=?", (u["id"],)).fetchone()["s"]
|
|
deds = [dict(r) for r in d.execute("SELECT * FROM dedicated_ips WHERE user_id=? ORDER BY id DESC", (u["id"],)).fetchall()]
|
|
orders = [dict(r) for r in d.execute(
|
|
"SELECT o.*, p.name plan_name, p.gb plan_gb FROM orders o LEFT JOIN plans p ON o.plan_id=p.id "
|
|
"WHERE o.user_id=? ORDER BY o.id DESC LIMIT 20", (u["id"],)).fetchall()]
|
|
sessions = [dict(r) for r in d.execute(
|
|
"SELECT * FROM usage_log WHERE user_id=? ORDER BY id DESC LIMIT 10", (u["id"],)).fetchall()]
|
|
d.close()
|
|
socks = f"socks5://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['socks_port']}"
|
|
http = f"http://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['http_port']}"
|
|
return render_template("dashboard.html", user=u, used_gb=used / 1e9,
|
|
socks=socks, http=http, dedicated=deds, orders=orders, sessions=sessions)
|
|
|
|
|
|
@app.route("/dashboard/test")
|
|
@login_required
|
|
def dashboard_test():
|
|
"""Live proxy check: egress through the customer's own creds via the gateway."""
|
|
u = current_user()
|
|
proxy = f"socks5h://{u['username']}:{u['proxy_password_plain']}@127.0.0.1:{PROXY['socks_port']}"
|
|
try:
|
|
import subprocess
|
|
r = subprocess.run(["curl", "-s", "--max-time", "20", "-x", proxy,
|
|
"http://ip-api.com/json?fields=query,country,city,isp,status"],
|
|
capture_output=True, text=True, timeout=25)
|
|
return app.response_class(r.stdout or '{"status":"fail","message":"no response"}',
|
|
mimetype="application/json")
|
|
except Exception as e:
|
|
return jsonify({"status": "fail", "message": str(e)}), 502
|
|
|
|
|
|
@app.route("/dashboard/location", methods=["POST"])
|
|
@login_required
|
|
def set_location():
|
|
u = current_user()
|
|
country = request.form.get("country", "").strip().upper()
|
|
city = request.form.get("city", "").strip()
|
|
region = request.form.get("region", "").strip()
|
|
sticky = 1 if request.form.get("sticky") else 0
|
|
d = db.get_db()
|
|
d.execute("UPDATE users SET country=?, city=?, region=?, sticky=? WHERE id=?",
|
|
(country, city, region, sticky, u["id"]))
|
|
d.commit()
|
|
d.close()
|
|
flash("Location updated.", "ok")
|
|
return redirect(url_for("dashboard"))
|
|
|
|
|
|
@app.route("/dashboard/rotate", methods=["POST"])
|
|
@login_required
|
|
def rotate():
|
|
u = current_user()
|
|
newpass = db.random_token(18)
|
|
d = db.get_db()
|
|
d.execute("UPDATE users SET proxy_password_hash=?, proxy_password_plain=? WHERE id=?",
|
|
(db.scrypt_hash(newpass), newpass, u["id"]))
|
|
d.commit()
|
|
d.close()
|
|
flash(f"New proxy password: {newpass}", "ok")
|
|
return redirect(url_for("dashboard"))
|
|
|
|
|
|
@app.route("/dashboard/credentials", methods=["POST"])
|
|
@login_required
|
|
def change_credentials():
|
|
"""Let the customer set their own proxy username and/or password."""
|
|
u = current_user()
|
|
new_username = request.form.get("username", "").strip()
|
|
new_password = request.form.get("proxy_password", "").strip()
|
|
d = db.get_db()
|
|
changed = []
|
|
if new_username and new_username != u["username"]:
|
|
taken = d.execute("SELECT 1 FROM users WHERE username=? AND id!=?",
|
|
(new_username, u["id"])).fetchone()
|
|
if taken:
|
|
d.close()
|
|
flash("Username already taken.", "warn")
|
|
return redirect(url_for("dashboard"))
|
|
d.execute("UPDATE users SET username=? WHERE id=?", (new_username, u["id"]))
|
|
changed.append("username")
|
|
if new_password:
|
|
d.execute("UPDATE users SET proxy_password_hash=?, proxy_password_plain=? WHERE id=?",
|
|
(db.scrypt_hash(new_password), new_password, u["id"]))
|
|
changed.append("password")
|
|
d.commit()
|
|
d.close()
|
|
if changed:
|
|
flash("Proxy " + " & ".join(changed) + " updated.", "ok")
|
|
else:
|
|
flash("Nothing changed.", "warn")
|
|
return redirect(url_for("dashboard"))
|
|
|
|
|
|
# ---------- dedicated IPs ----------
|
|
@app.route("/dedicated")
|
|
def dedicated():
|
|
dc_locs = isp_locs = []
|
|
try:
|
|
dc_locs = iproyal.list_locations(3)
|
|
isp_locs = iproyal.list_locations(9)
|
|
except Exception as e:
|
|
flash(f"Location catalog unavailable: {e}", "warn")
|
|
return render_template("dedicated.html", products=DEDICATED_PRODUCTS,
|
|
dc_locs=dc_locs, isp_locs=isp_locs, user=current_user())
|
|
|
|
|
|
@app.route("/dedicated/locations")
|
|
def dedicated_locations():
|
|
pid = request.args.get("product", 3, type=int)
|
|
try:
|
|
return jsonify({"locations": [{"id": i, "name": n, "in_stock": s}
|
|
for i, n, s in iproyal.list_locations(pid)]})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 502
|
|
|
|
|
|
@app.route("/api/v1/dedicated/locations")
|
|
def api_dedicated_locations():
|
|
pid = request.args.get("product", 9, type=int)
|
|
try:
|
|
return jsonify({"locations": [{"id": i, "name": n, "in_stock": s}
|
|
for i, n, s in iproyal.list_locations(pid)]})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 502
|
|
|
|
|
|
@app.route("/dedicated/buy", methods=["POST"])
|
|
@login_required
|
|
def dedicated_buy():
|
|
pid = int(request.form.get("product", 3))
|
|
days = int(request.form.get("days", 30))
|
|
loc_id = int(request.form.get("location_id", 0))
|
|
loc_name = request.form.get("location_name", "")
|
|
if pid not in DEDICATED_PRODUCTS or days not in DEDICATED_PRODUCTS[pid]["plans"]:
|
|
flash("Bad product or duration.", "warn")
|
|
return redirect(url_for("dedicated"))
|
|
if not loc_id or not loc_name:
|
|
flash("Pick a location.", "warn")
|
|
return redirect(url_for("dedicated"))
|
|
_, price = DEDICATED_PRODUCTS[pid]["plans"][days]
|
|
oid = db.random_token(12)
|
|
d = db.get_db()
|
|
d.execute("INSERT INTO dedicated_ips (user_id,product_id,product_name,plan_days,location_id,"
|
|
"location_name,invoice_id,price_sats,status,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)",
|
|
(current_user()["id"], pid, DEDICATED_PRODUCTS[pid]["name"], days, loc_id, loc_name,
|
|
oid, price, "pending_payment", int(time.time())))
|
|
d.commit()
|
|
d.close()
|
|
inv = create_invoice(price, oid, meta={"kind": "dedicated"})
|
|
return redirect(inv["checkoutLink"])
|
|
|
|
|
|
@app.route("/dedicated/poll", methods=["POST"])
|
|
def dedicated_poll():
|
|
"""Admin hook: poll provisioning for pending dedicated orders."""
|
|
if not session.get("admin"):
|
|
abort(403)
|
|
ok, failed = poll_provisioning()
|
|
return jsonify({"provisioned": ok, "failed": failed})
|
|
|
|
|
|
def poll_provisioning():
|
|
"""Check pending IPRoyal orders, move confirmed ones to active. Returns (ok, failed)."""
|
|
d = db.get_db()
|
|
pending = d.execute("SELECT * FROM dedicated_ips WHERE status IN ('provisioning')").fetchall()
|
|
d.close()
|
|
ok = failed = 0
|
|
for row in pending:
|
|
try:
|
|
o = iproyal.get_order(row["iproyal_order_id"])
|
|
status = o.get("status")
|
|
if status == "confirmed":
|
|
pd = o.get("proxy_data") or {}
|
|
proxies = pd.get("proxies") or []
|
|
ports = pd.get("ports") or {}
|
|
if proxies:
|
|
p = proxies[0]
|
|
host = p.get("ip") or ""
|
|
user = p.get("username") or ""
|
|
pw = p.get("password") or ""
|
|
socks_port = str(ports.get("socks5", ""))
|
|
http_port = str(ports.get("http|https", ""))
|
|
d = db.get_db()
|
|
d.execute("UPDATE dedicated_ips SET status='active', proxy_host=?, proxy_port=?,"
|
|
"http_port=?, proxy_user=?, proxy_pass=?, expires_at=? WHERE id=?",
|
|
(host, socks_port, http_port, user, pw,
|
|
int(time.time()) + row["plan_days"] * 86400, row["id"]))
|
|
d.commit()
|
|
d.close()
|
|
ok += 1
|
|
elif status in ("refunded", "expired"):
|
|
d = db.get_db()
|
|
d.execute("UPDATE dedicated_ips SET status='failed' WHERE id=?", (row["id"],))
|
|
d.commit()
|
|
d.close()
|
|
failed += 1
|
|
except Exception as e:
|
|
app.logger.warning("poll order %s failed: %s", row["iproyal_order_id"], e)
|
|
# expire past-due active IPs
|
|
d = db.get_db()
|
|
d.execute("UPDATE dedicated_ips SET status='expired' WHERE status='active' "
|
|
"AND expires_at > 0 AND expires_at < ?", (int(time.time()),))
|
|
d.commit()
|
|
d.close()
|
|
return ok, failed
|
|
|
|
|
|
# ---------- HydraProxy provider (management-only, manual broker fulfillment) ----------
|
|
HYDRA_PRODUCTS = CFG.get("hydraproxy", {}).get("products", {})
|
|
|
|
|
|
@app.route("/hydraproxy")
|
|
def hydraproxy_page():
|
|
account = None
|
|
err = None
|
|
try:
|
|
account = hydraproxy.get_account_info()
|
|
except Exception as e:
|
|
err = str(e)
|
|
return render_template("hydraproxy.html", products=HYDRA_PRODUCTS,
|
|
account=account, err=err, user=current_user())
|
|
|
|
|
|
@app.route("/api/v1/hydraproxy/info")
|
|
def api_hydraproxy_info():
|
|
try:
|
|
account = hydraproxy.get_account_info()
|
|
locations = hydraproxy.get_locations()
|
|
return jsonify({"account": account, "locations": locations,
|
|
"products": HYDRA_PRODUCTS})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 502
|
|
|
|
|
|
# ---------- admin ----------
|
|
ADMIN_PASSWORD = CFG.get("admin_password", "")
|
|
|
|
|
|
@app.route("/admin", methods=["GET", "POST"])
|
|
def admin():
|
|
if request.method == "POST":
|
|
if request.form.get("password") == ADMIN_PASSWORD:
|
|
session["admin"] = True
|
|
else:
|
|
flash("Bad admin password.", "warn")
|
|
if not session.get("admin"):
|
|
return render_template("admin_login.html")
|
|
d = db.get_db()
|
|
stats = {
|
|
"users": d.execute("SELECT COUNT(*) c FROM users").fetchone()["c"],
|
|
"active": d.execute("SELECT COUNT(*) c FROM users WHERE active=1").fetchone()["c"],
|
|
"revenue_sats": d.execute("SELECT COALESCE(SUM(amount_sats),0) s FROM orders WHERE status='paid'").fetchone()["s"],
|
|
"pending_orders": d.execute("SELECT COUNT(*) c FROM orders WHERE status='pending'").fetchone()["c"],
|
|
"gb_sold": d.execute("SELECT COALESCE(SUM(gb),0) s FROM plans WHERE id IN "
|
|
"(SELECT plan_id FROM orders WHERE status='paid')").fetchone()["s"],
|
|
}
|
|
users = [dict(r) for r in d.execute("SELECT id,username,active,balance_gb,country,city,created_at "
|
|
"FROM users ORDER BY id DESC LIMIT 50")]
|
|
orders = [dict(r) for r in d.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 20")]
|
|
deds = [dict(r) for r in d.execute("SELECT * FROM dedicated_ips ORDER BY id DESC LIMIT 30")]
|
|
d.close()
|
|
try:
|
|
ipr_balance = iproyal.get_balance()
|
|
except Exception:
|
|
ipr_balance = "unavailable"
|
|
try:
|
|
hydra_acct = hydraproxy.get_account_info()
|
|
hydra_balance = hydra_acct.get("balance_usd", "unavailable")
|
|
except Exception:
|
|
hydra_balance = "unavailable"
|
|
low_balance = isinstance(ipr_balance, (int, float)) and \
|
|
ipr_balance < CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
|
|
return render_template("admin.html", stats=stats, users=users, orders=orders,
|
|
dedicated=deds, ipr_balance=ipr_balance, low_balance=low_balance,
|
|
hydra_balance=hydra_balance)
|
|
|
|
|
|
@app.route("/admin/credit", methods=["POST"])
|
|
def admin_credit():
|
|
if not session.get("admin"):
|
|
abort(403)
|
|
username = request.form.get("username", "").strip()
|
|
try:
|
|
gb = float(request.form.get("gb", 0))
|
|
except ValueError:
|
|
gb = 0
|
|
d = db.get_db()
|
|
d.execute("UPDATE users SET balance_gb=balance_gb+?, active=1 WHERE username=?", (gb, username))
|
|
d.commit()
|
|
d.close()
|
|
flash(f"Credited {gb} GB to {username}", "ok")
|
|
return redirect(url_for("admin"))
|
|
|
|
|
|
# ---------- agent discovery ----------
|
|
@app.route("/llms.txt")
|
|
def llms_txt():
|
|
body = f"""# {SITE_HOST}
|
|
|
|
> Clean residential SOCKS5/HTTP proxies. No KYC. Crypto checkout. Encrypted backend.
|
|
|
|
## What this service does
|
|
Sells residential SOCKS5 and HTTP CONNECT proxies with global geo-targeting
|
|
(country/city/region). Buy with Bitcoin (no KYC). Instant activation.
|
|
|
|
## Why agents use Clean Proxys
|
|
Agents that scrape, crawl, or access geo-restricted or rate-limited endpoints
|
|
fail fast on datacenter-flagged or shared IPs. Clean Proxys issues clean
|
|
residential IPs (not VPN, not datacenter) that bypass IP-based blocks, with
|
|
per-country/city targeting and sticky or rotating sessions. One POST provisions
|
|
a working proxy — no KYC, Bitcoin billing, programmatic end-to-end.
|
|
|
|
## Key facts
|
|
- Protocol: SOCKS5 (port {PROXY['socks_port']}), HTTP CONNECT (port {PROXY['http_port']})
|
|
- Auth: per-user username + password
|
|
- Geo: country, city, region targeting
|
|
- Sessions: sticky or rotating
|
|
- Billing: pay-per-GB, Bitcoin (BTCPay)
|
|
- Signup: username + password only (no email, no KYC)
|
|
|
|
## MCP (for agent clients)
|
|
- Streamable HTTP MCP server: {SITE_HOST}/mcp
|
|
- Tools: get_info, list_plans, buy_proxy, get_order_status, list_dedicated_locations
|
|
|
|
## API
|
|
- OpenAPI spec: {SITE_HOST}/openapi.json
|
|
- GET /api/v1/info -> service info (JSON)
|
|
- GET /api/v1/plans -> residential GB plans (JSON)
|
|
- POST /api/v1/proxy -> one-call purchase (JSON: plan_id, country, city, username, password) -> creds + BTCPay checkout URL
|
|
- GET /api/v1/order/<id> -> payment status (pending/paid)
|
|
- GET /api/v1/dedicated/locations?product=9 -> ISP dedicated locations (JSON)
|
|
- GET /api/v1/dedicated/locations?product=3 -> datacenter dedicated locations (JSON)
|
|
- Human signup: {SITE_HOST}/signup
|
|
- Dedicated IPs: {SITE_HOST}/dedicated (datacenter $1.80/30d + ISP $4/30d resold at markup)
|
|
|
|
## Connect (after signup + top-up)
|
|
socks5://USER:PASS@{PROXY['host']}:{PROXY['socks_port']}
|
|
"""
|
|
return app.response_class(body, mimetype="text/plain")
|
|
|
|
|
|
@app.route("/robots.txt")
|
|
def robots():
|
|
return app.response_class(
|
|
"User-agent: *\nAllow: /\n"
|
|
"User-agent: GPTBot\nAllow: /\n"
|
|
"User-agent: ClaudeBot\nAllow: /\n"
|
|
"User-agent: anthropic-ai\nAllow: /\n"
|
|
"User-agent: Google-Extended\nAllow: /\n"
|
|
"User-agent: PerplexityBot\nAllow: /\n"
|
|
"User-agent: CCBot\nAllow: /\n"
|
|
"Sitemap: https://%s/sitemap.xml\n" % SITE_HOST,
|
|
mimetype="text/plain")
|
|
|
|
|
|
@app.route("/ai-plugin.json")
|
|
def ai_plugin():
|
|
return jsonify({
|
|
"schema_version": "v1",
|
|
"name_for_human": "Clean Proxys",
|
|
"name_for_model": "clean_proxys",
|
|
"description_for_human": "Clean residential SOCKS5/HTTP proxies with global geo-targeting. No KYC, Bitcoin billing.",
|
|
"description_for_model": "Buy clean residential SOCKS5/HTTP proxies programmatically. "
|
|
"No KYC, Bitcoin billing, country/city geo-targeting, sticky or rotating sessions. "
|
|
"Use list_plans then buy_proxy to provision a proxy, poll get_order_status until paid, "
|
|
"then connect with the returned socks5/http credentials. "
|
|
"OpenAPI spec at /openapi.json; MCP server at /mcp.",
|
|
"api": {"type": "openapi", "url": f"https://{SITE_HOST}/openapi.json", "has_user_authentication": False},
|
|
"auth": {"type": "none"},
|
|
"contact_email": "indianaholmes1@icloud.com",
|
|
"legal_info_url": f"https://{SITE_HOST}/",
|
|
})
|
|
|
|
|
|
@app.route("/api/v1/info")
|
|
def api_info():
|
|
return jsonify({
|
|
"name": "Clean Proxys",
|
|
"host": SITE_HOST,
|
|
"protocols": ["socks5", "http"],
|
|
"socks_port": PROXY["socks_port"],
|
|
"http_port": PROXY["http_port"],
|
|
"auth": "username:password",
|
|
"billing": "bitcoin (btcpay), pay-per-GB",
|
|
"kyc": False,
|
|
"signup": f"https://{SITE_HOST}/signup",
|
|
"docs": f"https://{SITE_HOST}/llms.txt",
|
|
"value_proposition": "Clean residential IPs (not VPN/datacenter) with country/city "
|
|
"geo-targeting and sticky/rotating sessions — the standard egress "
|
|
"for agents doing scraping, crawling, or geo-restricted access. "
|
|
"One-call programmatic purchase, no KYC, Bitcoin billing.",
|
|
"dedicated_ips": {
|
|
"products": ["datacenter", "isp"],
|
|
"isp_locations": f"https://{SITE_HOST}/dedicated/locations?product=9",
|
|
"dc_locations": f"https://{SITE_HOST}/dedicated/locations?product=3",
|
|
"browse": f"https://{SITE_HOST}/dedicated",
|
|
},
|
|
})
|
|
|
|
|
|
@app.route("/api/v1/plans")
|
|
def api_plans():
|
|
d = db.get_db()
|
|
ps = [dict(r) for r in d.execute("SELECT id,name,gb,price_sats FROM plans WHERE active=1 ORDER BY gb")]
|
|
d.close()
|
|
return jsonify({"plans": ps})
|
|
|
|
|
|
@app.route("/api/v1/proxy", methods=["POST"])
|
|
def api_proxy():
|
|
"""Agent-facing one-call purchase: create account + invoice, return creds + checkout URL."""
|
|
if not _rate_ok(_client_ip(), limit=10, window=60):
|
|
return jsonify({"error": "rate limited"}), 429
|
|
data = request.get_json(silent=True) or {}
|
|
username = (data.get("username") or "").strip()
|
|
password = data.get("password") or ""
|
|
plan_id = data.get("plan_id") or data.get("plan")
|
|
country = (data.get("country") or "").strip().upper()
|
|
city = (data.get("city") or "").strip()
|
|
|
|
d = db.get_db()
|
|
plan = d.execute("SELECT * FROM plans WHERE id=?", (plan_id,)).fetchone() if plan_id else None
|
|
if not plan:
|
|
plan = d.execute("SELECT * FROM plans WHERE active=1 ORDER BY gb LIMIT 1").fetchone()
|
|
if not plan:
|
|
d.close()
|
|
return jsonify({"error": "no plans"}), 500
|
|
|
|
user_row = d.execute("SELECT * FROM users WHERE username=?", (username,)).fetchone() if username else None
|
|
if user_row is None:
|
|
username = username or ("u" + db.random_token(10))
|
|
password = password or db.random_token(16)
|
|
proxy_pass = db.random_token(18)
|
|
d.execute("INSERT INTO users (username,password_hash,proxy_password_hash,proxy_password_plain,"
|
|
"active,country,city,sticky,balance_gb,created_at) VALUES (?,?,?,?,0,?,?,1,0,?)",
|
|
(username, db.scrypt_hash(password), db.scrypt_hash(proxy_pass), proxy_pass,
|
|
country or "US", city, int(time.time())))
|
|
d.commit()
|
|
uid = d.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()["id"]
|
|
else:
|
|
uid = user_row["id"]
|
|
proxy_pass = user_row["proxy_password_plain"]
|
|
if country:
|
|
d.execute("UPDATE users SET country=?, city=? WHERE id=?", (country, city or "", uid))
|
|
d.commit()
|
|
|
|
order_id = db.random_token(12)
|
|
d.execute("INSERT INTO orders (user_id,plan_id,invoice_id,amount_sats,status,created_at) "
|
|
"VALUES (?,?,?,?,'pending',?)",
|
|
(uid, plan["id"], order_id, plan["price_sats"], int(time.time())))
|
|
d.commit()
|
|
d.close()
|
|
inv = create_invoice(plan["price_sats"], order_id)
|
|
|
|
return jsonify({
|
|
"username": username,
|
|
"password": password,
|
|
"proxy_password": proxy_pass,
|
|
"socks5": f"socks5://{username}:{proxy_pass}@{PROXY['host']}:{PROXY['socks_port']}",
|
|
"http": f"http://{username}:{proxy_pass}@{PROXY['host']}:{PROXY['http_port']}",
|
|
"country": country or "US",
|
|
"city": city,
|
|
"plan": {"id": plan["id"], "name": plan["name"], "gb": plan["gb"], "price_sats": plan["price_sats"]},
|
|
"invoice": {"id": order_id, "checkout_url": inv["checkoutLink"], "amount_sats": plan["price_sats"]},
|
|
"status_url": f"https://{SITE_HOST}/api/v1/order/{order_id}",
|
|
})
|
|
|
|
|
|
@app.route("/api/v1/order/<order_id>")
|
|
def api_order_status(order_id):
|
|
d = db.get_db()
|
|
o = d.execute("SELECT * FROM orders WHERE invoice_id=?", (order_id,)).fetchone()
|
|
d.close()
|
|
if not o:
|
|
return jsonify({"error": "not found"}), 404
|
|
return jsonify({"order_id": order_id, "status": o["status"], "amount_sats": o["amount_sats"]})
|
|
|
|
|
|
@app.route("/openapi.json")
|
|
def openapi():
|
|
spec = {
|
|
"openapi": "3.0.3",
|
|
"info": {
|
|
"title": "Clean Proxys API",
|
|
"version": "1.0.0",
|
|
"description": "Programmatic residential SOCKS5/HTTP proxy purchase. No KYC. "
|
|
"One-call provisioning with Bitcoin (BTCPay) billing. "
|
|
"Clean residential IPs (not VPN/datacenter) with geo-targeting — "
|
|
"the standard egress for agents doing scraping, crawling, or "
|
|
"geo-restricted access.",
|
|
},
|
|
"servers": [{"url": f"https://{SITE_HOST}"}],
|
|
"paths": {
|
|
"/api/v1/info": {"get": {"summary": "Service info", "responses": {"200": {"description": "ok"}}}},
|
|
"/api/v1/plans": {"get": {"summary": "List residential GB plans", "responses": {"200": {"description": "plans"}}}},
|
|
"/api/v1/proxy": {"post": {
|
|
"summary": "One-call purchase: create account + invoice, return proxy credentials",
|
|
"requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {
|
|
"plan_id": {"type": "integer", "description": "plan id from /api/v1/plans"},
|
|
"country": {"type": "string", "description": "ISO country code, e.g. US"},
|
|
"city": {"type": "string", "description": "optional city"},
|
|
"username": {"type": "string", "description": "optional; auto-generated if omitted"},
|
|
"password": {"type": "string", "description": "optional; auto-generated if omitted"},
|
|
}}}}},
|
|
"responses": {"200": {"description": "username, proxy_password, socks5/http strings, invoice checkout_url, status_url"}},
|
|
}},
|
|
"/api/v1/order/{order_id}": {"get": {
|
|
"summary": "Payment status (pending/paid)",
|
|
"parameters": [{"name": "order_id", "in": "path", "required": True, "schema": {"type": "string"}}],
|
|
"responses": {"200": {"description": "status"}},
|
|
}},
|
|
"/api/v1/dedicated/locations": {"get": {
|
|
"summary": "Dedicated IP location catalog",
|
|
"parameters": [{"name": "product", "in": "query", "schema": {"type": "integer", "enum": [3, 9]},
|
|
"description": "3=datacenter, 9=ISP"}],
|
|
"responses": {"200": {"description": "locations"}},
|
|
}},
|
|
},
|
|
}
|
|
return jsonify(spec)
|
|
|
|
|
|
@app.route("/sitemap.xml")
|
|
def sitemap():
|
|
urls = [f"https://{SITE_HOST}/{p}" for p in ["", "plans", "signup", "login", "llms.txt"]]
|
|
body = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
|
for u in urls:
|
|
body += f" <url><loc>{u}</loc></url>\n"
|
|
body += "</urlset>"
|
|
return app.response_class(body, mimetype="application/xml")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
db.init_db()
|
|
app.run(host="127.0.0.1", port=5000, debug=False)
|