diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1deceb8..e6bf82bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] + +## [0.29.2] - 2025-05-19 +- Fixes cookies being set without expiry in Django + - Reverts timezone change from 0.28.0 and uses GMT + +### Infrastructure - Sets up workflow to run backend-sdk-testing - Updates test-servers to work with updated tests - Adds workflow to test supertokens-website @@ -35,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migrates unit tests from CircleCI to Github Actions - Adds lint/format checks to Github Actions +## [0.28.2] - 2025-05-19 +- Fixes cookies being set without expiry in Django + - Reverts timezone change from 0.28.0 and uses GMT ## [0.28.1] - 2025-02-26 - Pins `httpx` and `respx` to current major versions (<1.0.0) diff --git a/setup.py b/setup.py index 19c08ee41..81783e273 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ setup( name="supertokens_python", - version="0.29.1", + version="0.29.2", author="SuperTokens", license="Apache 2.0", author_email="team@supertokens.com", diff --git a/supertokens_python/constants.py b/supertokens_python/constants.py index 07e00aeeb..a11cadf94 100644 --- a/supertokens_python/constants.py +++ b/supertokens_python/constants.py @@ -15,7 +15,7 @@ from __future__ import annotations SUPPORTED_CDI_VERSIONS = ["5.2"] -VERSION = "0.29.1" +VERSION = "0.29.2" TELEMETRY = "/telemetry" USER_COUNT = "/users/count" USER_DELETE = "/user/remove" diff --git a/supertokens_python/framework/django/django_response.py b/supertokens_python/framework/django/django_response.py index 264a314f4..4c9e7e12a 100644 --- a/supertokens_python/framework/django/django_response.py +++ b/supertokens_python/framework/django/django_response.py @@ -51,7 +51,9 @@ def set_cookie( key=key, value=value, expires=datetime.fromtimestamp(ceil(expires / 1000)).strftime( - "%a, %d %b %Y %H:%M:%S UTC" + # NOTE: This should always be GMT. HTTP only supports GMT in cookies. + # If this is not respected, the cookie is always treated as a session cookie. + "%a, %d %b %Y %H:%M:%S GMT" ), path=path, domain=domain, diff --git a/tests/Django/test_django.py b/tests/Django/test_django.py index a2cdb9ed1..f294af2b5 100644 --- a/tests/Django/test_django.py +++ b/tests/Django/test_django.py @@ -279,7 +279,7 @@ async def test_login_handle(self): try: datetime.strptime( - cookies["sAccessToken"]["expires"], "%a, %d %b %Y %H:%M:%S UTC" + cookies["sAccessToken"]["expires"], "%a, %d %b %Y %H:%M:%S GMT" ) except ValueError: assert False, "cookies expiry time doesn't have the correct format" diff --git a/tests/test_session.py b/tests/test_session.py index 5b6420a5c..4b5c3e1c7 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -644,13 +644,15 @@ async def test_token_cookie_expires( for c in response.cookies.jar: if c.name == "sAccessToken": # 100 years (set by the SDK) # some time must have elasped since the cookie was set. So less than current time - assert datetime.fromtimestamp(c.expires or 0, tz=timezone.utc) - timedelta( - days=365.25 * 100 - ) < datetime.now(tz=timezone.utc) + assert datetime.fromtimestamp( + c.expires or 0, tz=timezone(timedelta(0), "GMT") + ) - timedelta(days=365.25 * 100) < datetime.now( + tz=timezone(timedelta(0), "GMT") + ) if c.name == "sRefreshToken": # 100 days (set by the core) - assert datetime.fromtimestamp(c.expires or 0, tz=timezone.utc) - timedelta( - days=100 - ) < datetime.now(tz=timezone.utc) + assert datetime.fromtimestamp( + c.expires or 0, tz=timezone(timedelta(0), "GMT") + ) - timedelta(days=100) < datetime.now(tz=timezone(timedelta(0), "GMT")) assert response.headers["anti-csrf"] != "" assert response.headers["front-token"] != "" @@ -672,13 +674,15 @@ async def test_token_cookie_expires( for c in response.cookies.jar: if c.name == "sAccessToken": # 100 years (set by the SDK) # some time must have elasped since the cookie was set. So less than current time - assert datetime.fromtimestamp(c.expires or 0, tz=timezone.utc) - timedelta( - days=365.25 * 100 - ) < datetime.now(tz=timezone.utc) + assert datetime.fromtimestamp( + c.expires or 0, tz=timezone(timedelta(0), "GMT") + ) - timedelta(days=365.25 * 100) < datetime.now( + tz=timezone(timedelta(0), "GMT") + ) if c.name == "sRefreshToken": # 100 days (set by the core) - assert datetime.fromtimestamp(c.expires or 0, tz=timezone.utc) - timedelta( - days=100 - ) < datetime.now(tz=timezone.utc) + assert datetime.fromtimestamp( + c.expires or 0, tz=timezone(timedelta(0), "GMT") + ) - timedelta(days=100) < datetime.now(tz=timezone(timedelta(0), "GMT")) assert response.headers["anti-csrf"] != "" assert response.headers["front-token"] != "" diff --git a/tests/utils.py b/tests/utils.py index 99d7424f3..4e553e8cb 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -17,7 +17,7 @@ # Import AsyncMock import sys from contextlib import contextmanager -from datetime import datetime +from datetime import datetime, timezone from functools import lru_cache from http.cookies import SimpleCookie from os import environ @@ -215,7 +215,11 @@ def assert_info_clears_tokens(info: Dict[str, Any], token_transfer_method: str): def get_unix_timestamp(expiry: str): - return int(datetime.strptime(expiry, "%a, %d %b %Y %H:%M:%S UTC").timestamp()) + return int( + datetime.strptime(expiry, "%a, %d %b %Y %H:%M:%S GMT") + .replace(tzinfo=timezone.utc) + .timestamp() + ) def verify_within_5_second_diff(n1: int, n2: int):