Spaces:
Sleeping
Sleeping
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
- app.py +134 -5
- auth_config.py +231 -0
- 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|