This commit is contained in:
Iliyan Angelov
2025-12-01 06:50:10 +02:00
parent 91f51bc6fe
commit 62c1fe5951
4682 changed files with 544807 additions and 31208 deletions

View File

@@ -1,14 +1,17 @@
"""
User session management routes.
"""
from fastapi import APIRouter, Depends, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Request, Response, Cookie
from sqlalchemy.orm import Session
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...shared.config.settings import settings
from ...security.middleware.auth import get_current_user
from ...auth.models.user import User
from ...auth.models.user_session import UserSession
from ...auth.services.session_service import session_service
from ...shared.utils.response_helpers import success_response
from jose import jwt
logger = get_logger(__name__)
router = APIRouter(prefix='/sessions', tags=['sessions'])
@@ -44,13 +47,15 @@ async def get_my_sessions(
@router.delete('/{session_id}')
async def revoke_session(
session_id: int,
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
access_token: str = Cookie(None, alias='accessToken'),
db: Session = Depends(get_db)
):
"""Revoke a specific session."""
try:
# Verify session belongs to user
from ...auth.models.user_session import UserSession
session = db.query(UserSession).filter(
UserSession.id == session_id,
UserSession.user_id == current_user.id
@@ -59,10 +64,62 @@ async def revoke_session(
if not session:
raise HTTPException(status_code=404, detail='Session not found')
# Check if this is the current session being revoked
# We detect this by checking if:
# 1. The session IP matches the request IP (if available)
# 2. The session is the most recent active session
is_current_session = False
try:
client_ip = request.client.host if request.client else None
user_agent = request.headers.get('User-Agent', '')
# Check if session matches current request characteristics
if client_ip and session.ip_address == client_ip:
# Also check if it's the most recent session
recent_session = db.query(UserSession).filter(
UserSession.user_id == current_user.id,
UserSession.is_active == True
).order_by(UserSession.last_activity.desc()).first()
if recent_session and recent_session.id == session_id:
is_current_session = True
except Exception as e:
logger.warning(f'Could not determine if session is current: {str(e)}')
# If we can't determine, check if it's the only active session
active_sessions_count = db.query(UserSession).filter(
UserSession.user_id == current_user.id,
UserSession.is_active == True
).count()
if active_sessions_count <= 1:
is_current_session = True
success = session_service.revoke_session(db=db, session_token=session.session_token)
if not success:
raise HTTPException(status_code=404, detail='Session not found')
# If this was the current session, clear cookies and indicate logout needed
if is_current_session:
from ...shared.config.settings import settings
samesite_value = 'strict' if settings.is_production else 'lax'
# Clear access token cookie
response.delete_cookie(
key='accessToken',
path='/',
samesite=samesite_value,
secure=settings.is_production
)
# Clear refresh token cookie
response.delete_cookie(
key='refreshToken',
path='/',
samesite=samesite_value,
secure=settings.is_production
)
return success_response(
message='Session revoked successfully. You have been logged out.',
data={'logout_required': True}
)
return success_response(message='Session revoked successfully')
except HTTPException:
raise
@@ -72,19 +129,41 @@ async def revoke_session(
@router.post('/revoke-all')
async def revoke_all_sessions(
request: Request,
response: Response,
current_user: User = Depends(get_current_user),
access_token: str = Cookie(None, alias='accessToken'),
db: Session = Depends(get_db)
):
"""Revoke all sessions for current user."""
try:
count = session_service.revoke_all_user_sessions(
db=db,
user_id=current_user.id
user_id=current_user.id,
exclude_token=None # Don't exclude current session, revoke all
)
# Clear cookies since all sessions (including current) are revoked
from ...shared.config.settings import settings
samesite_value = 'strict' if settings.is_production else 'lax'
# Clear access token cookie
response.delete_cookie(
key='accessToken',
path='/',
samesite=samesite_value,
secure=settings.is_production
)
# Clear refresh token cookie
response.delete_cookie(
key='refreshToken',
path='/',
samesite=samesite_value,
secure=settings.is_production
)
return success_response(
data={'revoked_count': count},
message=f'Revoked {count} session(s)'
data={'revoked_count': count, 'logout_required': True},
message=f'Revoked {count} session(s). You have been logged out.'
)
except Exception as e:
logger.error(f'Error revoking all sessions: {str(e)}', exc_info=True)