XThomasBU commited on
Commit
c3cd73e
1 Parent(s): 2719f21

login_app_initial

Browse files
code/app.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 modules.config.constants import OAUTH_GOOGLE_CLIENT_ID, OAUTH_GOOGLE_CLIENT_SECRET
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from fastapi.staticfiles import StaticFiles
14
+
15
+ GOOGLE_CLIENT_ID = OAUTH_GOOGLE_CLIENT_ID
16
+ GOOGLE_CLIENT_SECRET = OAUTH_GOOGLE_CLIENT_SECRET
17
+ GOOGLE_REDIRECT_URI = "http://localhost:8000/auth/oauth/google/callback"
18
+
19
+ app = FastAPI()
20
+ app.mount("/public", StaticFiles(directory="public"), name="public")
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"], # Update with appropriate origins
24
+ allow_methods=["*"],
25
+ allow_headers=["*"], # or specify the headers you want to allow
26
+ expose_headers=["X-User-Info"], # Expose the custom header
27
+ )
28
+
29
+ templates = Jinja2Templates(directory="templates")
30
+ session_store = {}
31
+ CHAINLIT_PATH = "/chainlit_tutor"
32
+
33
+ USER_ROLES = {
34
+ "tgardos@bu.edu": ["instructor", "bu"],
35
+ "xthomas@bu.edu": ["instructor", "bu"],
36
+ "faridkar@bu.edu": ["instructor", "bu"],
37
+ "xavierohan1@gmail.com": ["guest"],
38
+ # Add more users and roles as needed
39
+ }
40
+
41
+ # Create a Google OAuth flow
42
+ flow = Flow.from_client_config(
43
+ {
44
+ "web": {
45
+ "client_id": GOOGLE_CLIENT_ID,
46
+ "client_secret": GOOGLE_CLIENT_SECRET,
47
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
48
+ "token_uri": "https://oauth2.googleapis.com/token",
49
+ "redirect_uris": [GOOGLE_REDIRECT_URI],
50
+ "scopes": [
51
+ "openid",
52
+ "https://www.googleapis.com/auth/userinfo.email",
53
+ "https://www.googleapis.com/auth/userinfo.profile",
54
+ ],
55
+ }
56
+ },
57
+ scopes=[
58
+ "openid",
59
+ "https://www.googleapis.com/auth/userinfo.email",
60
+ "https://www.googleapis.com/auth/userinfo.profile",
61
+ ],
62
+ redirect_uri=GOOGLE_REDIRECT_URI,
63
+ )
64
+
65
+
66
+ def get_user_role(username: str):
67
+ return USER_ROLES.get(username, ["student"]) # Default to "student" role
68
+
69
+
70
+ def get_user_info_from_cookie(request: Request):
71
+ user_info_encoded = request.cookies.get("X-User-Info")
72
+ if user_info_encoded:
73
+ try:
74
+ user_info_json = base64.b64decode(user_info_encoded).decode()
75
+ return json.loads(user_info_json)
76
+ except Exception as e:
77
+ print(f"Error decoding user info: {e}")
78
+ return None
79
+ return None
80
+
81
+
82
+ def get_user_info(request: Request):
83
+ session_token = request.cookies.get("session_token")
84
+ if session_token and session_token in session_store:
85
+ return session_store[session_token]
86
+ return None
87
+
88
+
89
+ @app.get("/", response_class=HTMLResponse)
90
+ async def login_page(request: Request):
91
+ user_info = get_user_info_from_cookie(request)
92
+ if user_info and user_info.get("google_signed_in"):
93
+ return RedirectResponse("/post-signin")
94
+ return templates.TemplateResponse("login.html", {"request": request})
95
+
96
+
97
+ @app.get("/login/guest")
98
+ @app.post("/login/guest")
99
+ async def login_guest():
100
+ username = "guest"
101
+ session_token = secrets.token_hex(16)
102
+ unique_session_id = secrets.token_hex(8)
103
+ username = f"{username}_{unique_session_id}"
104
+ session_store[session_token] = {
105
+ "email": username,
106
+ "name": "Guest",
107
+ "profile_image": "",
108
+ "google_signed_in": False, # Ensure guest users do not have this flag
109
+ }
110
+ user_info_json = json.dumps(session_store[session_token])
111
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
112
+
113
+ # Set cookies
114
+ response = RedirectResponse(url="/post-signin", status_code=303)
115
+ response.set_cookie(key="session_token", value=session_token)
116
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
117
+ return response
118
+
119
+
120
+ @app.get("/login/google")
121
+ async def login_google(request: Request):
122
+ # Clear any existing session cookies to avoid conflicts with guest sessions
123
+ response = RedirectResponse(url="/post-signin")
124
+ response.delete_cookie(key="session_token")
125
+ response.delete_cookie(key="X-User-Info")
126
+
127
+ user_info = get_user_info_from_cookie(request)
128
+ print(f"User info: {user_info}")
129
+ # Check if user is already signed in using Google
130
+ if user_info and user_info.get("google_signed_in"):
131
+ return RedirectResponse("/post-signin")
132
+ else:
133
+ authorization_url, _ = flow.authorization_url(prompt="consent")
134
+ return RedirectResponse(authorization_url, headers=response.headers)
135
+
136
+
137
+ @app.get("/auth/oauth/google/callback")
138
+ async def auth_google(request: Request):
139
+ try:
140
+ flow.fetch_token(code=request.query_params.get("code"))
141
+ credentials = flow.credentials
142
+ user_info = id_token.verify_oauth2_token(
143
+ credentials.id_token, google_requests.Request(), GOOGLE_CLIENT_ID
144
+ )
145
+
146
+ email = user_info["email"]
147
+ name = user_info.get("name", "")
148
+ profile_image = user_info.get("picture", "")
149
+
150
+ session_token = secrets.token_hex(16)
151
+ session_store[session_token] = {
152
+ "email": email,
153
+ "name": name,
154
+ "profile_image": profile_image,
155
+ "google_signed_in": True, # Set this flag to True for Google-signed users
156
+ }
157
+
158
+ user_info_json = json.dumps(session_store[session_token])
159
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
160
+
161
+ # Set cookies
162
+ response = RedirectResponse(url="/post-signin", status_code=303)
163
+ response.set_cookie(key="session_token", value=session_token)
164
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
165
+ return response
166
+ except Exception as e:
167
+ print(f"Error during Google OAuth callback: {e}")
168
+ return RedirectResponse(url="/", status_code=302)
169
+
170
+
171
+ @app.get("/post-signin", response_class=HTMLResponse)
172
+ async def post_signin(request: Request):
173
+ user_info = get_user_info_from_cookie(request)
174
+ if not user_info:
175
+ user_info = get_user_info(request)
176
+ if user_info and user_info.get("google_signed_in"):
177
+ username = user_info["email"]
178
+ role = get_user_role(username)
179
+ jwt_token = request.cookies.get("X-User-Info")
180
+ return templates.TemplateResponse(
181
+ "dashboard.html",
182
+ {
183
+ "request": request,
184
+ "username": username,
185
+ "role": role,
186
+ "jwt_token": jwt_token,
187
+ },
188
+ )
189
+ return RedirectResponse("/")
190
+
191
+
192
+ @app.post("/start-tutor")
193
+ async def start_tutor(request: Request):
194
+ user_info = get_user_info_from_cookie(request)
195
+ if user_info:
196
+ user_info_json = json.dumps(user_info)
197
+ user_info_encoded = base64.b64encode(user_info_json.encode()).decode()
198
+
199
+ response = RedirectResponse(CHAINLIT_PATH, status_code=303)
200
+ response.set_cookie(key="X-User-Info", value=user_info_encoded, httponly=True)
201
+ return response
202
+
203
+ return RedirectResponse(url="/")
204
+
205
+
206
+ @app.exception_handler(Exception)
207
+ async def exception_handler(request: Request, exc: Exception):
208
+ return templates.TemplateResponse(
209
+ "error.html", {"request": request, "error": str(exc)}, status_code=500
210
+ )
211
+
212
+
213
+ @app.get("/chainlit_tutor/logout", response_class=HTMLResponse)
214
+ @app.post("/chainlit_tutor/logout", response_class=HTMLResponse)
215
+ async def app_logout(request: Request, response: Response):
216
+ # Clear session cookies
217
+ response.delete_cookie("session_token")
218
+ response.delete_cookie("X-User-Info")
219
+
220
+ print("logout_page called")
221
+
222
+ # Redirect to the logout page with embedded JavaScript
223
+ return RedirectResponse(url="/", status_code=302)
224
+
225
+
226
+ mount_chainlit(app=app, target="main.py", path=CHAINLIT_PATH)
227
+
228
+ if __name__ == "__main__":
229
+ import uvicorn
230
+
231
+ uvicorn.run(app, host="127.0.0.1", port=8000)
code/main.py CHANGED
@@ -5,7 +5,6 @@ from modules.config.constants import (
5
  LITERAL_API_URL,
6
  )
