Files
Hotel-Booking/Backend/src/auth/routes/session_routes.py
Iliyan Angelov 62c1fe5951 updates
2025-12-01 06:50:10 +02:00

172 lines
6.6 KiB
Python

"""
User session management routes.
"""
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'])
@router.get('/')
async def get_my_sessions(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Get current user's active sessions."""
try:
sessions = session_service.get_user_sessions(
db=db,
user_id=current_user.id,
active_only=True
)
return success_response(data={
'sessions': [{
'id': s.id,
'ip_address': s.ip_address,
'user_agent': s.user_agent,
'device_info': s.device_info,
'last_activity': s.last_activity.isoformat() if s.last_activity else None,
'created_at': s.created_at.isoformat() if s.created_at else None,
'expires_at': s.expires_at.isoformat() if s.expires_at else None
} for s in sessions]
})
except Exception as e:
logger.error(f'Error getting sessions: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@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
session = db.query(UserSession).filter(
UserSession.id == session_id,
UserSession.user_id == current_user.id
).first()
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
except Exception as e:
logger.error(f'Error revoking session: {str(e)}', exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@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,
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, '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)
raise HTTPException(status_code=500, detail=str(e))