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)
|
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:
|
async def invalidate_session(redis: aioredis.Redis, session_id: str) -> None:
|
||||||
"""Delete a session from Redis (logout). PostgreSQL record persists."""
|
"""Delete a session from Redis (logout). PostgreSQL record persists."""
|
||||||
await redis.delete(f"session:{session_id}")
|
await redis.delete(f"session:{session_id}")
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
|||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
content={"detail": "CSRF token mismatch", "code": "csrf_token_mismatch"},
|
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:
|
finally:
|
||||||
await redis.close()
|
await redis.close()
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -10,7 +10,7 @@ from fastapi import Depends, HTTPException, Request, status
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.config import get_settings
|
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
|
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"},
|
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):
|
if not session_data.get("is_active", True):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|||||||
+12
-1
@@ -133,7 +133,18 @@ async def me(
|
|||||||
detail={"detail": "Session expired or invalid", "code": "session_invalid"},
|
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")
|
@router.get("/me/permissions")
|
||||||
|
|||||||
Reference in New Issue
Block a user