The SoulSense EQ Assessment now includes a comprehensive session tracking system that generates unique session IDs for every user login. This feature improves security, enables better session management, and provides detailed tracking of user activities.
- Every successful login generates a cryptographically secure session ID (256-bit)
- Uses Python's
secretsmodule for secure random token generation - Each session ID is guaranteed to be unique (format: URL-safe base64 encoded string)
- All session data is stored in the database with comprehensive tracking
- Stores: session ID, user ID, username, timestamps, IP address (optional), user agent (optional)
- Maintains relationship with user records through foreign keys
- Sessions can be validated at any time using the session ID
- Automatic expiration after 24 hours of inactivity
- Last accessed timestamp updated on each validation
- Sessions are properly invalidated on user logout
- Logout timestamp recorded for audit purposes
- Session marked as inactive but retained for historical tracking
- Users can have multiple concurrent active sessions (different devices/browsers)
- Each session maintains independent state
- Ability to view all active sessions for a user
- Bulk invalidation of all sessions for a specific user
- Automatic cleanup of old/expired sessions
- Query active sessions with filtering options
The Session model includes the following fields:
class Session(Base):
id = Integer (Primary Key)
session_id = String (Unique, Indexed) - The unique session identifier
user_id = Integer (Foreign Key to users.id, Indexed)
username = String (Indexed) - Denormalized for quick lookups
created_at = String (ISO 8601 timestamp, Indexed)
last_accessed = String (ISO 8601 timestamp)
ip_address = String (Optional)
user_agent = String (Optional)
is_active = Boolean (Indexed)
logged_out_at = String (Optional)To optimize query performance, the following indexes are created:
idx_session_user_active: (user_id, is_active)idx_session_username_active: (username, is_active)idx_session_created: (created_at)- Unique index on
session_id
Session.user→User.sessions: Many-to-One relationship- Cascade delete: When a user is deleted, all their sessions are automatically removed
from app.auth import AuthManager
# Create auth manager
auth = AuthManager()
# Register a user
success, message = auth.register_user("john_doe", "secure_password")
# Login (creates a session)
success, message = auth.login_user("john_doe", "secure_password")
if success:
print(f"Session ID: {auth.current_session_id}")
print(f"Current User: {auth.current_user}")
# Logout (invalidates the session)
success, message = auth.logout_user()# Validate a session
is_valid, username = auth.validate_session(session_id)
if is_valid:
print(f"Valid session for user: {username}")
else:
print("Invalid or expired session")# Get all active sessions for a user
active_sessions = auth.get_active_sessions("john_doe")
for session in active_sessions:
print(f"Session: {session['session_id']}")
print(f"Created: {session['created_at']}")
print(f"Last Accessed: {session['last_accessed']}")
# Invalidate all sessions for a user
count = auth.invalidate_user_sessions("john_doe")
print(f"Invalidated {count} sessions")# Clean up sessions older than 24 hours (default)
count = auth.cleanup_old_sessions()
# Clean up sessions older than 48 hours
count = auth.cleanup_old_sessions(hours=48)
print(f"Cleaned up {count} old sessions")If you have an existing database without the sessions table:
# Add the sessions table to your database
python migrations/add_sessions_table.py
# To rollback (remove the sessions table)
python migrations/add_sessions_table.py --rollbackThe sessions table will be created automatically when initializing a new database.
- Description: Authenticates user and creates a new session
- Parameters:
username(str): The usernamepassword(str): The password
- Returns:
(success: bool, message: str) - Side Effects: Sets
self.current_userandself.current_session_idon success
- Description: Invalidates the current session and clears user data
- Returns:
(success: bool, message: str) - Side Effects: Clears
self.current_userandself.current_session_id
- Description: Validates if a session is active and not expired
- Parameters:
session_id(str): The session ID to validate
- Returns:
(is_valid: bool, username: str or None) - Side Effects: Updates
last_accessedtimestamp for valid sessions
- Description: Invalidates sessions older than specified hours
- Parameters:
hours(int): Age threshold in hours (default: 24)
- Returns:
count: int- Number of sessions cleaned up
- Description: Retrieves all active sessions, optionally filtered by username
- Parameters:
username(str, optional): Filter by username
- Returns:
list[dict]- List of session dictionaries
- Description: Invalidates all active sessions for a specific user
- Parameters:
username(str): The username
- Returns:
count: int- Number of sessions invalidated
- Uses
secrets.token_urlsafe(32)which generates 256-bit cryptographically secure tokens - Resistant to brute force attacks
- URL-safe format for easy transmission
- Sessions automatically expire after 24 hours of inactivity
- Expired sessions cannot be validated even if they're marked as active
- Session IDs are indexed for fast lookups
- Timestamps stored in ISO 8601 format (UTC)
- Optional IP address and user agent tracking for enhanced security
- Always validate session IDs before granting access
- Run regular cleanup to remove old sessions
- Consider implementing additional security measures:
- IP address validation
- User agent validation
- Concurrent session limits
- Session refresh tokens
Run the comprehensive test suite:
# Run all session tests
pytest tests/test_sessions.py -v
# Run specific test
pytest tests/test_sessions.py::TestSessionManagement::test_session_id_generation_on_login -vThe test suite covers:
- ✓ Session ID generation on login
- ✓ Unique session IDs for multiple logins
- ✓ Session data storage correctness
- ✓ Session invalidation on logout
- ✓ Multiple concurrent sessions
- ✓ Session validation
- ✓ Session cleanup
- ✓ Bulk session invalidation
- ✓ Last accessed timestamp updates
Run the demonstration script to see all features in action:
python demo_session_tracking.pyThis will demonstrate:
- Basic session flow (login → session creation → logout)
- Multiple concurrent sessions
- Session validation
- Session cleanup
- Bulk session invalidation
- Detailed session information
from app.db import get_session
from app.models import Session
from sqlalchemy import func
session = get_session()
# Total active sessions
active_count = session.query(func.count(Session.id))\
.filter(Session.is_active == True).scalar()
# Average session duration
# (This would require end time calculation)
# Sessions created today
from datetime import datetime, timedelta
today = datetime.utcnow().date().isoformat()
today_sessions = session.query(func.count(Session.id))\
.filter(Session.created_at >= today).scalar()
session.close()Solution: Ensure the sessions table exists in your database. Run the migration:
python migrations/add_sessions_table.pyPossible causes:
- Session has expired (> 24 hours old)
- Session was logged out
- Session ID is incorrect
Debug:
from app.db import get_session
from app.models import Session
db_session = get_session()
session_record = db_session.query(Session).filter_by(session_id=your_session_id).first()
if session_record:
print(f"Active: {session_record.is_active}")
print(f"Created: {session_record.created_at}")
db_session.close()Solution: Run cleanup regularly:
auth = AuthManager()
auth.cleanup_old_sessions(hours=12) # More aggressive cleanupAll critical fields are indexed:
session_id: Unique index for O(1) lookup(user_id, is_active): Composite index for user session queries(username, is_active): Composite index for username-based queriescreated_at: Index for time-based queries and cleanup
- Use parameterized queries to prevent SQL injection
- Batch operations where possible
- Regular cleanup to prevent table bloat
# Run this periodically (e.g., daily cron job)
def daily_session_maintenance():
auth = AuthManager()
# Clean up sessions older than 24 hours
cleaned = auth.cleanup_old_sessions(hours=24)
logging.info(f"Cleaned up {cleaned} old sessions")
# Optional: Delete very old session records to save space
from datetime import datetime, timedelta
cutoff = (datetime.utcnow() - timedelta(days=90)).isoformat()
session = get_session()
try:
session.query(Session).filter(
Session.created_at < cutoff
).delete()
session.commit()
finally:
session.close()Potential improvements to consider:
- Session Refresh: Implement token refresh mechanism
- Concurrent Session Limits: Limit number of active sessions per user
- Device Management: Allow users to view and manage their devices
- Geographic Tracking: Store and display login locations
- Suspicious Activity Detection: Alert on unusual session patterns
- Remember Me: Extended session duration option
- Session Transfer: Transfer session between devices
All acceptance criteria have been met:
- ✓ Every login generates a unique session ID
- ✓ Session ID is cryptographically secure (256-bit)
- ✓ Session data stored with user ID and timestamp
- ✓ Sessions can be validated using session ID
- ✓ Sessions invalidated on logout (marked inactive with timestamp)
- ✓ No stale sessions remain active (automatic cleanup available)
- ✓ No duplicate session IDs (enforced by unique constraint)
For issues or questions:
- Check the troubleshooting section above
- Run the demonstration script to verify functionality
- Review test cases for usage examples
- Check logs for error messages
This feature is part of the SoulSense EQ Assessment system and follows the same license terms.