XThomasBU commited on
Commit
65f7fb3
·
verified ·
1 Parent(s): e73267c

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +235 -0
app.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Response
2
+ from fastapi.responses import HTMLResponse, RedirectResponse
3
+ from fastapi.templating import Jinja2Templates
4
+ from google.oauth2 import id_token
5
+ from google.auth.transport import requests as google_requests
6
+ from google_auth_oauthlib.flow import Flow
7
+ from chainlit.utils import mount_chainlit
8
+ import secrets
9
+ import json
10
+ import base64
11
+ from constants import (
12
+ OAUTH_GOOGLE_CLIENT_ID,
13
+ OAUTH_GOOGLE_CLIENT_SECRET,
14
+ CHAINLIT_URL,
15
+ )
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.staticfiles import StaticFiles
18
+
19
+ GOOGLE_CLIENT_ID = OAUTH_GOOGLE_CLIENT_ID
20
+ GOOGLE_CLIENT_SECRET = OAUTH_GOOGLE_CLIENT_SECRET
21
+ GOOGLE_REDIRECT_URI = f"{CHAINLIT_URL}/auth/oauth/google/callback"
22
+
23
+ app = FastAPI()
24
+ app.mount("/public", StaticFiles(directory="public"), name="public")
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=["*"], # Update with appropriate origins
28
+ allow_methods=["*"],
29
+ allow_headers=["*"], # or specify the headers you want to allow
30
+ expose_headers=["X-User-Info"], # Expose the custom header
31
+ )
32
+
33
+ templates = Jinja2Templates(directory="templates")
34
+ session_store = {}
35
+ CHAINLIT_PATH = "/chainlit_tutor"
36
+
37
+ USER_ROLES = {
38
+ "tgardos@bu.edu": ["instructor", "bu"],
39
+ "xthomas@bu.edu": ["instructor", "bu"],
40
+ "faridkar@bu.edu": ["instructor", "bu"],
41
+ "xavierohan1@gmail.com": ["guest"],
42
+ # Add more users and roles as needed
43
+ }
44
+
45
+ # Create a Google OAuth flow
46
+ flow = Flow.from_client_config(
47
+ {
48
+ "web": {
49
+ "client_id": GOOGLE_CLIENT_ID,
50
+ "client_secret": GOOGLE_CLIENT_SECRET,
51
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
52
+ "token_uri": "https://oauth2.googleapis.com/token",
53
+ "redirect_uris": [GOOGLE_REDIRECT_URI],
54
+ "scopes": [
55
+ "openid",
56
+ "https://www.googleapis.com/auth/userinfo.email",
57
+ "https://www.googleapis.com/auth/userinfo.profile",
58
+ ],
59
+ }
60
+ },
61
+ scopes=[
62
+ "openid",
63
+ "https://www.googleapis.com/auth/userinfo.email",
64
+ "https://www.googleapis.com/auth/userinfo.profile",
65
+ ],
66
+ redirect_uri=GOOGLE_REDIRECT_URI,
67
+ )
68
+
69
+
70
+ def get_user_role(username: str):
71
+ return USER_ROLES.get(username, ["student"]) # Default to "student" role
72
+
73
+
74
+ def get_user_info_from_cookie(request: Request):
75
+ user_info_encoded = request.cookies.get("X-User-Info")
76
+ if user_info_encoded:
77
+ try:
78
+ user_info_json = base64.b64decode(user_info_encoded).decode()
79
+ return json.loads(user_info_json)
80
+ except Exception as e:
81
+ print(f"Error decoding user info: {e}")
82
+ return None
83
+ return None
84
+
85
+
86
+ def get_user_info(request: Request):
87
+ session_token = request.cookies.get("session_token")
88
+ if session_token and session_token in session_store:
89
+ return session_store[session_token]
90
+ return None
91
+
92
+
93
+ @app.get("/", response_class=HTMLResponse)
94
+ async def login_page(request: Request):
95
+ user_info = get_user_info_from_cookie(request)
96
+ if user_info and user_info.get("google_signed_in"):
97
+ return RedirectResponse("/post-signin")
98
+ return templates.TemplateResponse("login.html", {"request": request})
99
+
100
+
101
+ @app.get("/login/guest")
102
+ @app.post("/login/guest")
103
+ async def login_guest():
104
+ username = "guest"
105
+ session_token = secrets.token_hex(16)
106
+ unique_session_id = secrets.token_hex(8)
107
+ username = f"{username}_{unique_session_id}"
108
+ session_store[session_token] = {
109
+ "email": username,
110
+ "name": "Guest",
111
+ "profile_image": "",
112
+ "google_signed_in": False, # Ensure guest users do not have this flag
113
+ }
114
+ user_info_json = json.dumps(session_store[session_token])
115
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
116
+
117
+ # Set cookies
118
+ response = RedirectResponse(url="/post-signin", status_code=303)
119
+ response.set_cookie(key="session_token", value=session_token)
120
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
121
+ return response
122
+
123
+
124
+ @app.get("/login/google")
125
+ async def login_google(request: Request):
126
+ # Clear any existing session cookies to avoid conflicts with guest sessions
127
+ response = RedirectResponse(url="/post-signin")
128
+ response.delete_cookie(key="session_token")
129
+ response.delete_cookie(key="X-User-Info")
130
+
131
+ user_info = get_user_info_from_cookie(request)
132
+ print(f"User info: {user_info}")
133
+ # Check if user is already signed in using Google
134
+ if user_info and user_info.get("google_signed_in"):
135
+ return RedirectResponse("/post-signin")
136
+ else:
137
+ authorization_url, _ = flow.authorization_url(prompt="consent")
138
+ return RedirectResponse(authorization_url, headers=response.headers)
139
+
140
+
141
+ @app.get("/auth/oauth/google/callback")
142
+ async def auth_google(request: Request):
143
+ try:
144
+ flow.fetch_token(code=request.query_params.get("code"))
145
+ credentials = flow.credentials
146
+ user_info = id_token.verify_oauth2_token(
147
+ credentials.id_token, google_requests.Request(), GOOGLE_CLIENT_ID
148
+ )
149
+
150
+ email = user_info["email"]
151
+ name = user_info.get("name", "")
152
+ profile_image = user_info.get("picture", "")
153
+
154
+ session_token = secrets.token_hex(16)
155
+ session_store[session_token] = {
156
+ "email": email,
157
+ "name": name,
158
+ "profile_image": profile_image,
159
+ "google_signed_in": True, # Set this flag to True for Google-signed users
160
+ }
161
+
162
+ user_info_json = json.dumps(session_store[session_token])
163
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
164
+
165
+ # Set cookies
166
+ response = RedirectResponse(url="/post-signin", status_code=303)
167
+ response.set_cookie(key="session_token", value=session_token)
168
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
169
+ return response
170
+ except Exception as e:
171
+ print(f"Error during Google OAuth callback: {e}")
172
+ return RedirectResponse(url="/", status_code=302)
173
+
174
+
175
+ @app.get("/post-signin", response_class=HTMLResponse)
176
+ async def post_signin(request: Request):
177
+ user_info = get_user_info_from_cookie(request)
178
+ if not user_info:
179
+ user_info = get_user_info(request)
180
+ if user_info and user_info.get("google_signed_in"):
181
+ username = user_info["email"]
182
+ role = get_user_role(username)
183
+ jwt_token = request.cookies.get("X-User-Info")
184
+ return templates.TemplateResponse(
185
+ "dashboard.html",
186
+ {
187
+ "request": request,
188
+ "username": username,
189
+ "role": role,
190
+ "jwt_token": jwt_token,
191
+ },
192
+ )
193
+ return RedirectResponse("/")
194
+
195
+
196
+ @app.post("/start-tutor")
197
+ async def start_tutor(request: Request):
198
+ user_info = get_user_info_from_cookie(request)
199
+ if user_info:
200
+ user_info_json = json.dumps(user_info)
201
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
202
+
203
+ response = RedirectResponse(CHAINLIT_PATH, status_code=303)
204
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
205
+ return response
206
+
207
+ return RedirectResponse(url="/")
208
+
209
+
210
+ @app.exception_handler(Exception)
211
+ async def exception_handler(request: Request, exc: Exception):
212
+ return templates.TemplateResponse(
213
+ "error.html", {"request": request, "error": str(exc)}, status_code=500
214
+ )
215
+
216
+
217
+ @app.get("/chainlit_tutor/logout", response_class=HTMLResponse)
218
+ @app.post("/chainlit_tutor/logout", response_class=HTMLResponse)
219
+ async def app_logout(request: Request, response: Response):
220
+ # Clear session cookies
221
+ response.delete_cookie("session_token")
222
+ response.delete_cookie("X-User-Info")
223
+
224
+ print("logout_page called")
225
+
226
+ # Redirect to the logout page with embedded JavaScript
227
+ return RedirectResponse(url="/", status_code=302)
228
+
229
+
230
+ mount_chainlit(app=app, target="main.py", path=CHAINLIT_PATH)
231
+
232
+ if __name__ == "__main__":
233
+ import uvicorn
234
+
235
+ uvicorn.run(app, host=CHAINLIT_URL, port=7860)