""" 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.""" # Safely get password from session state entered_password = st.session_state.get("password", "") if not entered_password: st.session_state["authenticated"] = False st.session_state["password_incorrect"] = True return # Get password(s) from secrets - try multiple locations stored_password = None # Try auth.password first try: if "auth" in st.secrets and "password" in st.secrets["auth"]: stored_password = str(st.secrets["auth"]["password"]).strip() except Exception: pass # Fallback: try root level password if not stored_password: try: if "password" in st.secrets: stored_password = str(st.secrets["password"]).strip() except Exception: pass # Check single password if stored_password: # Use simple comparison (strip whitespace from both) 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 # Check multiple users (auth.passwords) 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 # Check if already authenticated if st.session_state.get("authenticated", False): return True # Show login form st.markdown(""" """, unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.markdown('
🔥 SPARKNET
', unsafe_allow_html=True) st.markdown('
Strategic Patent Acceleration & Research Kinetics NETwork
', unsafe_allow_html=True) st.markdown("""
AI-powered Technology Transfer Office Automation
VISTA/Horizon EU Project
""", unsafe_allow_html=True) st.markdown("---") # Check if we have multi-user setup 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()