| """ |
| Simple Password Authentication for SPARKNET |
| |
| Provides password-based access control for the Streamlit app. |
| |
| SECURITY NOTES: |
| --------------- |
| This module provides basic password authentication suitable for demos |
| and internal deployments. For production use, consider: |
| |
| 1. ENHANCED AUTHENTICATION: |
| - Integrate with OAuth/OIDC (Google, Azure AD, Okta) |
| - Use Streamlit's built-in OAuth support |
| - Implement multi-factor authentication (MFA) |
| |
| 2. SESSION MANAGEMENT: |
| - Configure session timeouts (default: browser session) |
| - Implement session invalidation on logout |
| - Consider IP-based session binding |
| |
| 3. PASSWORD SECURITY: |
| - Use strong password requirements |
| - Implement account lockout after failed attempts |
| - Store passwords hashed with bcrypt (not SHA-256) for production |
| |
| 4. AUDIT LOGGING: |
| - Log authentication attempts (success/failure) |
| - Track user sessions |
| - Monitor for suspicious activity |
| |
| GDPR CONSIDERATIONS: |
| ------------------- |
| - Authentication logs may contain personal data (usernames, IPs) |
| - Implement data retention policies for auth logs |
| - Support right-to-erasure for user accounts |
| - Document authentication processing in GDPR records |
| |
| PRIVATE DEPLOYMENT: |
| ------------------ |
| For enterprise deployments: |
| - Integrate with existing identity providers |
| - Use LDAP/Active Directory for user management |
| - Implement role-based access control (RBAC) |
| - Enable single sign-on (SSO) |
| |
| See SECURITY.md for comprehensive security documentation. |
| """ |
|
|
| import streamlit as st |
| import hashlib |
| import hmac |
| from typing import Optional |
|
|
|
|
| def hash_password(password: str) -> str: |
| """Hash a password for secure storage.""" |
| return hashlib.sha256(password.encode()).hexdigest() |
|
|
|
|
| def check_password() -> bool: |
| """ |
| Show login form and verify password. |
| |
| Returns True if password is correct, False otherwise. |
| |
| Configure password in Streamlit secrets: |
| [auth] |
| password = "your-secure-password" |
| |
| Or set multiple users: |
| [auth] |
| passwords = { admin = "admin123", user1 = "pass123" } |
| """ |
|
|
| def password_entered(): |
| """Check if entered password is correct.""" |
| |
| entered_password = st.session_state.get("password", "") |
|
|
| if not entered_password: |
| st.session_state["authenticated"] = False |
| st.session_state["password_incorrect"] = True |
| return |
|
|
| |
| stored_password = None |
|
|
| |
| try: |
| if "auth" in st.secrets and "password" in st.secrets["auth"]: |
| stored_password = str(st.secrets["auth"]["password"]).strip() |
| except Exception: |
| pass |
|
|
| |
| if not stored_password: |
| try: |
| if "password" in st.secrets: |
| stored_password = str(st.secrets["password"]).strip() |
| except Exception: |
| pass |
|
|
| |
| if stored_password: |
| |
| if entered_password.strip() == stored_password: |
| st.session_state["authenticated"] = True |
| st.session_state["username"] = "user" |
| if "password" in st.session_state: |
| del st.session_state["password"] |
| return |
|
|
| |
| try: |
| if "auth" in st.secrets and "passwords" in st.secrets["auth"]: |
| username = st.session_state.get("username_input", "") |
| passwords = st.secrets["auth"]["passwords"] |
| if username in passwords: |
| stored_user_pass = str(passwords[username]).strip() |
| if entered_password.strip() == stored_user_pass: |
| st.session_state["authenticated"] = True |
| st.session_state["username"] = username |
| if "password" in st.session_state: |
| del st.session_state["password"] |
| return |
| except Exception: |
| pass |
|
|
| st.session_state["authenticated"] = False |
| st.session_state["password_incorrect"] = True |
|
|
| |
| if st.session_state.get("authenticated", False): |
| return True |
|
|
| |
| st.markdown(""" |
| <style> |
| .login-container { |
| max-width: 400px; |
| margin: 100px auto; |
| padding: 40px; |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); |
| border-radius: 15px; |
| box-shadow: 0 10px 40px rgba(0,0,0,0.3); |
| } |
| .login-title { |
| text-align: center; |
| color: #4ECDC4; |
| font-size: 2em; |
| margin-bottom: 10px; |
| } |
| .login-subtitle { |
| text-align: center; |
| color: #8b949e; |
| margin-bottom: 30px; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
| with col2: |
| st.markdown('<div class="login-title">🔥 SPARKNET</div>', unsafe_allow_html=True) |
| st.markdown('<div class="login-subtitle">Strategic Patent Acceleration & Research Kinetics NETwork</div>', unsafe_allow_html=True) |
| st.markdown(""" |
| <div style="text-align: center; color: #8b949e; font-size: 0.85em; margin: 15px 0;"> |
| AI-powered Technology Transfer Office Automation<br/> |
| <span style="color: #4ECDC4;">VISTA/Horizon EU Project</span> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| st.markdown("---") |
|
|
| |
| has_multi_user = ( |
| "auth" in st.secrets and |
| "passwords" in st.secrets["auth"] |
| ) |
|
|
| if has_multi_user: |
| st.text_input( |
| "Username", |
| key="username_input", |
| placeholder="Enter username" |
| ) |
|
|
| st.text_input( |
| "Password", |
| type="password", |
| key="password", |
| placeholder="Enter password", |
| on_change=password_entered |
| ) |
|
|
| if st.button("🔐 Login", type="primary", use_container_width=True): |
| password_entered() |
|
|
| if st.session_state.get("password_incorrect", False): |
| st.error("😕 Incorrect password. Please try again.") |
|
|
| st.markdown("---") |
| st.caption("Contact administrator for access credentials.") |
|
|
| return False |
|
|
|
|
| def logout(): |
| """Log out the current user.""" |
| st.session_state["authenticated"] = False |
| st.session_state["username"] = None |
| st.rerun() |
|
|
|
|
| def get_current_user() -> Optional[str]: |
| """Get the current logged-in username.""" |
| return st.session_state.get("username") |
|
|
|
|
| def require_auth(func): |
| """Decorator to require authentication for a page.""" |
| def wrapper(*args, **kwargs): |
| if check_password(): |
| return func(*args, **kwargs) |
| return wrapper |
|
|
|
|
| def show_logout_button(): |
| """Show logout button in sidebar.""" |
| if st.session_state.get("authenticated", False): |
| with st.sidebar: |
| st.markdown("---") |
| user = get_current_user() |
| st.caption(f"Logged in as: **{user}**") |
| if st.button("🚪 Logout", use_container_width=True): |
| logout() |
|
|