Rigel: auth SOCKS5 frontend (atyp=3 length-prefix fix, UTC auth, per-upstream creds), full-creds dashboard, honest provision_proxy

This commit is contained in:
drjones
2026-09-09 22:08:34 -07:00
parent 90f7414172
commit a66fff6927
2 changed files with 295 additions and 10 deletions

41
app.py
View File

@@ -38,6 +38,10 @@ BTCPAY_URL = os.environ.get("BTCPAY_URL", "https://10.30.20.140")
BTCPAY_STORE = os.environ.get("BTCPAY_STORE_ID", "")
BTCPAY_TOKEN = os.environ.get("BTCPAY_API_KEY", "")
# Public SOCKS5 endpoint customers connect to (authenticated frontend on CT158)
PROXY_HOST = os.environ.get("PROXY_PUBLIC_HOST", "10.30.20.116")
PROXY_PORT = os.environ.get("PROXY_PUBLIC_PORT", "1081")
def db():
con = getattr(g, "_db", None)
if con is None:
@@ -108,7 +112,8 @@ def index():
user = current_user()
if not user:
return render_template_string(AUTH_HTML, mode="login", error="")
return render_template_string(DASH_HTML, user=user, locations=LOCATIONS, plans=PLANS)
return render_template_string(DASH_HTML, user=user, locations=LOCATIONS, plans=PLANS,
proxy_host=PROXY_HOST, proxy_port=PROXY_PORT)
@app.route("/health")
def health():
@@ -255,12 +260,12 @@ def btcpay_webhook():
return jsonify({"ok": True})
def provision_proxy(location, user, pw):
"""Best-effort: add user auth to the gost frontend for this location.
The gost SOCKS5 frontends need per-user auth; for now we record the creds
and the actual gost auth layer is wired per-location (see rigel-proxy setup)."""
# Placeholder — the real gost auth is applied via the proxy gateway config.
# Creds are persisted on the subscription row so the customer sees them.
app.logger.info(f"provision proxy {location} for {user}")
"""No per-node writes needed: the authenticated SOCKS5 frontend
(rigel-proxy.service on CT158) validates every connection live against the
subscriptions table, so persisting the row IS the provisioning step.
The upstream exits are dumb unauthenticated relays on the LAN."""
up = LOCATIONS.get(location, {}).get("upstream", "?")
app.logger.info(f"provisioned {location} (upstream {up}) for {user}")
# ── templates ───────────────────────────────────────────────────────────
AUTH_HTML = r"""
@@ -332,6 +337,10 @@ main{padding:28px;max-width:900px;margin:0 auto}
.subs{margin-top:32px}.subs h2{font-size:17px}.subs table{width:100%;border-collapse:collapse;font-size:13px}
.subs th,.subs td{padding:10px;text-align:left;border-bottom:1px solid #1e2742}.subs th{color:var(--muted);font-weight:500}
.badge{padding:3px 8px;border-radius:10px;font-size:11px}.active{background:#14321f;color:#2ecc71}.pending{background:#332a14;color:#f1c40f}.expired{background:#33171a;color:#e74c3c}
.subcard{background:var(--card);border:1px solid #1e2742;border-radius:12px;padding:18px;margin-top:14px}
.srow{font-size:15px;margin-bottom:6px}
.lbl{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-top:10px}
code{display:block;background:#0d1325;border:1px solid #26304f;border-radius:6px;padding:8px 10px;font-size:12px;color:#9fd0ff;word-break:break-all;margin-top:3px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
</style></head><body>
<header><h1><span>✦</span> Rigel <span style="color:var(--muted);font-weight:400;font-size:13px">— {{user["username"]}}</span></h1><button onclick="logout()">Logout</button></header>
<main>
@@ -359,9 +368,21 @@ async function logout(){await fetch('/api/logout',{method:'POST'});location.relo
async function loadSubs(){
let r=await fetch('/api/subscriptions').then(x=>x.json());
if(!r.length){document.getElementById('subs').innerHTML='<p style="color:var(--muted)">No subscriptions yet.</p>';return;}
let html='<table><tr><th>Location</th><th>Plan</th><th>Status</th><th>Expires</th><th>Proxy creds</th></tr>';
for(const s of r){html+=`<tr><td>${s.location}</td><td>${s.plan}</td><td><span class="badge ${s.status}">${s.status}</span></td><td>${s.expires_at||'—'}</td><td>${s.proxy_user||'—'}</td></tr>`;}
html+='</table>';document.getElementById('subs').innerHTML=html;
let h='';
for(const s of r){
h+=`<div class="subcard"><div class="srow"><b style="text-transform:capitalize">${s.location}</b> &middot; ${s.plan} &middot; <span class="badge ${s.status}">${s.status}</span></div>`;
if(s.status==='active'&&s.proxy_user){
h+=`<div class="lbl">SOCKS5 endpoint</div><code>{{proxy_host}}:{{proxy_port}}</code>`
+ `<div class="lbl">Username</div><code>${s.proxy_user}</code>`
+ `<div class="lbl">Password</div><code>${s.proxy_pass||'\u2014'}</code>`
+ `<div class="lbl">Expires (UTC)</div><code>${s.expires_at||'\u2014'}</code>`
+ `<div class="lbl">Test it</div><code>curl --socks5-hostname ${s.proxy_user}:${s.proxy_pass}@{{proxy_host}}:{{proxy_port}} https://api.ipify.org</code>`;
} else {
h+=`<div style="color:var(--muted);font-size:13px;margin-top:8px">Awaiting payment confirmation\u2026 credentials appear here automatically.</div>`;
}
h+='</div>';
}
document.getElementById('subs').innerHTML=h;
}
loadSubs();
</script></body></html>