From 169144630d75be6d935a42d16b41456e709fefd8 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Tue, 12 May 2026 16:35:18 -0700 Subject: [PATCH 1/2] fix: use BigInteger for OAuthAccount.expires_at to avoid 32-bit overflow --- fastapi_users_db_sqlalchemy/__init__.py | 4 ++-- tests/test_users.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/fastapi_users_db_sqlalchemy/__init__.py b/fastapi_users_db_sqlalchemy/__init__.py index 467a2bf..0f42dbf 100644 --- a/fastapi_users_db_sqlalchemy/__init__.py +++ b/fastapi_users_db_sqlalchemy/__init__.py @@ -5,7 +5,7 @@ from fastapi_users.db.base import BaseUserDatabase from fastapi_users.models import ID, OAP, UP -from sqlalchemy import Boolean, ForeignKey, Integer, String, func, select +from sqlalchemy import BigInteger, Boolean, ForeignKey, String, func, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped, declared_attr, mapped_column from sqlalchemy.sql import Select @@ -70,7 +70,7 @@ class SQLAlchemyBaseOAuthAccountTable(Generic[ID]): String(length=100), index=True, nullable=False ) access_token: Mapped[str] = mapped_column(String(length=1024), nullable=False) - expires_at: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) + expires_at: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True) refresh_token: Mapped[Optional[str]] = mapped_column( String(length=1024), nullable=True ) diff --git a/tests/test_users.py b/tests/test_users.py index 4a83e39..258eafc 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -219,3 +219,9 @@ async def test_queries_oauth( "foo", "bar" ) assert unknown_oauth_user is None + + +def test_oauth_account_expires_at_is_big_integer(): + from sqlalchemy import BigInteger + + assert isinstance(OAuthAccount.__table__.c.expires_at.type, BigInteger) From 21ef62760ef8f807027fac3b4d3ba4cf8ff23c1e Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Sun, 16 Aug 2026 02:46:40 -0700 Subject: [PATCH 2/2] test: round trip an oauth expires_at past the 32 bit limit Signed-off-by: Sai Asish Y --- tests/test_users.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/test_users.py b/tests/test_users.py index 258eafc..2607c4d 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -221,7 +221,22 @@ async def test_queries_oauth( assert unknown_oauth_user is None -def test_oauth_account_expires_at_is_big_integer(): - from sqlalchemy import BigInteger +@pytest.mark.asyncio +async def test_queries_oauth_expires_at_after_2038( + sqlalchemy_user_db_oauth: SQLAlchemyUserDatabase[UserOAuth, UUID_ID], + oauth_account1: dict[str, Any], +): + """It should store an expiry past the 32 bit signed limit.""" + # 2038-01-19T03:14:08Z, one second past the largest signed 32 bit value + expires_at = 2147483648 - assert isinstance(OAuthAccount.__table__.c.expires_at.type, BigInteger) + user = await sqlalchemy_user_db_oauth.create( + {"email": "lancelot@camelot.bt", "hashed_password": "guinevere"} + ) + user = await sqlalchemy_user_db_oauth.add_oauth_account( + user, {**oauth_account1, "expires_at": expires_at} + ) + + oauth_user = await sqlalchemy_user_db_oauth.get(user.id) + assert oauth_user is not None + assert oauth_user.oauth_accounts[0].expires_at == expires_at