From ca7924d5e1a0dd993898356c79c19002ba7c41bd Mon Sep 17 00:00:00 2001 From: architect-bot Date: Wed, 19 Aug 2026 16:44:14 +0000 Subject: [PATCH] WIP checkpoint: Address review feedback: Upgrade auth to RS256 signing, age-verification field, refresh-token sessions --- backend/tests/test_refresh_token_jti.py | 79 +++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 backend/tests/test_refresh_token_jti.py diff --git a/backend/tests/test_refresh_token_jti.py b/backend/tests/test_refresh_token_jti.py new file mode 100644 index 0000000..e8b1071 --- /dev/null +++ b/backend/tests/test_refresh_token_jti.py @@ -0,0 +1,79 @@ +"""Tests for refresh token uniqueness (jti claim) and model column length.""" + +import uuid +from datetime import datetime, timedelta, timezone + +import pytest +from jose import jwt + +from app.config import settings +from app.routers.auth import _create_refresh_token +from app.models.session import RefreshToken + + +class TestRefreshTokenJtiUniqueness: + """Verify that _create_refresh_token produces unique tokens even when + called with identical data in rapid succession (issue #1 from review).""" + + def test_two_calls_produce_different_tokens(self): + """Two calls with the same sub produce different JWTs because of jti.""" + token_a = _create_refresh_token({"sub": "42"}) + token_b = _create_refresh_token({"sub": "42"}) + assert token_a != token_b + + def test_jti_is_valid_uuid(self): + """The jti claim inside the refresh token is a valid UUID.""" + token = _create_refresh_token({"sub": "1"}) + payload = jwt.decode( + token, + settings.public_pem, + algorithms=[settings.ALGORITHM], + ) + assert "jti" in payload + # Verify it parses as a UUID (raises ValueError if not) + uuid.UUID(payload["jti"]) + + def test_each_token_has_unique_jti(self): + """Generate 100 tokens for the same user — all jtis must differ.""" + jtis = set() + for _ in range(100): + token = _create_refresh_token({"sub": "999"}) + payload = jwt.decode( + token, + settings.public_pem, + algorithms=[settings.ALGORITHM], + ) + jtis.add(payload["jti"]) + assert len(jtis) == 100 + + def test_jti_survives_decode(self): + """The jti claim is present and correct after decode.""" + token = _create_refresh_token({"sub": "7"}) + payload = jwt.decode( + token, + settings.public_pem, + algorithms=[settings.ALGORITHM], + ) + assert payload["sub"] == "7" + assert "jti" in payload + assert "exp" in payload + + +class TestRefreshTokenModelColumnLength: + """Verify the model's token column length matches the migration (issue #2).""" + + def test_token_column_has_length_512(self): + """RefreshToken.token column should be String(512), not unbounded.""" + from sqlalchemy import inspect + + # Get the column definition from the model's table + column = RefreshToken.__table__.columns["token"] + # The type should be String with length 512 + assert column.type.length == 512 + + def test_token_column_type_is_string(self): + """The token column should be a String type.""" + from sqlalchemy import String as SAString + + column = RefreshToken.__table__.columns["token"] + assert isinstance(column.type, SAString)