diff --git a/AUTHENTICATION.md b/AUTHENTICATION.md
new file mode 100644
index 0000000..0cd0510
--- /dev/null
+++ b/AUTHENTICATION.md
@@ -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!
\ No newline at end of file
diff --git a/app.py b/app.py
index 652baf5..481b265 100644
--- a/app.py
+++ b/app.py
@@ -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('
', unsafe_allow_html=True)
+ # Add role-based access message
+ if check_role_permission("educator"):
+ st.markdown('🎓 Educator Access: You have full access to prediction features and student data analysis.
', 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('📊 Export Feature: Available for educators and administrators only.
', unsafe_allow_html=True)
# Render footer
render_footer()
if __name__ == "__main__":
+ # Configure page before authentication
+ st.set_page_config(**PAGE_CONFIG)
main()
\ No newline at end of file
diff --git a/src/utils/auth.py b/src/utils/auth.py
new file mode 100644
index 0000000..5386b61
--- /dev/null
+++ b/src/utils/auth.py
@@ -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("""
+
+ """, unsafe_allow_html=True)
+
+ with st.container():
+ st.markdown('', unsafe_allow_html=True)
+
+ st.markdown('', unsafe_allow_html=True)
+
+ # Demo credentials info
+ st.markdown("""
+
+ Demo Credentials:
+ 👨💼 Admin: admin / admin123
+ 👩🏫 Educator: educator / hello123
+
+ """, 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('
', 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"""
+
+ 👋 {st.session_state.user_name}
+ Role: {st.session_state.user_role.title()}
+ User: @{st.session_state.username}
+
+ """, 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
\ No newline at end of file
diff --git a/users.json b/users.json
new file mode 100644
index 0000000..4de439b
--- /dev/null
+++ b/users.json
@@ -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"
+ }
+}
\ No newline at end of file