Harshil Patel commited on
Commit
522d1b9
·
1 Parent(s): 1b65cc5

Make auth0 login and logout work

Browse files
Files changed (1) hide show
  1. main.py +188 -48
main.py CHANGED
@@ -1,13 +1,12 @@
1
- # ------------------------------ main.py ------------------------------
2
-
3
  import os, base64
4
  from dotenv import load_dotenv
5
  from fastapi import FastAPI, Request
6
- from fastapi.responses import RedirectResponse
 
7
  from starlette.middleware.sessions import SessionMiddleware
8
- from starlette.middleware.base import BaseHTTPMiddleware
9
  from authlib.integrations.starlette_client import OAuth
10
  import gradio as gr
 
11
  from src.manager.manager import GeminiManager
12
 
13
  # 1. Load environment --------------------------------------------------
@@ -31,36 +30,72 @@ oauth.register(
31
  # 3. FastAPI app -------------------------------------------------------
32
  app = FastAPI()
33
 
34
- # 3a. *Inner* auth‑gate middleware (needs session already populated)
35
- class RequireAuthMiddleware(BaseHTTPMiddleware):
36
- async def dispatch(self, request: Request, call_next):
37
- public = ("/login", "/auth", "/logout", "/static", "/assets", "/favicon")
38
- if any(request.url.path.startswith(p) for p in public):
39
- return await call_next(request)
40
- if "user" not in request.session:
41
- return RedirectResponse("/login")
42
- return await call_next(request)
43
 
44
- app.add_middleware(RequireAuthMiddleware) # Add **first** (inner)
45
- app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET_KEY) # Add **second** (outer)
 
 
 
 
 
 
 
46
 
47
  # 4. Auth routes -------------------------------------------------------
48
  @app.get("/login")
49
  async def login(request: Request):
50
- return await oauth.auth0.authorize_redirect(request, request.url_for("auth"), audience=AUTH0_AUDIENCE)
 
 
51
 
52
  @app.get("/auth")
53
  async def auth(request: Request):
54
- token = await oauth.auth0.authorize_access_token(request)
55
- request.session["user"] = token["userinfo"]
56
- return RedirectResponse("/")
 
 
 
57
 
58
  @app.get("/logout")
59
  async def logout(request: Request):
