implemented auth

This commit is contained in:
2025-09-28 00:52:31 +03:00
parent e6aa56210f
commit 5cfc3fe507
4 changed files with 544 additions and 5 deletions
+201
View File
@@ -0,0 +1,201 @@
# 🔐 Authentication Guide
This guide explains how to use the authentication system in the Student Success Predictor application.
## 🚀 Quick Start
### Default Credentials
The application comes with two pre-configured demo accounts:
**Administrator Account:**
- Username: `admin`
- Password: `admin123`
- Role: Admin (full access + user management)
**Educator Account:**
- Username: `educator`
- Password: `hello123`
- Role: Educator (prediction access + export features)
### First Login
1. Run the application: `streamlit run app.py`
2. You'll see the login screen with demo credentials displayed
3. Use either account above to log in
4. **Important:** Change default passwords in production!
## 👥 User Roles & Permissions
### 🏷️ Role Hierarchy
1. **Admin** - Full access to everything
- Use prediction features
- Export prediction reports
- Manage users (add/delete)
- Access admin panel
2. **Educator** - Educational staff access
- Use prediction features
- Export prediction reports
- Cannot manage users
3. **User** - Basic access (for future use)
- Use prediction features only
- No export capabilities
- No admin functions
## 🛠️ User Management (Admin Only)
### Adding New Users
1. Login as admin
2. In the sidebar, expand "👨‍💼 Admin Panel"
3. Fill out the "Add New User" form:
- Username (unique identifier)
- Password (secure password)
- Full Name (display name)
- Role (user/educator/admin)
4. Click "➕ Add User"
### Managing Existing Users
- View all users in the "Existing Users" section
- Delete users with the 🗑️ button (except yourself)
- Users are stored in `users.json` file
## 🔒 Security Features
### Password Hashing
- All passwords are hashed using SHA-256
- Plain text passwords are never stored
- Secure credential verification
### Session Management
- Automatic session timeout (30 minutes of inactivity)
- Secure session state management
- Proper logout functionality
### Role-Based Access Control
- Different features available based on user role
- Hierarchical permission system
- Secure admin-only functions
## 📁 Data Storage
### User Data File
- Users stored in: `users.json`
- Contains hashed passwords only
- Includes user roles and names
- Automatically created on first run
### Session Storage
- Uses Streamlit's built-in session state
- No persistent session storage
- Secure temporary authentication
## 🔧 Customization
### Adding New Roles
Edit `src/utils/auth.py` and modify:
```python
role_hierarchy = {
"admin": 3,
"educator": 2,
"student": 1, # Add new role here
"user": 0
}
```
### Changing Session Timeout
In `app.py`, modify the timeout value:
```python
check_session_timeout(60) # 60 minutes instead of 30
```
### Custom Password Requirements
Add validation in `src/utils/auth.py`:
```python
def validate_password(password: str) -> bool:
# Add your password requirements here
return len(password) >= 8 and any(c.isdigit() for c in password)
```
## 🚨 Production Security
### Important Security Steps for Production:
1. **Change Default Passwords**
```python
# Delete users.json and create new admin account
```
2. **Use Environment Variables**
```python
import os
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', 'secure_password')
```
3. **Implement Database Storage**
- Replace JSON file with proper database
- Use encrypted connection strings
- Implement proper backup procedures
4. **Add Password Complexity Requirements**
- Minimum length requirements
- Special character requirements
- Password expiration policies
5. **Implement Rate Limiting**
- Prevent brute force attacks
- Temporary account lockouts
- Login attempt logging
6. **Enable HTTPS**
- Secure data transmission
- SSL/TLS certificates
- Secure cookie settings
## 🐛 Troubleshooting
### Login Issues
**Problem:** "Invalid username or password"
- **Solution:** Check credentials carefully, case-sensitive
**Problem:** Session expired message
- **Solution:** Normal after 30 minutes, just log in again
**Problem:** Cannot access admin features
- **Solution:** Ensure you're logged in with admin role
### File Permission Issues
**Problem:** Cannot create users.json
- **Solution:** Check write permissions in app directory
**Problem:** Cannot save new users
- **Solution:** Ensure sufficient disk space and permissions
## 📝 Development Notes
### File Structure
```
src/utils/auth.py # Main authentication module
users.json # User credentials storage (auto-created)
app.py # Main app with auth integration
```
### Key Functions
- `require_auth()` - Decorator for protected functions
- `check_role_permission()` - Role-based access control
- `render_admin_panel()` - User management interface
- `check_session_timeout()` - Session timeout handling
This authentication system provides a secure, user-friendly way to control access to your Student Success Predictor application!
+21 -5
View File
@@ -14,6 +14,7 @@ sys.path.append(str(src_path))
from src.config.theme import ROSE_PINE_THEME, PAGE_CONFIG, APP_TITLE, APP_SUBTITLE
from src.config.mappings import create_feature_mappings
from src.utils.model_utils import load_model_and_info
from src.utils.auth import require_auth, check_role_permission, render_admin_panel, check_session_timeout
from src.components.form_components import (
render_preset_selector,
render_personal_info_section,
@@ -77,10 +78,11 @@ def collect_user_input():
return user_input
@require_auth
def main():
"""Main Streamlit application"""
# Configure page
st.set_page_config(**PAGE_CONFIG)
# Check session timeout (30 minutes)
check_session_timeout(30)
# Apply theme
apply_theme()
@@ -88,9 +90,14 @@ def main():
# Load model and preprocessing info
model, preprocessing_info, feature_names = load_model_and_info()
# Render header
# Render header with user context
render_header()
# Add admin panel if user is admin
if check_role_permission("admin"):
with st.sidebar:
render_admin_panel()
# Collect user input
user_input = collect_user_input()
@@ -100,6 +107,10 @@ def main():
with col1:
st.markdown('<h2 class="sub-header">Prediction Results</h2>', unsafe_allow_html=True)
# Add role-based access message
if check_role_permission("educator"):
st.markdown('<div class="info-box">🎓 <strong>Educator Access:</strong> You have full access to prediction features and student data analysis.</div>', unsafe_allow_html=True)
# Render prediction section
render_prediction_section(model, preprocessing_info, user_input)
@@ -107,11 +118,16 @@ def main():
# Render model info and validation
render_model_info_sidebar(user_input)
# Render export section
render_export_section(user_input)
# Render export section (role-based access)
if check_role_permission("educator"):
render_export_section(user_input)
else:
st.markdown('<div class="warning-box">📊 <strong>Export Feature:</strong> Available for educators and administrators only.</div>', unsafe_allow_html=True)
# Render footer
render_footer()
if __name__ == "__main__":
# Configure page before authentication
st.set_page_config(**PAGE_CONFIG)
main()
+300
View File
@@ -0,0 +1,300 @@
"""
Authentication module for Student Success Predictor
Provides secure login/logout functionality with session management
"""
import streamlit as st
import hashlib
import json
import os
from typing import Dict, Optional, Tuple
from pathlib import Path
# Path to store user credentials (in production, use a proper database)
USERS_FILE = Path("users.json")
# Default admin user (change password in production!)
DEFAULT_USERS = {
"admin": {
"password_hash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9", # "admin123"
"role": "admin",
"name": "Administrator"
},
"educator": {
"password_hash": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", # "hello123"
"role": "educator",
"name": "Educator"
}
}
def hash_password(password: str) -> str:
"""Hash a password using SHA-256"""
return hashlib.sha256(password.encode()).hexdigest()
def load_users() -> Dict:
"""Load users from file or create default users"""
if USERS_FILE.exists():
try:
with open(USERS_FILE, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
pass
# Create default users file
save_users(DEFAULT_USERS)
return DEFAULT_USERS
def save_users(users: Dict) -> None:
"""Save users to file"""
with open(USERS_FILE, 'w') as f:
json.dump(users, f, indent=2)
def verify_credentials(username: str, password: str) -> Tuple[bool, Optional[Dict]]:
"""Verify user credentials"""
users = load_users()
if username in users:
stored_hash = users[username]["password_hash"]
input_hash = hash_password(password)
if stored_hash == input_hash:
return True, users[username]
return False, None
def init_session_state():
"""Initialize session state for authentication"""
if 'authenticated' not in st.session_state:
st.session_state.authenticated = False
if 'username' not in st.session_state:
st.session_state.username = None
if 'user_role' not in st.session_state:
st.session_state.user_role = None
if 'user_name' not in st.session_state:
st.session_state.user_name = None
def login_form():
"""Render login form with Rose Pine styling"""
st.markdown("""
<style>
.login-container {
max-width: 400px;
margin: 2rem auto;
padding: 2rem;
background: linear-gradient(135deg, var(--rp-surface), var(--rp-overlay));
border-radius: 16px;
border: 2px solid var(--rp-iris);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.login-header {
text-align: center;
color: var(--rp-text);
font-size: 2rem;
font-weight: 700;
margin-bottom: 2rem;
background: linear-gradient(135deg, var(--rp-iris), var(--rp-foam));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.login-info {
background: linear-gradient(135deg, var(--rp-overlay), var(--rp-surface));
padding: 1rem;
border-radius: 8px;
border-left: 4px solid var(--rp-gold);
margin-bottom: 1.5rem;
color: var(--rp-text);
font-size: 0.9rem;
}
</style>
""", unsafe_allow_html=True)
with st.container():
st.markdown('<div class="login-container">', unsafe_allow_html=True)
st.markdown('<h1 class="login-header">🔐 Student Success Predictor</h1>', unsafe_allow_html=True)
# Demo credentials info
st.markdown("""
<div class="login-info">
<strong>Demo Credentials:</strong><br>
👨‍💼 Admin: <code>admin</code> / <code>admin123</code><br>
👩‍🏫 Educator: <code>educator</code> / <code>hello123</code>
</div>
""", unsafe_allow_html=True)
with st.form("login_form"):
username = st.text_input("Username", placeholder="Enter your username")
password = st.text_input("Password", type="password", placeholder="Enter your password")
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
login_button = st.form_submit_button("🚀 Login", use_container_width=True)
if login_button:
if username and password:
is_valid, user_info = verify_credentials(username, password)
if is_valid:
st.session_state.authenticated = True
st.session_state.username = username
st.session_state.user_role = user_info["role"]
st.session_state.user_name = user_info["name"]
st.success(f"Welcome back, {user_info['name']}!")
st.rerun()
else:
st.error("❌ Invalid username or password")
else:
st.warning("⚠️ Please enter both username and password")
st.markdown('</div>', unsafe_allow_html=True)
def logout():
"""Logout user and clear session"""
st.session_state.authenticated = False
st.session_state.username = None
st.session_state.user_role = None
st.session_state.user_name = None
st.rerun()
def render_user_info():
"""Render user information in sidebar"""
if st.session_state.authenticated:
with st.sidebar:
st.markdown("---")
st.markdown("### 👤 User Information")
# User info card
st.markdown(f"""
<div style="
background: linear-gradient(135deg, var(--rp-surface), var(--rp-overlay));
padding: 1rem;
border-radius: 8px;
border-left: 3px solid var(--rp-iris);
margin-bottom: 1rem;
">
<strong>👋 {st.session_state.user_name}</strong><br>
<small>Role: {st.session_state.user_role.title()}</small><br>
<small>User: @{st.session_state.username}</small>
</div>
""", unsafe_allow_html=True)
# Logout button
if st.button("🚪 Logout", type="secondary", use_container_width=True):
logout()
def require_auth(func):
"""Decorator to require authentication for functions"""
def wrapper(*args, **kwargs):
init_session_state()
if not st.session_state.authenticated:
login_form()
return None
# Add user info to sidebar
render_user_info()
return func(*args, **kwargs)
return wrapper
def check_role_permission(required_role: str = None) -> bool:
"""Check if current user has required role"""
if not st.session_state.authenticated:
return False
if required_role is None:
return True
user_role = st.session_state.user_role
# Define role hierarchy (admin > educator > user)
role_hierarchy = {
"admin": 3,
"educator": 2,
"user": 1
}
current_level = role_hierarchy.get(user_role, 0)
required_level = role_hierarchy.get(required_role, 0)
return current_level >= required_level
def add_user(username: str, password: str, name: str, role: str = "user") -> bool:
"""Add a new user (admin only)"""
if not check_role_permission("admin"):
return False
users = load_users()
if username in users:
return False # User already exists
users[username] = {
"password_hash": hash_password(password),
"role": role,
"name": name
}
save_users(users)
return True
def render_admin_panel():
"""Render admin panel for user management"""
if not check_role_permission("admin"):
st.error("🚫 Access denied. Admin privileges required.")
return
st.markdown("### 👨‍💼 Admin Panel")
with st.expander("👥 User Management"):
st.markdown("#### Add New User")
with st.form("add_user_form"):
new_username = st.text_input("Username")
new_password = st.text_input("Password", type="password")
new_name = st.text_input("Full Name")
new_role = st.selectbox("Role", ["user", "educator", "admin"])
if st.form_submit_button("➕ Add User"):
if all([new_username, new_password, new_name]):
if add_user(new_username, new_password, new_name, new_role):
st.success(f"✅ User '{new_username}' added successfully!")
else:
st.error("❌ User already exists or operation failed")
else:
st.warning("⚠️ Please fill all fields")
# Display existing users
st.markdown("#### Existing Users")
users = load_users()
for username, info in users.items():
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
st.text(f"👤 {info['name']} (@{username})")
with col2:
st.text(f"🏷️ {info['role']}")
with col3:
if username != st.session_state.username: # Can't delete self
if st.button("🗑️", key=f"del_{username}", help="Delete user"):
del users[username]
save_users(users)
st.rerun()
# Session timeout functionality
def check_session_timeout(timeout_minutes: int = 30):
"""Check if session has timed out"""
import time
if 'last_activity' not in st.session_state:
st.session_state.last_activity = time.time()
current_time = time.time()
time_diff = (current_time - st.session_state.last_activity) / 60 # Convert to minutes
if time_diff > timeout_minutes and st.session_state.authenticated:
st.warning(f"⏰ Session expired after {timeout_minutes} minutes of inactivity")
logout()
else:
st.session_state.last_activity = current_time
+22
View File
@@ -0,0 +1,22 @@
{
"admin": {
"password_hash": "240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9",
"role": "admin",
"name": "Administrator"
},
"educator": {
"password_hash": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92",
"role": "educator",
"name": "Educator"
},
"izzy": {
"password_hash": "edf90c9dbbecc484d3507d19ab11d39e53ec3ed1c19e447fc229c0b008cc6721",
"role": "educator",
"name": "izzy"
},
"wow": {
"password_hash": "5257b476780ffe3a534df683a88355ace82f722998a813e9a12ffa23ffffca79",
"role": "user",
"name": "wow"
}
}