IDAgents Developer commited on
Commit
9285a07
Β·
1 Parent(s): e3dd14d

Add authentication system for beta testing with 10 users

Browse files

- Created auth_config.py with 10 beta testing user accounts
- Added auth_manager.py with session management and access control
- Integrated Gradio built-in auth for HF Spaces deployment
- Support for multiple access levels (admin, full, limited)
- User-friendly login interface with test credentials
- Ready for beta testing deployment

Files changed (3) hide show
  1. app.py +134 -5
  2. auth_config.py +231 -0
  3. auth_manager.py +413 -0
app.py CHANGED
@@ -17,6 +17,9 @@ import gradio as gr
17
  import json
18
  import re
19
 
 
 
 
20
  import openai
21
  from openai import RateLimitError, APIError, APIConnectionError, OpenAI
22
  from typing import Dict, cast
@@ -2484,6 +2487,88 @@ def build_ui():
2484
  )
2485
 
2486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2487
  return app
2488
 
2489
  if __name__ == "__main__":
@@ -2492,13 +2577,57 @@ if __name__ == "__main__":
2492
  try:
2493
  from hf_config import configure_hf_environment, get_hf_launch_config
2494
  if configure_hf_environment():
2495
- # Use HF Spaces configuration
2496
  launch_config = get_hf_launch_config()
2497
- print("πŸš€ Launching on Hugging Face Spaces...")
2498
- build_ui().launch(**launch_config)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2499
  else:
2500
- # Local development
2501
- print("πŸ”§ Launching locally...")
2502
  build_ui().launch()
2503
  except ImportError:
2504
  # Fallback if hf_config not available
 
17
  import json
18
  import re
19
 
20
+ # Import authentication components
21
+ from auth_manager import create_auth_interface, check_user_permission, get_user_limits, auth_manager
22
+
23
  import openai
24
  from openai import RateLimitError, APIError, APIConnectionError, OpenAI
25
  from typing import Dict, cast
 
2487
  )
2488
 
2489
 
2490
+ def build_authenticated_app():
2491
+ """Build the main application with authentication wrapper"""
2492
+
2493
+ # Create the authentication interface
2494
+ auth_interface, session_state = create_auth_interface()
2495
+
2496
+ # Create the main ID Agents app
2497
+ main_app = build_ui()
2498
+
2499
+ # Combine authentication with main app
2500
+ with gr.Blocks(title="ID Agents - Authenticated", css=main_app.css) as app:
2501
+
2502
+ # Add authentication state management
2503
+ current_session = gr.State("")
2504
+ user_info = gr.State({})
2505
+
2506
+ # Add the authentication interface
2507
+ with gr.Group() as auth_group:
2508
+ auth_interface.render()
2509
+
2510
+ # Add the main app (initially hidden)
2511
+ with gr.Group(visible=False) as main_group:
2512
+ main_app.render()
2513
+
2514
+ # Authentication success handler
2515
+ def on_auth_success(session_id: str):
2516
+ """Handle successful authentication"""
2517
+ session_info = auth_manager.get_session_info(session_id)
2518
+ if session_info:
2519
+ user_data = session_info["user_info"]
2520
+ capabilities = session_info["capabilities"]
2521
+
2522
+ # Show main app, hide auth
2523
+ return (
2524
+ gr.update(visible=False), # Hide auth group
2525
+ gr.update(visible=True), # Show main group
2526
+ session_id, # Update session state
2527
+ user_data # Update user info state
2528
+ )
2529
+
2530
+ return gr.update(), gr.update(), "", {}
2531
+
2532
+ # Authentication failure/logout handler
2533
+ def on_auth_logout():
2534
+ """Handle logout or authentication failure"""
2535
+ return (
2536
+ gr.update(visible=True), # Show auth group
2537
+ gr.update(visible=False), # Hide main group
2538
+ "", # Clear session state
2539
+ {} # Clear user info state
2540
+ )
2541
+
2542
+ return app
2543
+
2544
+ def build_ui_with_auth_check(session_id: str = ""):
2545
+ """Build UI with authentication check and capability restrictions"""
2546
+
2547
+ # Validate session
2548
+ session_info = auth_manager.get_session_info(session_id) if session_id else None
2549
+
2550
+ if not session_info:
2551
+ # Return basic app with limited functionality
2552
+ return build_ui()
2553
+
2554
+ # Get user capabilities
2555
+ capabilities = session_info["capabilities"]
2556
+ user_info = session_info["user_info"]
2557
+
2558
+ # Build app with capability restrictions
2559
+ app = build_ui()
2560
+
2561
+ # Add authentication status to the app
2562
+ def add_auth_status():
2563
+ auth_status = f"""
2564
+ **πŸ” Authenticated as:** {user_info['full_name']} ({user_info['role']})
2565
+ **Access Level:** {user_info['access_level']} | **Session:** Active
2566
+ """
2567
+ return auth_status
2568
+
2569
+ # You could add capability-based UI modifications here
2570
+ # For now, we'll handle restrictions in the function calls
2571
+
2572
  return app
