Refactor authentication and session management for optimized performance, enhanced security, and improved error handling

- Replaced N+1 deletion pattern with a bulk `DELETE` in session cleanup for better efficiency in `session_async`.
- Updated security utilities to use HMAC-SHA256 signatures to mitigate length extension attacks and added constant-time comparisons to prevent timing attacks.
- Improved exception hierarchy with custom error types `AuthError` and `DatabaseError` for better granularity in error handling.
- Enhanced logging with `exc_info=True` for detailed error contexts across authentication services.
- Removed unused imports and reordered imports for cleaner code structure.
This commit is contained in:
Felipe Cardoso
2025-11-01 04:50:01 +01:00
parent ea544ecbac
commit 61173d0dc1
4 changed files with 144 additions and 98 deletions

View File

@@ -1,13 +1,14 @@
"""
Async CRUD operations for user sessions using SQLAlchemy 2.0 patterns.
"""
import logging
from datetime import datetime, timezone, timedelta
from typing import List, Optional
from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import and_, select, update, delete, func
from sqlalchemy.orm import selectinload, joinedload
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from app.crud.base_async import CRUDBaseAsync
from app.models.user_session import UserSession
@@ -335,6 +336,61 @@ class CRUDSessionAsync(CRUDBaseAsync[UserSession, SessionCreate, SessionUpdate])
logger.error(f"Error cleaning up expired sessions: {str(e)}")
raise
async def cleanup_expired_for_user(
self,
db: AsyncSession,
*,
user_id: str
) -> int:
"""
Clean up expired and inactive sessions for a specific user.
Uses single bulk DELETE query for efficiency instead of N individual deletes.
Args:
db: Database session
user_id: User ID to cleanup sessions for
Returns:
Number of sessions deleted
"""
try:
# Validate UUID
try:
uuid_obj = uuid.UUID(user_id)
except (ValueError, AttributeError):
logger.error(f"Invalid UUID format: {user_id}")
raise ValueError(f"Invalid user ID format: {user_id}")
now = datetime.now(timezone.utc)
# Use bulk DELETE with WHERE clause - single query
stmt = delete(UserSession).where(
and_(
UserSession.user_id == uuid_obj,
UserSession.is_active == False,
UserSession.expires_at < now
)
)
result = await db.execute(stmt)
await db.commit()
count = result.rowcount
if count > 0:
logger.info(
f"Cleaned up {count} expired sessions for user {user_id} using bulk DELETE"
)
return count
except Exception as e:
await db.rollback()
logger.error(
f"Error cleaning up expired sessions for user {user_id}: {str(e)}"
)
raise
async def get_user_session_count(self, db: AsyncSession, *, user_id: str) -> int:
"""
Get count of active sessions for a user.