Skip to content

Commit

Permalink
Merge pull request #17 from alex-oleshkevich/clear-storage
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-oleshkevich committed Aug 7, 2022
2 parents a675eb9 + 8b99094 commit a99a63d
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 5 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,14 @@ handle.
Some backends (like `RedisBackend`) optionally accept `serializer` argument that will be used to serialize and
deserialize session data. By default, we provide (and use) `starsessions.JsonSerializer` but you can implement your own
by extending `starsessions.Serializer` class.


## Session termination

The session cookie will be automatically removed if session has no data by the middleware.
You can manually remove session data and clear underlying storage by calling `await request.session.delete()`

## Regenerating session ID

Sometimes you need a new session ID to avoid session fixation attacks (for example, after successful signs in).
For that, use `request.session.regenerate_id()`.
69 changes: 69 additions & 0 deletions examples/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""This examples demonstrates base usage of this library.
A CookieBackend is used.
Required dependencies: python-multipart
Usage:
> uvicorn examples.login:app
Access localhost:8000/ to see a landing page
"""
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse, Response
from starlette.routing import Route

from starsessions import SessionMiddleware


async def homepage(request: Request) -> Response:
"""Access this view (GET "/") to display session contents."""
return HTMLResponse(
"""
<form method="post" action="/login">
<label> Username <input type="text" name="username"> </label>
<button type="submit">Sign in</button>
</form>
"""
)


async def login(request: Request) -> Response:
form_data = await request.form()
username = form_data['username']
request.session['username'] = username
request.session.regenerate_id()
return RedirectResponse('/profile', 302)


async def logout(request: Request) -> Response:
request.session.clear()
return RedirectResponse('/', 302)


async def profile(request: Request) -> Response:
username = request.session.get('username')
if not username:
return RedirectResponse('/', 302)

return HTMLResponse(
"""
<p>Hi, {username}!</p>
<form method="post" action="/logout">
<button type="submit">logout</button>
</form>
""".format(
username=username
)
)


routes = [
Route("/", endpoint=homepage),
Route("/login", endpoint=login, methods=['POST']),
Route("/logout", endpoint=logout, methods=['POST']),
Route("/profile", endpoint=profile),
]
middleware = [Middleware(SessionMiddleware, secret_key="secret", autoload=True)]
app = Starlette(debug=True, routes=routes, middleware=middleware)
5 changes: 4 additions & 1 deletion starsessions/backends/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ async def write(self, data: typing.Dict, session_id: typing.Optional[str] = None
return session_id

async def remove(self, session_id: str) -> None:
del self.data[session_id]
try:
del self.data[session_id]
except KeyError:
pass

async def exists(self, session_id: str) -> bool:
return session_id in self.data
3 changes: 2 additions & 1 deletion starsessions/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
async def send_wrapper(message: Message) -> None:
if message["type"] == "http.response.start":
path = self.path or scope.get("root_path", "") or "/"
if scope["session"].is_modified:
if scope["session"].is_modified and not scope['session'].is_empty:
# We have session data to persist (data was changed, cleared, etc).
nonlocal session_id
session_id = await scope["session"].persist()
Expand Down Expand Up @@ -80,6 +80,7 @@ async def send_wrapper(message: Message) -> None:
self.security_flags,
)
headers.append("Set-Cookie", header_value)
await self.backend.remove(scope['session'].session_id)
await send(message)

await self.app(scope, receive, send_wrapper)
4 changes: 1 addition & 3 deletions starsessions/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ async def persist(self) -> str:

async def delete(self) -> None:
if self.session_id:
self.data = {}
self._is_modified = True
self.clear()
await self._backend.remove(self.session_id)

async def flush(self) -> str:
self._is_modified = True
await self.delete()
return await self.regenerate_id()

Expand Down
15 changes: 15 additions & 0 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,18 @@ def test_session_cookie_domain() -> None:
# check cookie max-age
set_cookie = response.headers["set-cookie"]
assert 'Domain=example.com' in set_cookie


def test_middleware_has_to_clean_storage_after_removing_cookie() -> None:
backend = InMemoryBackend()
app = create_app()
app.add_middleware(SessionMiddleware, backend=backend, secret_key="example", autoload=True)
client = TestClient(app)

# set some session data
client.post("/update_session", json={"some": "data"})
assert len(backend.data) # it now contains 1 session

# clear session data, the middleware has to free strorage space
client.post("/clear_session")
assert not len(backend.data) # it now contains zero sessions

0 comments on commit a99a63d

Please sign in to comment.