Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 

78 rader
3.0 KiB

  1. import json
  2. import typing
  3. from base64 import b64decode, b64encode
  4. import itsdangerous
  5. from itsdangerous.exc import BadSignature
  6. from starlette.datastructures import MutableHeaders, Secret
  7. from starlette.requests import HTTPConnection
  8. from starlette.types import ASGIApp, Message, Receive, Scope, Send
  9. class SessionMiddleware:
  10. def __init__(
  11. self,
  12. app: ASGIApp,
  13. secret_key: typing.Union[str, Secret],
  14. session_cookie: str = "session",
  15. max_age: int = 14 * 24 * 60 * 60, # 14 days, in seconds
  16. same_site: str = "lax",
  17. https_only: bool = False,
  18. ) -> None:
  19. self.app = app
  20. self.signer = itsdangerous.TimestampSigner(str(secret_key))
  21. self.session_cookie = session_cookie
  22. self.max_age = max_age
  23. self.security_flags = "httponly; samesite=" + same_site
  24. if https_only: # Secure flag can be used with HTTPS only
  25. self.security_flags += "; secure"
  26. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  27. if scope["type"] not in ("http", "websocket"): # pragma: no cover
  28. await self.app(scope, receive, send)
  29. return
  30. connection = HTTPConnection(scope)
  31. initial_session_was_empty = True
  32. if self.session_cookie in connection.cookies:
  33. data = connection.cookies[self.session_cookie].encode("utf-8")
  34. try:
  35. data = self.signer.unsign(data, max_age=self.max_age)
  36. scope["session"] = json.loads(b64decode(data))
  37. initial_session_was_empty = False
  38. except BadSignature:
  39. scope["session"] = {}
  40. else:
  41. scope["session"] = {}
  42. async def send_wrapper(message: Message) -> None:
  43. if message["type"] == "http.response.start":
  44. path = scope.get("root_path", "") or "/"
  45. if scope["session"]:
  46. # We have session data to persist.
  47. data = b64encode(json.dumps(scope["session"]).encode("utf-8"))
  48. data = self.signer.sign(data)
  49. headers = MutableHeaders(scope=message)
  50. header_value = "%s=%s; path=%s; Max-Age=%d; %s" % (
  51. self.session_cookie,
  52. data.decode("utf-8"),
  53. path,
  54. self.max_age,
  55. self.security_flags,
  56. )
  57. headers.append("Set-Cookie", header_value)
  58. elif not initial_session_was_empty:
  59. # The session has been cleared.
  60. headers = MutableHeaders(scope=message)
  61. header_value = "{}={}; {}".format(
  62. self.session_cookie,
  63. f"null; path={path}; expires=Thu, 01 Jan 1970 00:00:00 GMT;",
  64. self.security_flags,
  65. )
  66. headers.append("Set-Cookie", header_value)
  67. await send(message)
  68. await self.app(scope, receive, send_wrapper)