-
-
Notifications
You must be signed in to change notification settings - Fork 736
fix(security): close a set of small auth and hardening gaps #4635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6a0681a
0d653b1
6942651
e993b2f
b902d9f
a6d582c
d15d9e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,10 +31,16 @@ async def clear_user_sessions(user_id: str) -> None: | |
| Clears all active sessions for a given user. | ||
| """ | ||
| session_ids = await async_cache.smembers(f"user_sessions:{user_id}") | ||
| if session_ids: | ||
| for session_id in session_ids: | ||
| await async_cache.delete(f"session:{session_id}") | ||
| await async_cache.delete(f"user_sessions:{user_id}") | ||
| if not session_ids: | ||
| return | ||
|
|
||
| # A member arrives as bytes from a client that does not decode, and its | ||
| # repr in a key name would miss the session and leave it live. | ||
| keys = [ | ||
| f"session:{sid.decode() if isinstance(sid, bytes) else sid}" | ||
| for sid in session_ids | ||
| ] | ||
| await async_cache.delete(*keys, f"user_sessions:{user_id}") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified, and you're right that the Two things about its shape, for whoever picks this up:
I'm not fixing it in this PR. Closing it properly means the per-user revocation generation you and Greptile both suggested — stamp sessions with an issue time, record a Generated by Claude Code |
||
|
|
||
| async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
| if scope["type"] not in ("http", "websocket"): | ||
|
|
@@ -66,19 +72,29 @@ async def send_wrapper(message: Message) -> None: | |
| user_id = scope["session"].get("sub") | ||
|
|
||
| if scope["session"]: | ||
| session_id = scope["session"].pop("session_id", None) or str( | ||
| uuid.uuid4() | ||
| ) # Retrieve or create session_id | ||
| existing_id = scope["session"].pop("session_id", None) | ||
| session_id = existing_id or str(uuid.uuid4()) | ||
| session_data_json = json.dumps(scope["session"]) | ||
| await async_cache.set( | ||
| f"session:{session_id}", session_data_json, ex=self.max_age | ||
| # Refreshed only while its record is still there, so a | ||
| # session revoked mid-request is not written back. | ||
| stored = await async_cache.set( | ||
| f"session:{session_id}", | ||
| session_data_json, | ||
| ex=self.max_age, | ||
| xx=existing_id is not None, | ||
| ) | ||
|
|
||
| # Add session_id to user set of sessions | ||
| if user_id: | ||
| await async_cache.sadd(f"user_sessions:{user_id}", session_id) | ||
| if stored: | ||
| # Add session_id to user set of sessions | ||
| if user_id: | ||
| await async_cache.sadd( | ||
| f"user_sessions:{user_id}", session_id | ||
| ) | ||
|
|
||
| header_value = f"{self.session_cookie}={session_id}; path=/; Max-Age={self.max_age}; {self.security_flags}" | ||
| else: | ||
| header_value = f"{self.session_cookie}=null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; {self.security_flags}" | ||
|
|
||
| header_value = f"{self.session_cookie}={session_id}; path=/; Max-Age={self.max_age}; {self.security_flags}" | ||
| headers.append("Set-Cookie", header_value) | ||
| elif session_id: | ||
| await async_cache.delete(f"session:{session_id}") | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.