Coder Social home page Coder Social logo

Comments (6)

aranvir avatar aranvir commented on June 16, 2024 1

Based on @guacs suggestion I implemented and tested the following overrides. Used the session example code from the docs with this CustomServerSideSessionConfig and it works fine.

  • The session id is available before the route handler scope is reached
  • It will be used as session id by the store_in _message function
  • It's available to the user via request.scope.get('_session_id'). Might not be the most explicit way but simpler than an extra dependency I guess? And for convenience, this can always be wrapped in a function and made available as dependency injection if that's preferred.
from typing import Any

from litestar.connection import ASGIConnection
from litestar.types import Receive, Scope, Send
from litestar.middleware.base import DefineMiddleware
from litestar.middleware.session.base import SessionMiddleware
from litestar.middleware.session.server_side import ServerSideSessionConfig, ServerSideSessionBackend
from litestar.datastructures import Cookie, MutableScopeHeaders
from litestar.types import Empty, Message, ScopeSession
from litestar.utils.dataclass import extract_dataclass_items


class CustomSessionMiddleware(SessionMiddleware):
    """Custom override to generate a session id before the route handler."""
    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        """ASGI-callable.

        Args:
            scope: The ASGI connection scope.
            receive: The ASGI receive function.
            send: The ASGI send function.

        Returns:
            None
        """
        connection = ASGIConnection[Any, Any, Any, Any](scope, receive=receive, send=send)
        scope["session"] = await self.backend.load_from_connection(connection)

        # get existing session ID or create new one here
        scope["_session_id"] = self.backend.get_session_id(connection)

        await self.app(scope, receive, self.create_send_wrapper(connection))


class CustomServerSideSessionBackend(ServerSideSessionBackend):
    """Custom override to use the session id generated by the Middleware before the route handler instead of
    generating it after the route handler.
    """
    def get_session_id(self, connection: ASGIConnection):
        session_id = connection.cookies.get(self.config.key)
        if not session_id or session_id == "null":
            session_id = self.generate_session_id()
        return session_id

    async def store_in_message(self, scope_session: ScopeSession, message: Message, connection: ASGIConnection) -> None:
        scope = connection.scope
        store = self.config.get_store_from_app(scope["app"])
        headers = MutableScopeHeaders.from_message(message)
        session_id = connection.cookies.get(self.config.key)
        if not session_id or session_id == "null":
            session_id = scope["_session_id"]  # replaced id generation with loading from scope

        cookie_params = dict(extract_dataclass_items(self.config, exclude_none=True, include=Cookie.__dict__.keys()))

        if scope_session is Empty:
            await self.delete(session_id, store=store)
            headers.add(
                "Set-Cookie",
                Cookie(value="null", key=self.config.key, expires=0, **cookie_params).to_header(header=""),
            )
        else:
            serialised_data = self.serialize_data(scope_session, scope)
            await self.set(session_id=session_id, data=serialised_data, store=store)
            headers.add(
                "Set-Cookie", Cookie(value=session_id, key=self.config.key, **cookie_params).to_header(header="")
            )


class CustomServerSideSessionConfig(ServerSideSessionConfig):
    """Load custom overrides for session id generation before route handler."""
    _backend_class = CustomServerSideSessionBackend

    @property
    def middleware(self) -> DefineMiddleware:
        return DefineMiddleware(CustomSessionMiddleware, backend=self._backend_class(config=self))

from litestar.

guacs avatar guacs commented on June 16, 2024 1

@aranvir would you like to make a PR based on this?

Also, it may be better if we just return the session ID whenever the user calls request.set_session. Currently that returns None, but we could change that to just return the value of _session_id from the scope. Also, while I marked it as private, I think it may be better to just make the session ID part of the public API i.e. add it to the TypedDict that defines Scope.

from litestar.

aranvir avatar aranvir commented on June 16, 2024

Thought about this some more. In my example, I only consider the use case where a session requires authentication. But there is also the case where you'd want "anonymous" sessions, e.g., you put stuff in a shopping cart and want to check out without a user account. In that case you'd want any route to be able to start a session instead of creating a session via a "logino" route.

I don't know if it makes sense to try and put both use cases in one session solution... probably cleaner to have separate implementations for separate needs.

from litestar.

provinzkraut avatar provinzkraut commented on June 16, 2024

I don't know if it makes sense to try and put both use cases in one session solution... probably cleaner to have separate implementations for separate needs.

It does. Sessions aren't tied to authentication. How your app handles users is entirely up to you; If you add an anonymous user, their session would be indistinguishable from an authenticated user. The only difference is what kind of data you store; If you have anonymous user sessions, you shouldn't treat "has a session" as "is authenticated", but store that information inside the session.

from litestar.

guacs avatar guacs commented on June 16, 2024

Instead of having to inject the session backend as a dependency, how about we just return the current session ID whenever the user calls request.set_session?

I was thinking that we could create it or get the existing one in the SessionMiddleware before we invoke the route handler. So something like this:

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        """ASGI-callable.

        Args:
            scope: The ASGI connection scope.
            receive: The ASGI receive function.
            send: The ASGI send function.

        Returns:
            None
        """

        connection = ASGIConnection[Any, Any, Any, Any](scope, receive=receive, send=send)
        scope["session"] = await self.backend.load_from_connection(connection)

        # get existing session ID or create new one here
        scope["_session_id"] = self.backend.get_session_id(connection)

        await self.app(scope, receive, self.create_send_wrapper(connection))

from litestar.

github-actions avatar github-actions commented on June 16, 2024

A fix for this issue has been released in v2.7.0

from litestar.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.