114 lines
4.4 KiB
Python
114 lines
4.4 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
from html import escape as html_escape
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
|
|
GHOST_URL = "http://ghost:2368"
|
|
ALLOWED_EMAIL = os.environ["OPS_ALLOWED_EMAIL"].strip().lower()
|
|
CONTENT_API_KEY = os.environ["GHOST_CONTENT_API_KEY"].strip()
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_safe(self, **fields):
|
|
print(json.dumps(fields, sort_keys=True), file=sys.stderr, flush=True)
|
|
|
|
def redirect_to_ghost_404(self):
|
|
self.send_response(302)
|
|
self.send_header("Location", "/404/")
|
|
self.send_header("Cache-Control", "no-store")
|
|
self.end_headers()
|
|
|
|
def do_GET(self):
|
|
if self.path not in ("/ops", "/ops/"):
|
|
self.redirect_to_ghost_404()
|
|
return
|
|
|
|
headers = {
|
|
"Host": "news.sherlockhomeless.net",
|
|
"X-Forwarded-Proto": "https",
|
|
"User-Agent": self.headers.get("User-Agent", "ghost-ops-gate"),
|
|
}
|
|
cookie = self.headers.get("Cookie")
|
|
if cookie:
|
|
headers["Cookie"] = cookie
|
|
cookie_names = []
|
|
if cookie:
|
|
cookie_names = [part.split("=", 1)[0].strip() for part in cookie.split(";") if "=" in part]
|
|
|
|
try:
|
|
member_req = Request(f"{GHOST_URL}/members/api/member/", headers=headers)
|
|
with urlopen(member_req, timeout=10) as member_response:
|
|
self.log_safe(path=self.path, cookie_names=cookie_names, member_status=member_response.status)
|
|
if member_response.status != 200:
|
|
self.redirect_to_ghost_404()
|
|
return
|
|
member_payload = json.loads(member_response.read().decode("utf-8") or "{}")
|
|
member = member_payload.get("member") or member_payload
|
|
self.log_safe(path=self.path, member_payload_keys=sorted(member_payload.keys()), member_keys=sorted(member.keys()))
|
|
member_email = str(member.get("email", "")).lower()
|
|
self.log_safe(path=self.path, member_email_present=bool(member_email), member_email_match=member_email == ALLOWED_EMAIL)
|
|
if member_email != ALLOWED_EMAIL:
|
|
self.redirect_to_ghost_404()
|
|
return
|
|
|
|
req = Request(
|
|
f"{GHOST_URL}/ghost/api/content/posts/"
|
|
f"?key={CONTENT_API_KEY}"
|
|
"&filter=tag:hash-private-ops"
|
|
"&fields=title,slug,url,published_at,custom_excerpt"
|
|
"&order=published_at%20desc"
|
|
"&limit=50",
|
|
headers=headers,
|
|
)
|
|
with urlopen(req, timeout=10) as response:
|
|
posts_payload = json.loads(response.read().decode("utf-8") or "{}")
|
|
except (HTTPError, URLError, TimeoutError, json.JSONDecodeError):
|
|
self.redirect_to_ghost_404()
|
|
return
|
|
|
|
posts = posts_payload.get("posts") or []
|
|
if not posts:
|
|
self.redirect_to_ghost_404()
|
|
return
|
|
|
|
items = "\n".join(
|
|
f'<li style="margin:14px 0;"><a href="/ops/{post["slug"]}/" '
|
|
f'style="color:#1d4ed8;text-decoration:none;font-weight:700;">'
|
|
f'{html_escape(post["title"])}</a>'
|
|
f'<div style="color:#64748b;font-size:13px;margin-top:4px;">'
|
|
f'{html_escape((post.get("published_at") or "")[:10])}</div></li>'
|
|
for post in posts
|
|
)
|
|
body = f"""<!doctype html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="robots" content="noindex,nofollow">
|
|
<title>Private Ops</title>
|
|
</head>
|
|
<body style="margin:0;background:#f8fafc;color:#111827;font-family:Arial,Helvetica,sans-serif;">
|
|
<main style="max-width:860px;margin:0 auto;padding:42px 20px;">
|
|
<h1 style="font-size:32px;line-height:1.2;margin:0 0 22px;">Private Ops</h1>
|
|
<ul style="list-style:none;padding:0;margin:0;">{items}</ul>
|
|
</main>
|
|
</body>
|
|
</html>""".encode("utf-8")
|
|
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
self.send_header("Cache-Control", "private, no-store")
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, format, *args):
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
ThreadingHTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
|