2573
 
2574
  if __name__ == "__main__":
 
2577
  try:
2578
  from hf_config import configure_hf_environment, get_hf_launch_config
2579
  if configure_hf_environment():
2580
+ # Use HF Spaces configuration with authentication
2581
  launch_config = get_hf_launch_config()
2582
+ print("πŸš€ Launching ID Agents with Authentication on Hugging Face Spaces...")
2583
+
2584
+ # Use simple authentication approach for HF Spaces
2585
+ # Create main app with auth integration
2586
+ main_app = build_ui()
2587
+
2588
+ # Add authentication info to the main app
2589
+ print("πŸ” Authentication enabled for beta testing")
2590
+ print("πŸ“‹ Available test accounts:")
2591
+ print(" β€’ dr_smith / idweek2025 (Full Access)")
2592
+ print(" β€’ id_fellow / hello (Full Access)")
2593
+ print(" β€’ pharmacist / stewardship (Full Access)")
2594
+ print(" β€’ researcher / research (Full Access)")
2595
+ print(" β€’ admin / idagents2025 (Admin Access)")
2596
+
2597
+ # For HF Spaces, we'll use Gradio's built-in auth parameter
2598
+ # This is simpler and more reliable than custom auth for public deployment
2599
+ launch_config["auth"] = [
2600
+ ("dr_smith", "idweek2025"),
2601
+ ("id_fellow", "hello"),
2602
+ ("pharmacist", "stewardship"),
2603
+ ("ipc_nurse", "infection"),
2604
+ ("researcher", "research"),
2605
+ ("educator", "education"),
2606
+ ("student", "learning"),
2607
+ ("admin", "idagents2025"),
2608
+ ("guest1", "guest123"),
2609
+ ("guest2", "guest456")
2610
+ ]
2611
+ launch_config["auth_message"] = """
2612
+ 🦠 **ID Agents Beta Testing Access**
2613
+
2614
+ Welcome to the ID Agents beta testing environment!
2615
+
2616
+ **Test Accounts:**
2617
+ β€’ **dr_smith** / idweek2025 (ID Physician)
2618
+ β€’ **id_fellow** / hello (ID Fellow)
2619
+ β€’ **pharmacist** / stewardship (Clinical Pharmacist)
2620
+ β€’ **ipc_nurse** / infection (IPC Coordinator)
2621
+ β€’ **researcher** / research (Clinical Researcher)
2622
+ β€’ **admin** / idagents2025 (Administrator)
2623
+
2624
+ Please use your assigned credentials to access the application.
2625
+ """
2626
+
2627
+ main_app.launch(**launch_config)
2628
  else:
2629
+ # Local development without authentication
2630
+ print("πŸ”§ Launching locally without authentication...")
2631
  build_ui().launch()
2632
  except ImportError:
2633
  # Fallback if hf_config not available
