feat(api): add Redis session cache to reduce Appwrite API calls by ~90%
- Add SessionCacheService with 5-minute TTL Redis cache - Cache validated sessions to avoid redundant Appwrite calls - Add /api/v1/auth/me endpoint for retrieving current user - Invalidate cache on logout and password reset - Add session_cache config to auth section (Redis db 2) - Fix Docker Redis hostname (localhost -> redis) - Handle timezone-aware datetime comparisons Security: tokens hashed before use as cache keys, explicit invalidation on logout/password change, graceful degradation when Redis unavailable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ from flask import Blueprint, request, make_response, render_template, redirect,
|
||||
from appwrite.exception import AppwriteException
|
||||
|
||||
from app.services.appwrite_service import AppwriteService
|
||||
from app.services.session_cache_service import SessionCacheService
|
||||
from app.utils.response import (
|
||||
success_response,
|
||||
created_response,
|
||||
@@ -305,7 +306,11 @@ def api_logout():
|
||||
if not token:
|
||||
return unauthorized_response(message="No active session")
|
||||
|
||||
# Logout user
|
||||
# Invalidate session cache before Appwrite logout
|
||||
cache = SessionCacheService()
|
||||
cache.invalidate_token(token)
|
||||
|
||||
# Logout user from Appwrite
|
||||
appwrite = AppwriteService()
|
||||
appwrite.logout_user(session_id=token)
|
||||
|
||||
@@ -340,6 +345,36 @@ def api_logout():
|
||||
return error_response(message="An unexpected error occurred", code="INTERNAL_ERROR")
|
||||
|
||||
|
||||
@auth_bp.route('/api/v1/auth/me', methods=['GET'])
|
||||
@require_auth
|
||||
def api_get_current_user():
|
||||
"""
|
||||
Get the currently authenticated user's data.
|
||||
|
||||
This endpoint is lightweight and uses cached session data when available,
|
||||
making it suitable for frequent use (e.g., checking user tier, verifying
|
||||
session is still valid).
|
||||
|
||||
Returns:
|
||||
200: User data
|
||||
401: Not authenticated
|
||||
"""
|
||||
user = get_current_user()
|
||||
|
||||
if not user:
|
||||
return unauthorized_response(message="Not authenticated")
|
||||
|
||||
return success_response(
|
||||
result={
|
||||
"id": user.id,
|
||||
"email": user.email,
|
||||
"name": user.name,
|
||||
"email_verified": user.email_verified,
|
||||
"tier": user.tier
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@auth_bp.route('/api/v1/auth/verify-email', methods=['GET'])
|
||||
def api_verify_email():
|
||||
"""
|
||||
@@ -480,6 +515,10 @@ def api_reset_password():
|
||||
appwrite = AppwriteService()
|
||||
appwrite.confirm_password_reset(user_id=user_id, secret=secret, password=password)
|
||||
|
||||
# Invalidate all cached sessions for this user (security: password changed)
|
||||
cache = SessionCacheService()
|
||||
cache.invalidate_user(user_id)
|
||||
|
||||
logger.info("Password reset successfully", user_id=user_id)
|
||||
|
||||
return success_response(
|
||||
|
||||
Reference in New Issue
Block a user