File size: 7,414 Bytes
a05467c
 
 
 
76c3b0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a05467c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2bd5cbf
 
 
 
 
 
 
 
5d516d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a05467c
 
5d516d9
 
 
 
 
 
 
 
 
 
a05467c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d516d9
 
 
 
 
 
 
a05467c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
"""
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("""
    <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("---")

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