Codex on Andrea Mac mini commited on
Commit
1d244c9
·
1 Parent(s): 384b704

Add password gate for Phase I dashboard

Browse files
Files changed (3) hide show
  1. Dockerfile +7 -0
  2. README.md +17 -5
  3. app.py +150 -0
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+ COPY app.py /app/app.py
5
+
6
+ EXPOSE 7860
7
+ CMD ["python", "/app/app.py"]
README.md CHANGED
@@ -1,10 +1,22 @@
1
  ---
2
- title: Phase1 Aai Source Coverage Gate
3
- emoji: 🚀
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: docker
 
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Phase I AAI Dashboard Gate
 
 
 
3
  sdk: docker
4
+ app_port: 7860
5
  pinned: false
6
+ license: other
7
  ---
8
 
9
+ # Phase I AAI Dashboard Gate
10
+
11
+ Public HuggingFace Space with server-side HTTP Basic Auth for the private Phase I
12
+ AAI dashboard.
13
+
14
+ Required Space secrets:
15
+
16
+ - `DASHBOARD_PASSWORD`
17
+ - `HF_PRIVATE_READ_TOKEN`
18
+
19
+ Required Space variables:
20
+
21
+ - `DASHBOARD_USERNAME`
22
+ - `PRIVATE_DASHBOARD_URL`
app.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Password gate for the private Phase I AAI dashboard."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import base64
7
+ import hmac
8
+ import os
9
+ import time
10
+ import urllib.error
11
+ import urllib.request
12
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
13
+
14
+
15
+ USERNAME = os.environ.get("DASHBOARD_USERNAME", "richard")
16
+ PASSWORD = os.environ.get("DASHBOARD_PASSWORD", "")
17
+ HF_READ_TOKEN = os.environ.get("HF_PRIVATE_READ_TOKEN", "")
18
+ PRIVATE_DASHBOARD_URL = os.environ.get(
19
+ "PRIVATE_DASHBOARD_URL",
20
+ "https://huggingface.co/spaces/andreaparker/phase1-aai-source-coverage-dashboard/raw/main/index.html",
21
+ )
22
+ CACHE_TTL_SECONDS = int(os.environ.get("CACHE_TTL_SECONDS", "300"))
23
+
24
+ _cached_html: bytes | None = None
25
+ _cached_at = 0.0
26
+
27
+
28
+ def require_env() -> None:
29
+ missing = [
30
+ name
31
+ for name, value in {
32
+ "DASHBOARD_PASSWORD": PASSWORD,
33
+ "HF_PRIVATE_READ_TOKEN": HF_READ_TOKEN,
34
+ }.items()
35
+ if not value
36
+ ]
37
+ if missing:
38
+ raise RuntimeError(f"Missing required environment variable(s): {', '.join(missing)}")
39
+
40
+
41
+ def fetch_dashboard() -> bytes:
42
+ global _cached_html, _cached_at
43
+ now = time.time()
44
+ if _cached_html and now - _cached_at < CACHE_TTL_SECONDS:
45
+ return _cached_html
46
+
47
+ req = urllib.request.Request(
48
+ PRIVATE_DASHBOARD_URL,
49
+ headers={"Authorization": f"Bearer {HF_READ_TOKEN}"},
50
+ )
51
+ with urllib.request.urlopen(req, timeout=45) as response:
52
+ html = response.read()
53
+ _cached_html = html
54
+ _cached_at = now
55
+ return html
56
+
57
+
58
+ def parse_basic_auth(header: str) -> tuple[str, str] | None:
59
+ prefix = "Basic "
60
+ if not header.startswith(prefix):
61
+ return None
62
+ try:
63
+ decoded = base64.b64decode(header[len(prefix) :], validate=True).decode("utf-8")
64
+ except Exception:
65
+ return None
66
+ username, sep, password = decoded.partition(":")
67
+ if not sep:
68
+ return None
69
+ return username, password
70
+
71
+
72
+ class Handler(BaseHTTPRequestHandler):
73
+ server_version = "Phase1DashboardGate/1.0"
74
+
75
+ def log_message(self, fmt: str, *args: object) -> None:
76
+ print(f"{self.address_string()} - {fmt % args}")
77
+
78
+ def is_authorized(self) -> bool:
79
+ parsed = parse_basic_auth(self.headers.get("Authorization", ""))
80
+ if not parsed:
81
+ return False
82
+ username, password = parsed
83
+ return hmac.compare_digest(username, USERNAME) and hmac.compare_digest(password, PASSWORD)
84
+
85
+ def send_login(self) -> None:
86
+ body = b"Authentication required.\n"
87
+ self.send_response(401)
88
+ self.send_header("WWW-Authenticate", 'Basic realm="Phase I AAI Dashboard"')
89
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
90
+ self.send_header("Content-Length", str(len(body)))
91
+ self.end_headers()
92
+ self.wfile.write(body)
93
+
94
+ def do_GET(self) -> None:
95
+ if self.path == "/healthz":
96
+ body = b"ok\n"
97
+ self.send_response(200)
98
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
99
+ self.send_header("Content-Length", str(len(body)))
100
+ self.end_headers()
101
+ self.wfile.write(body)
102
+ return
103
+
104
+ if self.path not in {"/", "/index.html"}:
105
+ self.send_response(302)
106
+ self.send_header("Location", "/")
107
+ self.end_headers()
108
+ return
109
+
110
+ if not self.is_authorized():
111
+ self.send_login()
112
+ return
113
+
114
+ try:
115
+ html = fetch_dashboard()
116
+ except urllib.error.HTTPError as exc:
117
+ body = f"Could not fetch private dashboard: HTTP {exc.code}\n".encode("utf-8")
118
+ self.send_response(502)
119
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
120
+ self.send_header("Content-Length", str(len(body)))
121
+ self.end_headers()
122
+ self.wfile.write(body)
123
+ return
124
+ except Exception as exc:
125
+ body = f"Could not fetch private dashboard: {type(exc).__name__}\n".encode("utf-8")
126
+ self.send_response(502)
127
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
128
+ self.send_header("Content-Length", str(len(body)))
129
+ self.end_headers()
130
+ self.wfile.write(body)
131
+ return
132
+
133
+ self.send_response(200)
134
+ self.send_header("Content-Type", "text/html; charset=utf-8")
135
+ self.send_header("Cache-Control", "private, max-age=60")
136
+ self.send_header("Content-Length", str(len(html)))
137
+ self.end_headers()
138
+ self.wfile.write(html)
139
+
140
+
141
+ def main() -> None:
142
+ require_env()
143
+ port = int(os.environ.get("PORT", "7860"))
144
+ server = ThreadingHTTPServer(("0.0.0.0", port), Handler)
145
+ print(f"Serving password-gated dashboard on port {port}")
146
+ server.serve_forever()
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()