Self-serve proxy username+password change, config-driven dedicated pricing
This commit is contained in:
50
app.py
50
app.py
@@ -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():
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user