Anirudh Esthuri commited on
Commit
0dad901
Β·
1 Parent(s): 88bde37

Add Hugging Face OAuth gate

Browse files
Files changed (1) hide show
  1. app.py +107 -0
app.py CHANGED
@@ -1,5 +1,9 @@
 
 
1
  from typing import cast
 
2
 
 
3
  import streamlit as st
4
 
5
  from gateway_client import delete_profile, ingest_and_rewrite
@@ -49,6 +53,105 @@ try:
49
  except FileNotFoundError:
50
  pass
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  # ──────────────────────────────────────────────────────────────
54
  # Sidebar
@@ -142,6 +245,10 @@ def append_user_turn(msgs: list[dict], new_user_msg: str) -> list[dict]:
142
  # Title
143
  # ──────────────────────────────────────────────────────────────
144
  st.title("MemMachine Chatbot")
 
 
 
 
145
 
146
 
147
  # ──────────────────────────────────────────────────────────────
 
1
+ import os
2
+ import secrets
3
  from typing import cast
4
+ from urllib.parse import urlencode
5
 
6
+ import requests
7
  import streamlit as st
8
 
9
  from gateway_client import delete_profile, ingest_and_rewrite
 
53
  except FileNotFoundError:
54
  pass
55
 
56
+ HF_CLIENT_ID = os.getenv("HF_OAUTH_CLIENT_ID")
57
+ HF_CLIENT_SECRET = os.getenv("HF_OAUTH_CLIENT_SECRET")
58
+ HF_REDIRECT_URI = os.getenv(
59
+ "HF_OAUTH_REDIRECT_URI", "https://memverge-memmachine-playground.hf.space/"
60
+ )
61
+ HF_SCOPES = os.getenv("HF_OAUTH_SCOPES", "openid profile email")
62
+ HF_AUTH_URL = "https://huggingface.co/oauth/authorize"
63
+ HF_TOKEN_URL = "https://huggingface.co/oauth/token"
64
+ HF_PROFILE_URL = "https://huggingface.co/api/whoami-v2"
65
+ HF_OAUTH_READY = bool(HF_CLIENT_ID and HF_CLIENT_SECRET and HF_REDIRECT_URI)
66
+
67
+
68
+ def build_authorize_url(state: str) -> str:
69
+ params = {
70
+ "client_id": HF_CLIENT_ID,
71
+ "response_type": "code",
72
+ "redirect_uri": HF_REDIRECT_URI,
73
+ "scope": HF_SCOPES,
74
+ "state": state,
75
+ }
76
+ return f"{HF_AUTH_URL}?{urlencode(params)}"
77
+
78
+
79
+ def exchange_code_for_token(code: str) -> dict | None:
80
+ payload = {
81
+ "grant_type": "authorization_code",
82
+ "code": code,
83
+ "client_id": HF_CLIENT_ID,
84
+ "client_secret": HF_CLIENT_SECRET,
85
+ "redirect_uri": HF_REDIRECT_URI,
86
+ }
87
+ try:
88
+ resp = requests.post(HF_TOKEN_URL, data=payload, timeout=15)
89
+ resp.raise_for_status()
90
+ return resp.json()
91
+ except Exception as e:
92
+ st.error(f"Failed to exchange auth code: {e}")
93
+ return None
94
+
95
+
96
+ def fetch_profile(access_token: str) -> dict | None:
97
+ try:
98
+ resp = requests.get(
99
+ HF_PROFILE_URL,
100
+ headers={"Authorization": f"Bearer {access_token}"},
101
+ timeout=10,
102
+ )
103
+ resp.raise_for_status()
104
+ return resp.json()
105
+ except Exception as e:
106
+ st.error(f"Failed to fetch Hugging Face profile: {e}")
107
+ return None
108
+
109
+
110
+ def ensure_authenticated() -> bool:
111
+ if not HF_OAUTH_READY:
112
+ return True
113
+
114
+ if "hf_profile" in st.session_state:
115
+ return True
116
+
117
+ query_params = st.experimental_get_query_params()
118
+ if "code" in query_params:
119
+ code = query_params["code"][0]
120
+ returned_state = query_params.get("state", [""])[0]
121
+ expected_state = st.session_state.get("hf_oauth_state", "")
122
+ if expected_state and returned_state != expected_state:
123
+ st.error("Authentication error: state mismatch. Please try again.")
124
+ st.experimental_set_query_params()
125
+ return False
126
+
127
+ token_data = exchange_code_for_token(code)
128
+ if token_data and token_data.get("access_token"):
129
+ st.session_state["hf_token"] = token_data["access_token"]
130
+ profile = fetch_profile(token_data["access_token"])
131
+ if profile:
132
+ st.session_state["hf_profile"] = profile
133
+ st.experimental_set_query_params()
134
+ return True
135
+
136
+ state = secrets.token_urlsafe(16)
137
+ st.session_state["hf_oauth_state"] = state
138
+ login_url = build_authorize_url(state)
139
+
140
+ st.markdown("### πŸ” Authentication Required")
141
+ st.write(
142
+ "Sign in with your Hugging Face account to continue using the MemMachine playground."
143
+ )
144
+ st.markdown(
145
+ f'<a href="{login_url}" target="_self"><button style="padding:0.6rem 1.6rem;border-radius:999px;background:#ffbe5c;border:none;font-size:1rem;font-weight:600;">Sign in with Hugging Face</button></a>',
146
+ unsafe_allow_html=True,
147
+ )
148
+ st.info("After logging in, you will be redirected back automatically.")
149
+ return False
150
+
151
+
152
+ if not ensure_authenticated():
153
+ st.stop()
154
+
155
 
156
  # ──────────────────────────────────────────────────────────────
157
  # Sidebar
 
245
  # Title
246
  # ──────────────────────────────────────────────────────────────
247
  st.title("MemMachine Chatbot")
248
+ if "hf_profile" in st.session_state:
249
+ user = st.session_state["hf_profile"]
250
+ full_name = user.get("name") or user.get("fullname") or user.get("hf_username")
251
+ st.caption(f"Signed in as {full_name}")
252
 
253
 
254
  # ──────────────────────────────────────────────────────────────