boffire commited on
Commit
eaad188
Β·
verified Β·
1 Parent(s): 89f8edd

Create proxy.py

Browse files
Files changed (1) hide show
  1. proxy.py +107 -0
proxy.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import base64
3
+ import secrets
4
+ import httpx
5
+ from fastapi import FastAPI, Request, HTTPException
6
+ from fastapi.responses import RedirectResponse, Response
7
+ from urllib.parse import quote
8
+
9
+ app = FastAPI()
10
+ LT_INTERNAL = "http://127.0.0.1:5000"
11
+
12
+ CLIENT_ID = os.environ.get("OAUTH_CLIENT_ID")
13
+ CLIENT_SECRET = os.environ.get("OAUTH_CLIENT_SECRET")
14
+ SPACE_HOST = os.environ.get("SPACE_HOST", "localhost")
15
+ REDIRECT_URI = f"https://{SPACE_HOST}/auth/callback"
16
+
17
+ # In-memory session store (fine for a single-container Space)
18
+ sessions: dict[str, dict] = {}
19
+
20
+ # ── OAuth login ─────────────────────────────────────────────
21
+ @app.get("/auth/login")
22
+ async def auth_login():
23
+ state = secrets.token_urlsafe(32)
24
+ scope = quote("openid profile")
25
+ url = (
26
+ f"https://huggingface.co/oauth/authorize"
27
+ f"?client_id={CLIENT_ID}"
28
+ f"&redirect_uri={quote(REDIRECT_URI)}"
29
+ f"&response_type=code"
30
+ f"&scope={scope}"
31
+ f"&state={state}"
32
+ )
33
+ resp = RedirectResponse(url)
34
+ resp.set_cookie("oauth_state", state, max_age=600, httponly=True, samesite="lax")
35
+ return resp
36
+
37
+ # ── OAuth callback ──────────────────────────────────────────
38
+ @app.get("/auth/callback")
39
+ async def auth_callback(code: str, state: str):
40
+ # NOTE: In production, verify that `state` matches the cookie.
41
+ basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
42
+ async with httpx.AsyncClient() as client:
43
+ r = await client.post(
44
+ "https://huggingface.co/oauth/token",
45
+ data={
46
+ "grant_type": "authorization_code",
47
+ "code": code,
48
+ "redirect_uri": REDIRECT_URI,
49
+ },
50
+ headers={"Authorization": f"Basic {basic}"},
51
+ timeout=30.0,
52
+ )
53
+ if r.status_code != 200:
54
+ raise HTTPException(status_code=400, detail="HuggingFace OAuth failed")
55
+
56
+ token_data = r.json()
57
+ session_id = secrets.token_urlsafe(32)
58
+ sessions[session_id] = token_data
59
+
60
+ resp = RedirectResponse("/")
61
+ resp.set_cookie("lt_session", session_id, httponly=True, samesite="lax")
62
+ return resp
63
+
64
+ # ── Proxy everything ────────────────────────────────────────
65
+ @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"])
66
+ async def proxy(request: Request, path: str):
67
+ # Guard /suggest behind HF OAuth
68
+ if path == "suggest" and request.method == "POST":
69
+ session = request.cookies.get("lt_session")
70
+ if not session or session not in sessions:
71
+ raise HTTPException(
72
+ status_code=401,
73
+ detail="You must be logged in with a HuggingFace account to submit suggestions. "
74
+ "Visit /auth/login first."
75
+ )
76
+
77
+ # Forward to LibreTranslate
78
+ async with httpx.AsyncClient() as client:
79
+ url = f"{LT_INTERNAL}/{path}"
80
+ if request.query_params:
81
+ url = f"{url}?{request.query_params}"
82
+
83
+ body = await request.body()
84
+ headers = {
85
+ k: v for k, v in request.headers.items()
86
+ if k.lower() not in ("host", "content-length")
87
+ }
88
+
89
+ try:
90
+ lt_resp = await client.request(
91
+ method=request.method,
92
+ url=url,
93
+ headers=headers,
94
+ content=body,
95
+ timeout=60.0,
96
+ follow_redirects=True,
97
+ )
98
+ except Exception as e:
99
+ raise HTTPException(status_code=502, detail=f"LibreTranslate unreachable: {e}")
100
+
101
+ # Strip hop-by-hop headers
102
+ excluded = {"content-encoding", "transfer-encoding", "content-length"}
103
+ return Response(
104
+ content=lt_resp.content,
105
+ status_code=lt_resp.status_code,
106
+ headers={k: v for k, v in lt_resp.headers.items() if k.lower() not in excluded},
107
+ )