7
  from modules.chat_processor.literal_ai import CustomLiteralDataLayer
8
-
9
  import json
10
  import yaml
11
  from typing import Any, Dict, no_type_check
@@ -21,6 +20,7 @@ import copy
21
  from typing import Optional
22
  from chainlit.types import ThreadDict
23
  import time
 
24
 
25
  USER_TIMEOUT = 60_000
26
  SYSTEM = "System"
@@ -445,14 +445,41 @@ class Chatbot:
445
  cl.user_session.set("memory", conversation_list)
446
  await self.start(config=thread_config)
447
 
448
- @cl.oauth_callback
449
- def auth_callback(
450
- provider_id: str,
451
- token: str,
452
- raw_user_data: Dict[str, str],
453
- default_user: cl.User,
454
- ) -> Optional[cl.User]:
455
- return default_user
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
  async def on_follow_up(self, action: cl.Action):
458
  message = await cl.Message(
@@ -482,4 +509,8 @@ async def start_app():
482
  cl.action_callback("follow up question")(chatbot.on_follow_up)
483
 
484
 
485
- asyncio.run(start_app())
 
 
 
 
 
5
  LITERAL_API_URL,
6
  )
7
  from modules.chat_processor.literal_ai import CustomLiteralDataLayer
 
8
  import json
