informed-decision/tools/smoke_browser_shell.py
tegwick 0e48355b9f Add verified browser login and human approval HTTP client
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-10 22:05:17 +02:00

54 lines
2.9 KiB
Python

"""Exercise the installed HTTP entrypoint with metadata-only output. Port 8080 must be free."""
import argparse, json, os, socket, subprocess, sys, time
from urllib.request import build_opener, HTTPRedirectHandler, ProxyHandler, Request
from urllib.error import HTTPError, URLError
from urllib.parse import urlsplit, parse_qs
from pathlib import Path
parser = argparse.ArgumentParser(description="Local auth-shell smoke; never follows the issuer redirect.")
parser.add_argument("--receipt", type=Path)
args = parser.parse_args()
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, *args): return None
opener = build_opener(ProxyHandler({}), NoRedirect())
with socket.socket() as sock:
sock.bind(("127.0.0.1", 8080))
env = {**os.environ, "INFD_KEYCAPE_ISSUER": "https://kc.coulomb.social"}
proc = subprocess.Popen([str(Path(sys.executable).with_name("informed-decision-web"))], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
checks = {}
try:
for attempt in range(50):
try:
with opener.open("http://127.0.0.1:8080/healthz", timeout=1) as r:
checks["installed_entrypoint_health"] = r.status == 200 and json.load(r) == {"status": "ok"}
break
except URLError:
if proc.poll() is not None: raise RuntimeError("service failed to start")
time.sleep(0.1)
assert checks.get("installed_entrypoint_health")
try: opener.open("http://127.0.0.1:8080/auth/start", timeout=2)
except HTTPError as r:
parsed=urlsplit(r.headers["Location"]); q=parse_qs(parsed.query)
checks["fixed_issuer_authorization_redirect"] = r.code==303 and parsed.scheme=="https" and parsed.netloc=="kc.coulomb.social" and parsed.path=="/authorize"
checks["exact_registered_callback_and_scopes"] = q["redirect_uri"]==["https://decisions.coulomb.social/auth/callback"] and q["scope"]==["openid approval:read approval:approve"]
checks["pkce_s256_and_browser_cookie"] = q["code_challenge_method"]==["S256"] and "Secure; HttpOnly; SameSite=Lax" in r.headers["Set-Cookie"]
r.close()
try: opener.open("http://127.0.0.1:8080/readyz", timeout=2)
except HTTPError as r:
checks["incomplete_approval_path_not_ready"] = r.code==503 and json.load(r)["reason"]=="approval_path_not_connected"
r.close()
try: opener.open(Request("http://127.0.0.1:8080/approvals/fixture/accept", data=b"{}"), timeout=2)
except HTTPError as r:
checks["binding_route_unavailable"] = r.code==404
r.close()
assert len(checks)==6 and all(checks.values())
receipt={"status":"passed", "checks":checks,"issuer_contacted":False,"native_human_login":False,"model_calls":0}
if args.receipt:
args.receipt.write_text(json.dumps(receipt,indent=2)+"\n")
print(json.dumps(receipt,indent=2))
finally:
proc.terminate()
try: proc.communicate(timeout=5)
except subprocess.TimeoutExpired:
proc.kill(); proc.communicate()