From 7f4e3eb4285108a2cc3fb2dce1544642ac06fa84 Mon Sep 17 00:00:00 2001 From: Ejiro Asiuwhu Date: Wed, 25 Mar 2026 01:11:22 +0100 Subject: [PATCH] feat: add session management methods to StackServerApp and AsyncStackServerApp - list_sessions(user_id) returns list of ActiveSession for a user - get_session(session_id, user_id) filters from list_sessions, returns ActiveSession or None - revoke_session(session_id, user_id) deletes session via DELETE endpoint - Both sync and async facades implement all three methods --- .../python/src/stack_auth/_app.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/sdks/implementations/python/src/stack_auth/_app.py b/sdks/implementations/python/src/stack_auth/_app.py index 209efbd08..0a53226c7 100644 --- a/sdks/implementations/python/src/stack_auth/_app.py +++ b/sdks/implementations/python/src/stack_auth/_app.py @@ -17,6 +17,7 @@ from stack_auth._constants import DEFAULT_BASE_URL from stack_auth._pagination import PaginatedResult, _PaginationMeta from stack_auth._token_store import TokenStore, TokenStoreInit, resolve_token_store from stack_auth.errors import ApiKeyError, NotFoundError +from stack_auth.models.sessions import ActiveSession from stack_auth.models.users import ServerUser # Sentinel object to distinguish "not provided" from "explicitly None". @@ -200,6 +201,49 @@ class StackServerApp: return None return self.get_user(data["user_id"]) + # -- session management -------------------------------------------------- + + def list_sessions(self, user_id: str) -> list[ActiveSession]: + """List active sessions for a user. + + Args: + user_id: The user whose sessions to list. + + Returns: + A list of :class:`ActiveSession` objects. + """ + data = self._client.request( + "GET", "/auth/sessions", params={"user_id": user_id} + ) + return [ + ActiveSession.model_validate(item) + for item in (data or {}).get("items", []) + ] + + def get_session( + self, session_id: str, *, user_id: str + ) -> ActiveSession | None: + """Get a specific session by ID. + + Fetches all sessions for the user and filters by *session_id*. + Returns ``None`` if the session is not found. + """ + sessions = self.list_sessions(user_id) + return next((s for s in sessions if s.id == session_id), None) + + def revoke_session(self, session_id: str, *, user_id: str) -> None: + """Revoke (delete) a session. + + Args: + session_id: The session to revoke. + user_id: The user who owns the session. + """ + self._client.request( + "DELETE", + f"/auth/sessions/{session_id}", + params={"user_id": user_id}, + ) + # --------------------------------------------------------------------------- # AsyncStackServerApp (async) @@ -372,3 +416,46 @@ class AsyncStackServerApp: if data is None or "user_id" not in data: return None return await self.get_user(data["user_id"]) + + # -- session management -------------------------------------------------- + + async def list_sessions(self, user_id: str) -> list[ActiveSession]: + """List active sessions for a user. + + Args: + user_id: The user whose sessions to list. + + Returns: + A list of :class:`ActiveSession` objects. + """ + data = await self._client.request( + "GET", "/auth/sessions", params={"user_id": user_id} + ) + return [ + ActiveSession.model_validate(item) + for item in (data or {}).get("items", []) + ] + + async def get_session( + self, session_id: str, *, user_id: str + ) -> ActiveSession | None: + """Get a specific session by ID. + + Fetches all sessions for the user and filters by *session_id*. + Returns ``None`` if the session is not found. + """ + sessions = await self.list_sessions(user_id) + return next((s for s in sessions if s.id == session_id), None) + + async def revoke_session(self, session_id: str, *, user_id: str) -> None: + """Revoke (delete) a session. + + Args: + session_id: The session to revoke. + user_id: The user who owns the session. + """ + await self._client.request( + "DELETE", + f"/auth/sessions/{session_id}", + params={"user_id": user_id}, + )