9
  import yaml
10
  from typing import Any, Dict, no_type_check
 
20
  from typing import Optional
21
  from chainlit.types import ThreadDict
22
  import time
23
+ import base64
24
 
25
  USER_TIMEOUT = 60_000
26
  SYSTEM = "System"
 
445
  cl.user_session.set("memory", conversation_list)
446
  await self.start(config=thread_config)
447
 
448
+ # @cl.oauth_callback
449
+ # def auth_callback(
450
+ # provider_id: str,
451
+ # token: str,
452
+ # raw_user_data: Dict[str, str],
453
+ # default_user: cl.User,
454
+ # ) -> Optional[cl.User]:
455
+ # return default_user
456
+
457
+ @cl.header_auth_callback
458
+ def header_auth_callback(headers: dict) -> Optional[cl.User]:
459
+
460
+ print("\n\n\nI am here\n\n\n")
461
+ # try: # TODO: Add try-except block after testing
462
+ # TODO: Implement to get the user information from the headers (not the cookie)
463
+ cookie = headers.get("cookie") # gets back a str
464
+ # Create a dictionary from the pairs
465
+ cookie_dict = {}
466
+ for pair in cookie.split("; "):
467
+ key, value = pair.split("=", 1)
468
+ # Strip surrounding quotes if present
469
+ cookie_dict[key] = value.strip('"')
470
+
471
+ decoded_user_info = base64.b64decode(
472
+ cookie_dict.get("X-User-Info", "")
473
+ ).decode()
474
+ decoded_user_info = json.loads(decoded_user_info)
475
+
476
+ return cl.User(
477
+ identifier=decoded_user_info["email"],
478
+ metadata={
479
+ "name": decoded_user_info["name"],
480
+ "avatar": decoded_user_info["profile_image"],
481
+ },
482
+ )
483
 
484
  async def on_follow_up(self, action: cl.Action):