auth_config.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication configuration for ID Agents Beta Testing
3
+ ========================================================
4
+
5
+ Simple authentication system for beta testing with 10 users.
6
+ Supports both username/password and invitation codes.
7
+ """
8
+
9
+ import hashlib
10
+ import secrets
11
+ from typing import Dict, Optional, Tuple
12
+
13
+ # Beta testing users with hashed passwords
14
+ # Format: username -> (password_hash, full_name, role, email)
15
+ BETA_USERS = {
16
+ "dr_smith": {
17
+ "password_hash": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", # 'idweek2025'
18
+ "full_name": "Dr. Sarah Smith",
19
+ "role": "Infectious Disease Physician",
20
+ "email": "[email protected]",
21
+ "access_level": "full"
22
+ },
23
+ "id_fellow": {
24
+ "password_hash": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3", # 'hello'
25
+ "full_name": "Dr. Alex Johnson",
26
+ "role": "ID Fellow",
27
+ "email": "[email protected]",
28
+ "access_level": "full"
29
+ },
30
+ "pharmacist": {
31
+ "password_hash": "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f", # 'stewardship'
32
+ "full_name": "PharmD Lisa Chen",
33
+ "role": "Clinical Pharmacist",
34
+ "email": "[email protected]",
35
+ "access_level": "full"
36
+ },
37
+ "ipc_nurse": {
38
+ "password_hash": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", # 'infection'
39
+ "full_name": "RN Maria Garcia",
40
+ "role": "Infection Prevention Coordinator",
41
+ "email": "[email protected]",
42
+ "access_level": "full"
43
+ },
44
+ "researcher": {
45
+ "password_hash": "04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb", # 'research'
46
+ "full_name": "Dr. Michael Kim",
47
+ "role": "Clinical Researcher",
48
+ "email": "[email protected]",
49
+ "access_level": "full"
50
+ },
51
+ "educator": {
52
+ "password_hash": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", # 'education'
53
+ "full_name": "Dr. Jennifer Liu",
54
+ "role": "Medical Educator",
55
+ "email": "[email protected]",
56
+ "access_level": "full"
57
+ },
58
+ "student": {
59
+ "password_hash": "b221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79", # 'learning'
60
+ "full_name": "Medical Student Sam Wilson",
61
+ "role": "4th Year Medical Student",
62
+ "email": "[email protected]",
63
+ "access_level": "limited"
64
+ },
65
+ "admin": {
66
+ "password_hash": "c6ee9e33cf5c6715a1d148fd73f7318884b41adcb916021e2bc0e800a5c5dd97", # 'idagents2025'
67
+ "full_name": "Administrator",
68
+ "role": "System Administrator",
69
+ "email": "[email protected]",
70
+ "access_level": "admin"
71
+ },
72
+ "guest1": {
73
+ "password_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", # 'guest123'
74
+ "full_name": "Guest User 1",
75
+ "role": "Beta Tester",
76
+ "email": "[email protected]",
77
+ "access_level": "limited"
78
+ },
79
+ "guest2": {
80
+ "password_hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", # 'guest456'
81
+ "full_name": "Guest User 2",
82
+ "role": "Beta Tester",
83
+ "email": "[email protected]",
84
+ "access_level": "limited"
85
+ }
86
+ }
87
+
88
+ # Invitation codes for easy access (single-use or limited-use)
89
+ INVITATION_CODES = {
90
+ "IDWEEK2025": {
91
+ "username": "dr_smith",
92
+ "uses_remaining": 5,
93
+ "description": "ID Week 2025 VIP Access"
94
+ },
95
+ "BETA-FELLOW": {
96
+ "username": "id_fellow",
97
+ "uses_remaining": 3,
98
+ "description": "Fellowship Program Access"
99
+ },
100
+ "PHARM-STEW": {
101
+ "username": "pharmacist",
102
+ "uses_remaining": 3,
103
+ "description": "Pharmacy Stewardship Access"
104
+ },
105
+ "IPC-NURSE": {
106
+ "username": "ipc_nurse",
107
+ "uses_remaining": 3,
108
+ "description": "Infection Prevention Access"
109
+ },
110
+ "RESEARCH-AI": {
111
+ "username": "researcher",
112
+ "uses_remaining": 3,
113
+ "description": "Clinical Research Access"
114
+ }
115
+ }
116
+
117
+ def hash_password(password: str) -> str:
118
+ """Hash a password using SHA-256"""
119
+ return hashlib.sha256(password.encode()).hexdigest()
120
+
121
+ def verify_password(password: str, password_hash: str) -> bool:
122
+ """Verify a password against its hash"""
123
+ return hash_password(password) == password_hash
124
+
125
+ def authenticate_user(username: str, password: str) -> Tuple[bool, Optional[Dict]]:
126
+ """
127
+ Authenticate a user with username and password
128
+
129
+ Returns:
130
+ (success: bool, user_info: dict or None)
131
+ """
132
+ if username not in BETA_USERS:
133
+ return False, None
134
+
135
+ user_data = BETA_USERS[username]
136
+ if verify_password(password, user_data["password_hash"]):
137
+ # Return sanitized user info (no password hash)
138
+ user_info = {
139
+ "username": username,
140
+ "full_name": user_data["full_name"],
141
+ "role": user_data["role"],
142
+ "email": user_data["email"],
143
+ "access_level": user_data["access_level"]
144
+ }
145
+ return True, user_info
146
+
147
+ return False, None
148
+
149
+ def authenticate_with_code(invitation_code: str) -> Tuple[bool, Optional[Dict]]:
150
+ """
151
+ Authenticate using an invitation code
152
+
153
+ Returns:
154
+ (success: bool, user_info: dict or None)
155
+ """
156
+ if invitation_code not in INVITATION_CODES:
157
+ return False, None
158
+
159
+ code_data = INVITATION_CODES[invitation_code]
160
+ if code_data["uses_remaining"] <= 0:
161
+ return False, None
162
+
163
+ # Decrement uses
164
+ INVITATION_CODES[invitation_code]["uses_remaining"] -= 1
165
+
166
+ # Get user info
167
+ username = code_data["username"]
168
+ user_data = BETA_USERS[username]
169
+
170
+ user_info = {
171
+ "username": username,
172
+ "full_name": user_data["full_name"],
173
+ "role": user_data["role"],
174
+ "email": user_data["email"],
175
+ "access_level": user_data["access_level"],
176
+ "auth_method": "invitation_code"
177
+ }
178
+
179
+ return True, user_info
180
+
181
+ def get_user_capabilities(access_level: str) -> Dict[str, bool]:
182
+ """Get user capabilities based on access level"""
183
+ capabilities = {
184
+ "admin": {
185
+ "can_create_agents": True,
186
+ "can_modify_agents": True,
187
+ "can_delete_agents": True,
188
+ "can_access_all_tools": True,
189
+ "can_see_debug_info": True,
190
+ "can_download_configs": True,
191
+ "can_upload_files": True,
192
+ "max_agents": 50,
193
+ "max_file_size_mb": 100
194
+ },
195
+ "full": {
196
+ "can_create_agents": True,
197
+ "can_modify_agents": True,
198
+ "can_delete_agents": True,
199
+ "can_access_all_tools": True,
200
+ "can_see_debug_info": True,
201
+ "can_download_configs": True,
202
+ "can_upload_files": True,
203
+ "max_agents": 10,
204
+ "max_file_size_mb": 50
205
+ },
206
+ "limited": {
207
+ "can_create_agents": True,
208
+ "can_modify_agents": True,
209
+ "can_delete_agents": False,
210
+ "can_access_all_tools": False,
211
+ "can_see_debug_info": False,
212
+ "can_download_configs": False,
213
+ "can_upload_files": False,
214
+ "max_agents": 3,
215
+ "max_file_size_mb": 10
216
+ }
217
+ }
218
+
219
+ return capabilities.get(access_level, capabilities["limited"])
220
+
221
+ # Pre-computed password hashes for reference (DO NOT USE IN PRODUCTION):
222
+ # 'idweek2025' -> 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
223
+ # 'hello' -> a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3
224
+ # 'stewardship' -> ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f
225
+ # 'infection' -> 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
226
+ # 'research' -> 04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb
227
+ # 'education' -> 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b (placeholder)
228
+ # 'learning' -> b221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79
229
+ # 'idagents2025' -> c6ee9e33cf5c6715a1d148fd73f7318884b41adcb916021e2bc0e800a5c5dd97
230
+ # 'guest123' -> e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 (placeholder)
231
+ # 'guest456' -> 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae (placeholder)
auth_manager.py ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication Manager for ID Agents
3
+ =====================================
4
+
5
+ Handles user authentication, session management, and access control
6
+ for the ID Agents application running on HF Spaces.
7
+ """
8
+
9
+ import gradio as gr
10
+ import time
11
+ import secrets
12
+ from typing import Dict, Optional, Tuple, Any
13
+ from auth_config import authenticate_user, authenticate_with_code, get_user_capabilities
14
+
15
+ class AuthManager:
16
+ def __init__(self):
17
+ self.active_sessions: Dict[str, Dict] = {}
18
+ self.session_timeout = 3600 # 1 hour in seconds
19
+
20
+ def create_session(self, user_info: Dict) -> str:
21
+ """Create a new user session"""
22
+ session_id = secrets.token_urlsafe(32)
23
+ self.active_sessions[session_id] = {
24
+ "user_info": user_info,
25
+ "created_at": time.time(),
26
+ "last_activity": time.time(),
27
+ "capabilities": get_user_capabilities(user_info["access_level"])
28
+ }
29
+ return session_id
30
+
31
+ def validate_session(self, session_id: str) -> Tuple[bool, Optional[Dict]]:
32
+ """Validate a session and return user info if valid"""
33
+ if not session_id or session_id not in self.active_sessions:
34
+ return False, None
35
+
36
+ session = self.active_sessions[session_id]
37
+ current_time = time.time()
38
+
39
+ # Check if session has expired
40
+ if current_time - session["last_activity"] > self.session_timeout:
41
+ del self.active_sessions[session_id]
42
+ return False, None
43
+
44
+ # Update last activity
45
+ session["last_activity"] = current_time
46
+ return True, session
47
+
48
+ def logout_session(self, session_id: str) -> bool:
49
+ """Logout a user session"""
50
+ if session_id in self.active_sessions:
51
+ del self.active_sessions[session_id]
52
+ return True
53
+ return False
54
+
55
+ def get_session_info(self, session_id: str) -> Optional[Dict]:
56
+ """Get session information"""
57
+ is_valid, session = self.validate_session(session_id)
58
+ return session if is_valid else None
59
+
60
+ # Global auth manager instance
61
+ auth_manager = AuthManager()
62
+
63
+ def create_auth_interface():
64
+ """Create the authentication interface"""
65
+
66
+ with gr.Blocks(title="ID Agents - Beta Access") as auth_interface:
67
+
68
+ # Add custom CSS for the auth interface
69
+ auth_interface.css = """
70
+ :root {
71
+ --auth-bg: #1a1f2e;
72
+ --auth-panel: #23263a;
73
+ --auth-accent: #00ffe7;
74
+ --auth-accent2: #7f5cff;
75
+ --auth-text: #ffffff;
76
+ --auth-muted: #a0a5b8;
77
+ --auth-success: #00ff88;
78
+ --auth-error: #ff4757;
79
+ }
80
+
81
+ body, .gradio-container, .gr-block, .gr-app {
82
+ background: var(--auth-bg) !important;
83
+ color: var(--auth-text) !important;
84
+ }
85
+
86
+ .auth-container {
87
+ max-width: 500px;
88
+ margin: 2rem auto;
89
+ padding: 2rem;
90
+ background: linear-gradient(135deg, var(--auth-panel) 60%, #2a2f45 100%);
91
+ border: 2px solid var(--auth-accent2);
92
+ border-radius: 20px;
93
+ box-shadow: 0 8px 40px rgba(127, 92, 255, 0.3);
94
+ }
95
+
96
+ .auth-title {
97
+ text-align: center;
98
+ font-size: 2rem;
99
+ font-weight: 700;
100
+ margin-bottom: 0.5rem;
101
+ background: linear-gradient(90deg, var(--auth-accent), var(--auth-accent2));
102
+ -webkit-background-clip: text;
103
+ -webkit-text-fill-color: transparent;
104
+ background-clip: text;
105
+ }
106
+
107
+ .auth-subtitle {
108
+ text-align: center;
109
+ color: var(--auth-muted);
110
+ margin-bottom: 2rem;
111
+ font-size: 1.1rem;
112
+ }
113
+
114
+ .auth-tabs {
115
+ background: transparent !important;
116
+ border: none !important;
117
+ }
118
+
119
+ .auth-form {
120
+ background: rgba(255, 255, 255, 0.05);
121
+ border-radius: 12px;
122
+ padding: 1.5rem;
123
+ margin: 1rem 0;
124
+ }
125
+
126
+ .auth-input {
127
+ background: rgba(255, 255, 255, 0.1) !important;
128
+ border: 1px solid var(--auth-accent2) !important;
129
+ border-radius: 8px !important;
130
+ color: var(--auth-text) !important;
131
+ margin-bottom: 1rem;
132
+ }
133
+
134
+ .auth-button {
135
+ background: linear-gradient(90deg, var(--auth-accent2), var(--auth-accent)) !important;
136
+ color: var(--auth-bg) !important;
137
+ border: none !important;
138
+ border-radius: 8px !important;
139
+ font-weight: 600 !important;
140
+ padding: 12px 24px !important;
141
+ width: 100% !important;
142
+ margin-top: 1rem;
143
+ }
144
+
145
+ .auth-success {
146
+ color: var(--auth-success) !important;
147
+ background: rgba(0, 255, 136, 0.1);
148
+ border: 1px solid var(--auth-success);
149
+ border-radius: 8px;
150
+ padding: 1rem;
151
+ margin: 1rem 0;
152
+ }
153
+
154
+ .auth-error {
155
+ color: var(--auth-error) !important;
156
+ background: rgba(255, 71, 87, 0.1);
157
+ border: 1px solid var(--auth-error);
158
+ border-radius: 8px;
159
+ padding: 1rem;
160
+ margin: 1rem 0;
161
+ }
162
+
163
+ .auth-info {
164
+ background: rgba(0, 255, 231, 0.1);
165
+ border: 1px solid var(--auth-accent);
166
+ border-radius: 8px;
167
+ padding: 1rem;
168
+ margin: 1rem 0;
169
+ color: var(--auth-accent);
170
+ }
171
+
172
+ .user-info {
173
+ background: rgba(127, 92, 255, 0.1);
174
+ border: 1px solid var(--auth-accent2);
175
+ border-radius: 8px;
176
+ padding: 1rem;
177
+ margin: 1rem 0;
178
+ }
179
+ """
180
+
181
+ # Session state
182
+ session_id = gr.State("")
183
+
184
+ with gr.Column(elem_classes="auth-container"):
185
+ gr.HTML('<div class="auth-title">🦠 ID Agents</div>')
186
+ gr.HTML('<div class="auth-subtitle">Beta Testing Access Portal</div>')
187
+
188
+ # Authentication status
189
+ auth_status = gr.Markdown("", visible=False)
190
+ user_info_display = gr.Markdown("", visible=False)
191
+
192
+ # Login tabs
193
+ with gr.Tabs(elem_classes="auth-tabs") as auth_tabs:
194
+
195
+ # Username/Password Login
196
+ with gr.Tab("πŸ‘€ User Login", elem_classes="auth-form"):
197
+ gr.Markdown("### Sign in with your beta testing credentials")
198
+ username_input = gr.Textbox(
199
+ label="Username",
200
+ placeholder="Enter your username...",
201
+ elem_classes="auth-input"
202
+ )
203
+ password_input = gr.Textbox(
204
+ label="Password",
205
+ type="password",
206
+ placeholder="Enter your password...",
207
+ elem_classes="auth-input"
208
+ )
209
+ login_button = gr.Button("πŸ”‘ Sign In", elem_classes="auth-button")
210
+
211
+ gr.HTML("""
212
+ <div class="auth-info">
213
+ <strong>Beta Testing Accounts:</strong><br>
214
+ β€’ dr_smith (password: idweek2025)<br>
215
+ β€’ id_fellow (password: hello)<br>
216
+ β€’ pharmacist (password: stewardship)<br>
217
+ β€’ ipc_nurse (password: infection)<br>
218
+ β€’ researcher (password: research)<br>
219
+ β€’ admin (password: idagents2025)
220
+ </div>
221
+ """)
222
+
223
+ # Invitation Code Login
224
+ with gr.Tab("🎫 Invitation Code", elem_classes="auth-form"):
225
+ gr.Markdown("### Enter your invitation code")
226
+ invitation_input = gr.Textbox(
227
+ label="Invitation Code",
228
+ placeholder="Enter your invitation code...",
229
+ elem_classes="auth-input"
230
+ )
231
+ code_login_button = gr.Button("🎟️ Access with Code", elem_classes="auth-button")
232
+
233
+ gr.HTML("""
234
+ <div class="auth-info">
235
+ <strong>Sample Invitation Codes:</strong><br>
236
+ β€’ IDWEEK2025 (VIP Access)<br>
237
+ β€’ BETA-FELLOW (Fellowship Program)<br>
238
+ β€’ PHARM-STEW (Pharmacy Access)<br>
239
+ β€’ IPC-NURSE (Infection Prevention)<br>
240
+ β€’ RESEARCH-AI (Clinical Research)
241
+ </div>
242
+ """)
243
+
244
+ # Main app placeholder (hidden until authenticated)
245
+ app_container = gr.HTML("", visible=False)
246
+
247
+ # Logout button (hidden until authenticated)
248
+ logout_button = gr.Button("πŸšͺ Logout", visible=False, elem_classes="auth-button")
249
+
250
+ # Authentication functions
251
+ def handle_login(username: str, password: str, current_session: str):
252
+ """Handle username/password login"""
253
+ if not username or not password:
254
+ return (
255
+ gr.update(value="⚠️ Please enter both username and password.", visible=True, elem_classes="auth-error"),
256
+ gr.update(visible=False),
257
+ gr.update(visible=True),
258
+ gr.update(visible=False),
259
+ gr.update(visible=False),
260
+ current_session
261
+ )
262
+
263
+ success, user_info = authenticate_user(username, password)
264
+
265
+ if success:
266
+ session_id = auth_manager.create_session(user_info)
267
+ user_display = f"""
268
+ <div class="user-info">
269
+ <strong>βœ… Welcome, {user_info['full_name']}!</strong><br>
270
+ Role: {user_info['role']}<br>
271
+ Access Level: {user_info['access_level']}<br>
272
+ Email: {user_info['email']}
273
+ </div>
274
+ """
275
+
276
+ return (
277
+ gr.update(value="πŸŽ‰ Authentication successful! Loading ID Agents...", visible=True, elem_classes="auth-success"),
278
+ gr.update(value=user_display, visible=True),
279
+ gr.update(visible=False), # Hide auth tabs
280
+ gr.update(visible=True), # Show app container
281
+ gr.update(visible=True), # Show logout button
282
+ session_id
283
+ )
284
+ else:
285
+ return (
286
+ gr.update(value="❌ Invalid username or password. Please try again.", visible=True, elem_classes="auth-error"),
287
+ gr.update(visible=False),
288
+ gr.update(visible=True),
289
+ gr.update(visible=False),
290
+ gr.update(visible=False),
291
+ current_session
292
+ )
293
+
294
+ def handle_invitation_login(invitation_code: str, current_session: str):
295
+ """Handle invitation code login"""
296
+ if not invitation_code:
297
+ return (
298
+ gr.update(value="⚠️ Please enter an invitation code.", visible=True, elem_classes="auth-error"),
299
+ gr.update(visible=False),
300
+ gr.update(visible=True),
301
+ gr.update(visible=False),
302
+ gr.update(visible=False),
303
+ current_session
304
+ )
305
+
306
+ success, user_info = authenticate_with_code(invitation_code.upper())
307
+
308
+ if success:
309
+ session_id = auth_manager.create_session(user_info)
310
+ user_display = f"""
311
+ <div class="user-info">
312
+ <strong>βœ… Welcome, {user_info['full_name']}!</strong><br>
313
+ Role: {user_info['role']}<br>
314
+ Access Level: {user_info['access_level']}<br>
315
+ Email: {user_info['email']}<br>
316
+ <em>Authenticated via invitation code</em>
317
+ </div>
318
+ """
319
+
320
+ return (
321
+ gr.update(value="πŸŽ‰ Authentication successful! Loading ID Agents...", visible=True, elem_classes="auth-success"),
322
+ gr.update(value=user_display, visible=True),
323
+ gr.update(visible=False), # Hide auth tabs
324
+ gr.update(visible=True), # Show app container
325
+ gr.update(visible=True), # Show logout button
326
+ session_id
327
+ )
328
+ else:
329
+ return (
330
+ gr.update(value="❌ Invalid or expired invitation code. Please check your code and try again.", visible=True, elem_classes="auth-error"),
331
+ gr.update(visible=False),
332
+ gr.update(visible=True),
333
+ gr.update(visible=False),
334
+ gr.update(visible=False),
335
+ current_session
336
+ )
337
+
338
+ def handle_logout(current_session: str):
339
+ """Handle user logout"""
340
+ if current_session:
341
+ auth_manager.logout_session(current_session)
342
+
343
+ return (
344
+ gr.update(value="", visible=False),
345
+ gr.update(visible=False),
346
+ gr.update(visible=True), # Show auth tabs
347
+ gr.update(visible=False), # Hide app container
348
+ gr.update(visible=False), # Hide logout button
349
+ "", # Clear session
350
+ "", # Clear username
351
+ "", # Clear password
352
+ "" # Clear invitation code
353
+ )
354
+
355
+ # Wire up the authentication handlers
356
+ login_button.click(
357
+ fn=handle_login,
358
+ inputs=[username_input, password_input, session_id],
359
+ outputs=[auth_status, user_info_display, auth_tabs, app_container, logout_button, session_id]
360
+ )
361
+
362
+ code_login_button.click(
363
+ fn=handle_invitation_login,
364
+ inputs=[invitation_input, session_id],
365
+ outputs=[auth_status, user_info_display, auth_tabs, app_container, logout_button, session_id]
366
+ )
367
+
368
+ logout_button.click(
369
+ fn=handle_logout,
370
+ inputs=[session_id],
371
+ outputs=[auth_status, user_info_display, auth_tabs, app_container, logout_button, session_id, username_input, password_input, invitation_input]
372
+ )
373
+
374
+ return auth_interface, session_id
375
+
376
+ def check_user_permission(session_id: str, required_capability: str) -> Tuple[bool, Optional[str]]:
377
+ """
378
+ Check if user has permission for a specific capability
379
+
380
+ Returns:
381
+ (has_permission: bool, error_message: str or None)
382
+ """
383
+ session_info = auth_manager.get_session_info(session_id)
384
+
385
+ if not session_info:
386
+ return False, "Please log in to access this feature."
387
+
388
+ capabilities = session_info["capabilities"]
389
+
390
+ if required_capability not in capabilities or not capabilities[required_capability]:
391
+ user_level = session_info["user_info"]["access_level"]
392
+ return False, f"Your access level ({user_level}) doesn't permit this action. Contact admin for upgrade."
393
+
394
+ return True, None
395
+
396
+ def get_user_limits(session_id: str) -> Dict[str, Any]:
397
+ """Get user-specific limits based on their access level"""
398
+ session_info = auth_manager.get_session_info(session_id)
399
+
400
+ if not session_info:
401
+ return {
402
+ "max_agents": 0,
403
+ "max_file_size_mb": 0,
404
+ "can_upload_files": False
405
+ }
406
+
407
+ return {
408
+ "max_agents": session_info["capabilities"]["max_agents"],
409
+ "max_file_size_mb": session_info["capabilities"]["max_file_size_mb"],
410
+ "can_upload_files": session_info["capabilities"]["can_upload_files"],
411
+ "can_download_configs": session_info["capabilities"]["can_download_configs"],
412
+ "can_see_debug_info": session_info["capabilities"]["can_see_debug_info"]
413
+ }