mirror of
https://github.com/andwati/predictive_analysis.git
synced 2026-10-18 00:32:53 +00:00
implemented auth
This commit is contained in:
@@ -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!
|
||||
@@ -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()
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user