485
  message = await cl.Message(
 
509
  cl.action_callback("follow up question")(chatbot.on_follow_up)
510
 
511
 
512
+ loop = asyncio.get_event_loop()
513
+ if loop.is_running():
514
+ asyncio.ensure_future(start_app())
515
+ else:
516
+ asyncio.run(start_app())
code/templates/dashboard.html ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dashboard</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
9
+
10
+ body, html {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: 'Inter', sans-serif;
14
+ background: url('./public/space.jpg') no-repeat center center fixed;
15
+ background-size: cover;
16
+ background-color: #1a1a1a;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ height: 100vh;
21
+ color: #f5f5f5;
22
+ }
23
+
24
+ .container {
25
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.85), rgba(240, 240, 240, 0.85));
26
+ border: 1px solid rgba(255, 255, 255, 0.3);
27
+ border-radius: 12px;
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
29
+ backdrop-filter: blur(10px);
30
+ -webkit-backdrop-filter: blur(10px);
31
+ width: 100%;
32
+ max-width: 400px;
33
+ padding: 40px;
34
+ box-sizing: border-box;
35
+ text-align: center;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .container:before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 70%);
48
+ z-index: 1;
49
+ pointer-events: none;
50
+ }
51
+
52
+ .container > * {
53
+ position: relative;
54
+ z-index: 2;
55
+ }
56
+
57
+ .avatar {
58
+ width: 80px;
59
+ height: 80px;
60
+ border-radius: 50%;
61
+ margin-bottom: 20px;
62
+ border: 2px solid #fff;
63
+ }
64
+
65
+ .container h1 {
66
+ margin-bottom: 20px;
67
+ font-size: 26px;
68
+ font-weight: 600;
69
+ color: #333;
70
+ }
71
+
72
+ .container p {
73
+ font-size: 15px;
74
+ color: #666;
75
+ margin-bottom: 30px;
76
+ }
77
+
78
+ .button {
79
+ padding: 12px 0;
80
+ margin: 10px 0;
81
+ font-size: 16px;
82
+ border: none;
83
+ border-radius: 5px;
84
+ cursor: pointer;
85
+ width: 100%;
86
+ transition: background-color 0.3s ease, color 0.3s ease;
87
+ background-color: #FAFAFA;
88
+ color: #333;
89
+ }
90
+
91
+ .button:hover {
92
+ background-color: #f0f0f0;
93
+ }
94
+
95
+ .start-button {
96
+ background-color: #4CAF50;
97
+ color: #fff;
98
+ }
99
+
100
+ .start-button:hover {
101
+ background-color: #45a049;
102
+ }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <div class="container">
107
+ <img src="/public/avatars/ai_tutor.png" alt="AI Tutor Avatar" class="avatar">
108
+ <h1>Welcome, {{ username }}</h1>
109
+ <p>Ready to start your AI tutoring session?</p>
110
+ <form action="/start-tutor" method="post">
111
+ <button type="submit" class="button start-button">Start AI Tutor</button>
112
+ </form>
113
+ </div>
114
+ <script>
115
+ let token = "{{ jwt_token }}";
116
+ console.log("Token: ", token);
117
+ localStorage.setItem('token', token);
118
+ </script>
119
+ </body>
120
+ </html>
code/templates/login.html ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
9
+
10
+ body, html {
11
+ margin: 0;
12
+ padding: 0;
13
+ font-family: 'Inter', sans-serif;
14
+ background: url('/public/space.jpg') no-repeat center center fixed;
15
+ background-size: cover;
16
+ background-color: #1a1a1a;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ height: 100vh;
21
+ color: #f5f5f5;
22
+ }
23
+
24
+ .container {
25
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.85), rgba(240, 240, 240, 0.85));
26
+ border: 1px solid rgba(255, 255, 255, 0.3);
27
+ border-radius: 12px;
28
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
29
+ backdrop-filter: blur(10px);
30
+ -webkit-backdrop-filter: blur(10px);
31
+ width: 100%;
32
+ max-width: 400px;
33
+ padding: 40px;
34
+ box-sizing: border-box;
35
+ text-align: center;
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .container:before {
41
+ content: '';
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 70%);
48
+ z-index: 1;
49
+ pointer-events: none;
50
+ }
51
+
52
+ .container > * {
53
+ position: relative;
54
+ z-index: 2;
55
+ }
56
+
57
+ .avatar {
58
+ width: 80px;
59
+ height: 80px;
60
+ border-radius: 50%;
61
+ margin-bottom: 20px;
62
+ border: 2px solid #fff;
63
+ }
64
+
65
+ .container h1 {
66
+ margin-bottom: 20px;
67
+ font-size: 26px;
68
+ font-weight: 600;
69
+ color: #333;
70
+ }
71
+
72
+ .container p {
73
+ font-size: 15px;
74
+ color: #666;
75
+ margin-bottom: 30px;
76
+ }
77
+
78
+ .button {
79
+ padding: 12px 0;
80
+ margin: 10px 0;
81
+ font-size: 16px;
82
+ border: none;
83
+ border-radius: 5px;
84
+ cursor: pointer;
85
+ width: 100%;
86
+ transition: background-color 0.3s ease, color 0.3s ease;
87
+ background-color: #FAFAFA;
88
+ color: #333;
89
+ }
90
+
91
+ .button:hover {
92
+ background-color: #f0f0f0;
93
+ }
94
+
95
+ .google-button {
96
+ background-color: #4285F4;
97
+ color: #fff;
98
+ border: none;
99
+ }
100
+
101
+ .google-button:hover {
102
+ background-color: #357ae8;
103
+ }
104
+ </style>
105
+ </head>
106
+ <body>
107
+ <div class="container">
108
+ <img src="/public/avatars/ai_tutor.png" alt="AI Tutor Avatar" class="avatar">
109
+ <h1>Terrier Tutor</h1>
110
+ <p>Hey! Welcome to the DS598 AI tutor</p>
111
+ <form action="/login/guest" method="post">
112
+ <button type="submit" class="button">Sign in as Guest</button>
113
+ </form>
114
+ <form action="/login/google" method="get">
115
+ <button type="submit" class="button google-button">Sign in with Google</button>
116
+ </form>
117
+ </div>
118
+ </body>
119
+ </html>
code/templates/logout.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Logout</title>
5
+ <script>
6
+ window.onload = function() {
7
+ fetch('/chainlit_tutor/logout', {
8
+ method: 'POST',
9
+ credentials: 'include' // Ensure cookies are sent
10
+ }).then(() => {
11
+ window.location.href = '/';
12
+ }).catch(error => {
13
+ console.error('Logout failed:', error);
14
+ });
15
+ };
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <p>Logging out... If you are not redirected, <a href="/">click here</a>.</p>
20
+ </body>
21
+ </html>