Self-serve proxy username+password change, config-driven dedicated pricing

This commit is contained in:
drjones
2026-09-19 19:38:01 -07:00
parent 05b5a4aa61
commit 15c103eb65
2 changed files with 63 additions and 4 deletions

50
app.py
View File

@@ -43,8 +43,9 @@ SITE_HOST = CFG.get("site_host", "clean-proxys.thetempleofdoom.com")
PRICE_USD_PER_GB = CFG.get("price_usd_per_gb", 8.0) # display only PRICE_USD_PER_GB = CFG.get("price_usd_per_gb", 8.0) # display only
# Dedicated IP catalog: product_id -> {name, plans: {days: (iproyal_plan_id, price_sats)}} # Dedicated IP catalog: product_id -> {name, desc, plans: {days: (iproyal_plan_id, price_sats)}}
DEDICATED_PRODUCTS = { # Overridable via config.json "dedicated_products" (JSON keys are strings -> converted).
_DEDICATED_DEFAULT = {
3: {"name": "Datacenter Dedicated", "desc": "Static datacenter IP — budget tier", 3: {"name": "Datacenter Dedicated", "desc": "Static datacenter IP — budget tier",
"plans": {30: (5, 10000), 60: (26, 20000), 90: (27, 30000)}}, "plans": {30: (5, 10000), 60: (26, 20000), 90: (27, 30000)}},
9: {"name": "ISP Dedicated", "desc": "Clean residential-ISP IP — premium tier", 9: {"name": "ISP Dedicated", "desc": "Clean residential-ISP IP — premium tier",
@@ -52,6 +53,20 @@ DEDICATED_PRODUCTS = {
} }
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 ---------- # ---------- auth ----------
def current_user(): def current_user():
uid = session.get("uid") uid = session.get("uid")
@@ -308,6 +323,37 @@ def rotate():
return redirect(url_for("dashboard")) 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 ---------- # ---------- dedicated IPs ----------
@app.route("/dedicated") @app.route("/dedicated")
def dedicated(): def dedicated():

View File

@@ -30,8 +30,15 @@
<p class="muted" style="font-size:12px;margin-bottom:0">Proxy password is unique to you. Use it for proxy auth, not your web password.</p> <p class="muted" style="font-size:12px;margin-bottom:0">Proxy password is unique to you. Use it for proxy auth, not your web password.</p>
<button class="btn btn-ghost" id="testbtn" onclick="testProxy()" style="margin-top:12px">⚡ Test my proxy</button> <button class="btn btn-ghost" id="testbtn" onclick="testProxy()" style="margin-top:12px">⚡ Test my proxy</button>
<pre class="code" id="testout" style="display:none;margin-top:10px"></pre> <pre class="code" id="testout" style="display:none;margin-top:10px"></pre>
<form method="post" action="/dashboard/rotate" style="margin-top:14px"> <form method="post" action="/dashboard/credentials" style="margin-top:14px">
<input type="submit" value="Rotate proxy password"> <label>Proxy username</label>
<input type="text" name="username" value="{{ user.username }}" autocomplete="off">
<label>Proxy password</label>
<div class="copy" style="margin:0">
<input type="text" name="proxy_password" id="pwfield" placeholder="leave blank to keep" autocomplete="off">
<button type="button" onclick="randompw()">⚡ random</button>
</div>
<input type="submit" value="Update proxy credentials">
</form> </form>
</div> </div>
</div> </div>
@@ -105,6 +112,12 @@
<script> <script>
function copy(el){ el.select(); document.execCommand('copy'); } function copy(el){ el.select(); document.execCommand('copy'); }
function randompw(){
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789';
let s = '';
for (let i=0;i<18;i++) s += chars[Math.floor(Math.random()*chars.length)];
document.getElementById('pwfield').value = s;
}
async function testProxy(){ async function testProxy(){
const btn = document.getElementById('testbtn'); const btn = document.getElementById('testbtn');
const out = document.getElementById('testout'); const out = document.getElementById('testout');