60
- request.session.clear()
61
- return RedirectResponse(
62
- f"https://{AUTH0_DOMAIN}/v2/logout?client_id={AUTH0_CLIENT_ID}&returnTo=http://localhost:7860/"
 
 
63
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  # 5. Gradio UI ---------------------------------------------------------
66
  _logo_b64 = base64.b64encode(open("HASHIRU_LOGO.png", "rb").read()).decode()
@@ -69,8 +104,74 @@ HEADER_HTML = f"""
69
  <img src='data:image/png;base64,{_logo_b64}' width='40' class='logo'/>
70
  <h1>HASHIRU AI</h1>
71
  </div>"""
72
- CSS = ".logo{margin-right:20px;}"
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  def run_model(message, history):
76
  history.append({"role": "user", "content": message})
@@ -81,37 +182,76 @@ def run_model(message, history):
81
  print("Summary:", m["content"])
82
  yield "", messages
83
 
84
-
85
  def update_model(name):
86
  print("Model changed to:", name)
87
 
88
-
89
  with gr.Blocks(css=CSS, fill_width=True, fill_height=True) as demo:
90
  model_manager = GeminiManager(gemini_model="gemini-2.0-flash")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  with gr.Column():
92
- with gr.Row():
93
- gr.Markdown(HEADER_HTML)
94
- model_dropdown = gr.Dropdown(
95
- [
96
- "HASHIRU",
97
- "Static-HASHIRU",
98
- "Cloud-Only HASHIRU",
99
- "Local-Only HASHIRU",
100
- "No-Economy HASHIRU",
101
- ],
102
- value="HASHIRU",
103
- interactive=True,
104
- )
105
- model_dropdown.change(update_model, model_dropdown)
106
- with gr.Row():
107
- chatbot = gr.Chatbot(
108
- avatar_images=("HASHIRU_2.png", "HASHIRU.png"),
109
- type="messages", show_copy_button=True, editable="user",
110
- placeholder="Type your message here…",
111
- )
112
- gr.ChatInterface(run_model, type="messages", chatbot=chatbot, additional_outputs=[chatbot], save_history=True)
113
-
114
- # Mount at root
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  gr.mount_gradio_app(app, demo, path="/")
116
 
117
  # 6. Entrypoint --------------------------------------------------------
 
 
 
1
  import os, base64
2
  from dotenv import load_dotenv
3
  from fastapi import FastAPI, Request
4
+ from fastapi.responses import RedirectResponse, JSONResponse, FileResponse
5
+ from fastapi.staticfiles import StaticFiles
6
  from starlette.middleware.sessions import SessionMiddleware
 
7
  from authlib.integrations.starlette_client import OAuth
8
  import gradio as gr
9
+ import requests
10
  from src.manager.manager import GeminiManager
11
 
12
  # 1. Load environment --------------------------------------------------
 
30
  # 3. FastAPI app -------------------------------------------------------
31
  app = FastAPI()
32
 
33
+ # Create static directory if it doesn't exist
34
+ os.makedirs("static/fonts/ui-sans-serif", exist_ok=True)
35
+ os.makedirs("static/fonts/system-ui", exist_ok=True)
36
+
37
+ # Mount static files directory
38
+ app.mount("/static", StaticFiles(directory="static"), name="static")
 
 
 
39
 
40
+ # Add session middleware (no auth requirement)
41
+ app.add_middleware(
42
+ SessionMiddleware,
43
+ secret_key=SESSION_SECRET_KEY,
44
+ session_cookie="session",
45
+ max_age=86400,
46
+ same_site="lax",
47
+ https_only=False
48
+ )
49
 
50
  # 4. Auth routes -------------------------------------------------------
51
  @app.get("/login")
52
  async def login(request: Request):
53
+ print("Session cookie:", request.cookies.get("session"))
54
+ print("Session data:", dict(request.session))
55
+ return await oauth.auth0.authorize_redirect(request, request.url_for("auth"), audience=AUTH0_AUDIENCE, prompt="login")
56
 
57
  @app.get("/auth")
58
  async def auth(request: Request):
59
+ try:
60
+ token = await oauth.auth0.authorize_access_token(request)
61
+ request.session["user"] = token["userinfo"]
62
+ return RedirectResponse("/")
63
+ except Exception as e:
64
+ return JSONResponse({"error": str(e)}, status_code=500)
65
 
66
  @app.get("/logout")
67
  async def logout(request: Request):
68
+ auth0_logout_url = (
69
+ f"https://{AUTH0_DOMAIN}/v2/logout"
70
+ f"?client_id={AUTH0_CLIENT_ID}"
71
+ f"&returnTo=http://localhost:7860/post-logout"
72
+ f"&federated"
73
  )
74
+ return RedirectResponse(auth0_logout_url)
75
+
76
+ @app.get("/post-logout")
77
+ async def post_logout(request: Request):
78
+ request.session.clear()
79
+ return RedirectResponse("/")
80
+
81
+ @app.get("/manifest.json")
82
+ async def manifest():
83
+ return JSONResponse({
84
+ "name": "HASHIRU AI",
85
+ "short_name": "HASHIRU",
86
+ "icons": [],
87
+ "start_url": "/",
88
+ "display": "standalone"
89
+ })
90
+
91
+ @app.get("/api/login-status")
92
+ async def api_login_status(request: Request):
93
+ if "user" in request.session:
94
+ user_info = request.session["user"]
95
+ user_name = user_info.get("name", user_info.get("email", "User"))
96
+ return {"status": f"Logged in: {user_name}"}
97
+ else:
98
+ return {"status": "Logged out"}
99
 
100
  # 5. Gradio UI ---------------------------------------------------------
101
  _logo_b64 = base64.b64encode(open("HASHIRU_LOGO.png", "rb").read()).decode()
 
104
  <img src='data:image/png;base64,{_logo_b64}' width='40' class='logo'/>
105
  <h1>HASHIRU AI</h1>
106
  </div>"""
 
107
 
108
+ CSS = """
109
+ .logo {
110
+ margin-right: 20px;
111
+ }
112
+ .login-status {
113
+ font-weight: bold;
114
+ margin-right: 20px;
115
+ padding: 8px;
116
+ border-radius: 4px;
117
+ background-color: #f0f0f0;
118
+ }
119
+ #profile-name {
120
+ background-color: #f97316; /* Orange */
121
+ color: white;
122
+ font-weight: bold;
123
+ padding: 10px 14px;
124
+ border-radius: 6px;
125
+ cursor: pointer;
126
+ user-select: none;
127
+ display: inline-block;
128
+ position: relative;
129
+ z-index: 10;
130
+ }
131
+ /* Fix profile menu */
132
+ #profile-menu {
133
+ position: absolute;
134
+ background: transparent;
135
+ border: 1px solid transparent;
136
+ border-radius: 8px;
137
+ padding: 10px;
138
+ z-index: 1000;
139
+ box-shadow: 0px 4px 8px rgba(0,0,0,0.1);
140
+ margin-top: 5px;
141
+ }
142
+ #profile-menu.hidden {
143
+ display: none;
144
+ }
145
+ #profile-menu button {
146
+ background-color: #f97316; /* Orange (Tailwind orange-500) */
147
+ border: none;
148
+ color: white;
149
+ font-size: 16px;
150
+ text-align: left;
151
+ width: 100%;
152
+ padding: 10px;
153
+ margin-bottom: 5px;
154
+ border-radius: 6px;
155
+ cursor: pointer;
156
+ transition: background-color 0.2s ease;
157
+ }
158
+ #profile-menu button:hover {
159
+ background-color: #ea580c; /* Darker orange on hover (Tailwind orange-600) */
160
+ }
161
+
162
+
163
+ /* Fix dropdown issues */
164
+ input[type="text"], select {
165
+ color: black !important;
166
+ }
167
+
168
+ /* Optional: limit dropdown scroll if options are long */
169
+ .gr-dropdown .gr-dropdown-options {
170
+ max-height: 200px;
171
+ overflow-y: auto;
172
+ }
173
+
174
+ """
175
 
176
  def run_model(message, history):
177
  history.append({"role": "user", "content": message})
 
182
  print("Summary:", m["content"])
183
  yield "", messages
184
 
 
185
  def update_model(name):
186
  print("Model changed to:", name)
187
 
 
188
  with gr.Blocks(css=CSS, fill_width=True, fill_height=True) as demo:
189
  model_manager = GeminiManager(gemini_model="gemini-2.0-flash")
190
+
191
+ with gr.Row():
192
+ gr.Markdown(HEADER_HTML)
193
+
194
+ with gr.Column(scale=1, min_width=250):
195
+ profile_html = gr.HTML(value="""
196
+ <div class="profile-container">
197
+ <div id="profile-name" class="profile-btn">Guest</div>
198
+ <div id="profile-menu" class="dropdown hidden">
199
+ <button onclick="window.location.href='/login'">🔐 Login</button>
200
+ <button onclick="window.location.href='/logout'">🚪 Logout</button>
201
+ </div>
202
+ </div>
203
+ """)
204
+
205
  with gr.Column():
206
+ model_dropdown = gr.Dropdown(
207
+ [
208
+ "HASHIRU",
209
+ "Static-HASHIRU",
210
+ "Cloud-Only HASHIRU",
211
+ "Local-Only HASHIRU",
212
+ "No-Economy HASHIRU",
213
+ ],
214
+ value="HASHIRU",
215
+ interactive=True,
216
+ )
217
+ model_dropdown.change(update_model, model_dropdown)
218
+
219
+ chatbot = gr.Chatbot(
220
+ avatar_images=("HASHIRU_2.png", "HASHIRU.png"),
221
+ type="messages", show_copy_button=True, editable="user",
222
+ placeholder="Type your message here…",
223
+ )
224
+ gr.ChatInterface(run_model, type="messages", chatbot=chatbot, additional_outputs=[chatbot], save_history=True)
225
+
226
+ demo.load(None, None, None, js="""
227
+ async () => {
228
+ const profileBtn = document.getElementById("profile-name");
229
+ const profileMenu = document.getElementById("profile-menu");
230
+
231
+ // Toggle menu
232
+ profileBtn.onclick = () => {
233
+ profileMenu.classList.toggle("hidden");
234
+ }
235
+
236
+ try {
237
+ const res = await fetch('/api/login-status', { credentials: 'include' });
238
+ const data = await res.json();
239
+ const name = data.status.replace("Logged in: ", "");
240
+ if (!data.status.includes("Logged out")) {
241
+ profileBtn.innerText = name;
242
+ document.querySelector("#profile-menu button[onclick*='/login']").style.display = "none";
243
+ document.querySelector("#profile-menu button[onclick*='/logout']").style.display = "block";
244
+ } else {
245
+ profileBtn.innerText = "Guest";
246
+ document.querySelector("#profile-menu button[onclick*='/login']").style.display = "block";
247
+ document.querySelector("#profile-menu button[onclick*='/logout']").style.display = "none";
248
+ }
249
+ } catch {
250
+ profileBtn.innerText = "Login status unknown";
251
+ }
252
+ }
253
+ """)
254
+
255
  gr.mount_gradio_app(app, demo, path="/")
256
 
257
  # 6. Entrypoint --------------------------------------------------------