b50289074d
gates / gates (push) Successful in 28s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
60 lines
4.8 KiB
Python
60 lines
4.8 KiB
Python
"""Exit items 1 and 2 of EXIT-TEST.md."""
|
||
import json, re, subprocess, sys, time
|
||
from sp import *
|
||
out = open(sys.argv[1], "w", buffering=1)
|
||
def say(*a):
|
||
s = " ".join(map(str, a)); print(s); out.write(s + "\n")
|
||
|
||
say(f"# items 1–2, {time.strftime('%FT%TZ', time.gmtime())} — 9202, throwaway familygate (forwardAuth), Grimmory v3.4.1 + MeTube 2026.09.29")
|
||
WS = ("Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Version: 13", "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==")
|
||
cases = [
|
||
(GM, "GET", "/", ("Accept: text/html",), None), (GM, "GET", "/", (), None), (GM, "GET", "/api/v1/books", (), None),
|
||
(GM, "GET", "/api/v1/healthcheck", (), None), (GM, "GET", "/api/v1/public-settings", (), None),
|
||
(GM, "POST", "/api/v1/setup", ("Content-Type: application/json",), '{"username":"x","password":"Stranger-1234","email":"x@x.hu","name":"x"}'),
|
||
(GM, "POST", "/api/v1/auth/login", ("Content-Type: application/json",), '{"username":"admin","password":"guess"}'),
|
||
(GM, "GET", "/ws/websocket", WS, None), (GM, "GET", "/assets/index.js", (), None), (GM, "GET", "/no-such-page", ("Accept: text/html",), None),
|
||
(MT, "GET", "/", ("Accept: text/html",), None), (MT, "GET", "/", (), None), (MT, "GET", "/history", (), None),
|
||
(MT, "POST", "/add", ("Content-Type: application/json",), '{"url":"https://example.com/v","quality":"best"}'),
|
||
(MT, "GET", "/socket.io/?EIO=4&transport=polling", (), None), (MT, "GET", "/socket.io/?EIO=4&transport=websocket", WS, None),
|
||
(MT, "GET", "/download/x.mp4", (), None), (MT, "GET", "/version", (), None),
|
||
]
|
||
say("## item 1 — a STRANGER (no cookie), from the LAN and through the simulated tunnel; GATE = the gate answered, APP = the app did")
|
||
app_answers = 0
|
||
for host, m, p, h, d in cases:
|
||
c, head, body = curl(host, p, method=m, data=d, hdrs=h)
|
||
hl = list(h)
|
||
tc, thead, tbody = tunnel(host, p, "198.51.100.66", None, "-X", m, *sum([["-H", x] for x in hl], []), *(["--data", d] if d else []))
|
||
g1, g2 = is_gate(c, head, body), is_gate(tc, thead, tbody)
|
||
app_answers += (not g1) + (not g2)
|
||
say(f" {host.split('.')[0]:9s} {m:4s} {p:42s} LAN {c} {'GATE' if g1 else 'APP!'} | tunnel {tc} {'GATE' if g2 else 'APP!'}")
|
||
say(f"item 1: {app_answers} app answers of {2*len(cases)} stranger requests -> {'PASS' if app_answers == 0 else 'FAIL'}")
|
||
|
||
say("## item 2 — family members with their OWN logins (not the dashboard password)")
|
||
c, ck, head = family_login(GM, "anna")
|
||
ma = re.search(r"(?i)max-age=(\d+)", head)
|
||
say(f" anna signs in on {GM}: {c}; cookie {'set' if ck else 'NONE'}; Max-Age {ma.group(1) if ma else '?'} s = {int(ma.group(1))/86400 if ma else 0:.0f} days; flags: "
|
||
f"{', '.join(f for f in ('HttpOnly','Secure','SameSite=Lax') if f.lower() in head.lower())}; Domain attr: {'YES' if re.search(r'(?i)set-cookie: felhom_family=[^\n]*domain=', head) else 'none (host-only)'}")
|
||
for p in ("/", "/api/v1/healthcheck"):
|
||
cc, hh, bb = curl(GM, p, cookie=ck, hdrs=("Accept: text/html",))
|
||
say(f" anna GET {GM}{p} -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP'}")
|
||
c2, ck2, head2 = family_login(MT, "bela")
|
||
say(f" bela signs in on {MT}: {c2}; cookie {'set' if ck2 else 'NONE'}")
|
||
for host, m, p, h, d in [(MT, "GET", "/", ("Accept: text/html",), None), (MT, "GET", "/socket.io/?EIO=4&transport=polling", (), None),
|
||
(MT, "GET", "/socket.io/?EIO=4&transport=websocket", WS, None), (MT, "GET", "/history", (), None)]:
|
||
cc, hh, bb = curl(host, p, method=m, cookie=ck2, hdrs=h, timeout=5)
|
||
say(f" bela {m} {host}{p} -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP'}")
|
||
cc, hh, bb = curl(GM, "/", cookie=ck2.replace("felhom_family", "felhom_family") if ck2 else None, hdrs=("Accept: text/html",))
|
||
say(f" bela's MeTube cookie sent to Grimmory -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP!'} (a session is per app host)")
|
||
cc, hh, bb = curl(GM, "/", cookie="felhom_family=" + "0"*64, hdrs=("Accept: text/html",))
|
||
say(f" a made-up session id -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP!'}")
|
||
subprocess.run([GSH, "9202", "docker restart spike-gate >/dev/null; sleep 3"], capture_output=True)
|
||
cc, hh, bb = curl(GM, "/", cookie=ck, hdrs=("Accept: text/html",))
|
||
say(f" after the gate RESTARTED, anna's cookie -> {cc} {'GATE' if is_gate(cc, hh, bb) else 'APP (session survived)'}")
|
||
lc, lh, lb = curl(GM, "/__family/logout", cookie=ck)
|
||
say(f" anna logs out -> {lc} {re.search(r'(?im)^location: (.*)$', lh).group(1).strip() if re.search(r'(?im)^location:', lh) else ''}")
|
||
cc, hh, bb = curl(GM, "/", cookie=ck, hdrs=("Accept: text/html",))
|
||
say(f" anna's OLD cookie after logout -> {cc} {'GATE (refused)' if is_gate(cc, hh, bb) else 'APP! (still in)'}")
|
||
c3, ck3, _ = family_login(GM, "anna", "wrong-password-x")
|
||
say(f" anna with a WRONG password -> {c3}, cookie {'set!' if ck3 else 'none'}")
|
||
json.dump({"anna_gm": family_login(GM, "anna")[1], "bela_mt": ck2}, open(f"{SPIKE}/cookies.json", "w")); import os; os.chmod(f"{SPIKE}/cookies.json", 0o600)
|