| | """ |
| | 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() |
| |
|