feat: implement sliding session - extend TTL on each authenticated request
This commit is contained in:
@@ -108,6 +108,12 @@ async def get_session_data(redis: aioredis.Redis, session_id: str) -> dict[str,
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
async def refresh_session_ttl(redis: aioredis.Redis, session_id: str) -> None:
|
||||
"""Extend the Redis session TTL on activity (sliding session)."""
|
||||
settings = get_settings()
|
||||
await redis.expire(f"session:{session_id}", settings.session_ttl_seconds)
|
||||
|
||||
|
||||
async def invalidate_session(redis: aioredis.Redis, session_id: str) -> None:
|
||||
"""Delete a session from Redis (logout). PostgreSQL record persists."""
|
||||
await redis.delete(f"session:{session_id}")
|
||||
|
||||
@@ -83,6 +83,9 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
content={"detail": "CSRF token mismatch", "code": "csrf_token_mismatch"},
|
||||
)
|
||||
|
||||
# Sliding session: also extend TTL on CSRF-validated unsafe requests
|
||||
await redis.expire(f"session:{session_id}", settings.session_ttl_seconds)
|
||||
finally:
|
||||
await redis.close()
|
||||
|
||||
|
||||
+4
-1
@@ -10,7 +10,7 @@ from fastapi import Depends, HTTPException, Request, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import get_settings
|
||||
from app.core.auth import get_redis, get_session_data
|
||||
from app.core.auth import get_redis, get_session_data, refresh_session_ttl
|
||||
from app.core.db import get_db, set_tenant_context
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ async def get_current_user(
|
||||
detail={"detail": "Session expired or invalid", "code": "session_invalid"},
|
||||
)
|
||||
|
||||
# Sliding session: extend TTL on each authenticated request
|
||||
await refresh_session_ttl(redis, session_id)
|
||||
|
||||
if not session_data.get("is_active", True):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
||||
+12
-1
@@ -133,7 +133,18 @@ async def me(
|
||||
detail={"detail": "Session expired or invalid", "code": "session_invalid"},
|
||||
)
|
||||
|
||||
return info
|
||||
from fastapi.responses import JSONResponse
|
||||
resp = JSONResponse(status_code=200, content=info)
|
||||
resp.set_cookie(
|
||||
key=settings.session_cookie_name,
|
||||
value=session_id,
|
||||
httponly=settings.session_cookie_httponly,
|
||||
secure=settings.session_cookie_secure,
|
||||
samesite=settings.session_cookie_samesite,
|
||||
max_age=settings.session_ttl_seconds,
|
||||
path="/",
|
||||
)
|
||||
return resp
|
||||
|
||||
|
||||
@router.get("/me/permissions")
|
||||
|
||||
Reference in New Issue
Block a user