From 758307bbee29e9c356741f55cc57d743fd72f106 Mon Sep 17 00:00:00 2001 From: architect-bot Date: Wed, 19 Aug 2026 16:40:38 +0000 Subject: [PATCH] WIP checkpoint: Address review feedback: Upgrade auth to RS256 signing, age-verification field, refresh-token sessions --- backend/app/models/session.py | 5 ++--- backend/app/routers/auth.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/app/models/session.py b/backend/app/models/session.py index 8fe773b..274e13b 100644 --- a/backend/app/models/session.py +++ b/backend/app/models/session.py @@ -26,9 +26,8 @@ class RefreshToken(Base): id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) - # RS256 JWTs are ~420+ chars; Text avoids length issues and is fine for - # a UUID-indexed lookup by hash in the future. - token = Column(String, unique=True, index=True, nullable=False) + # RS256 JWTs are ~420+ chars; 512 is sufficient and matches the migration. + token = Column(String(512), unique=True, index=True, nullable=False) expires_at = Column(DateTime(timezone=True), nullable=False) is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 1c6ab0d..260042f 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -1,7 +1,7 @@ """Authentication router - login, register, token refresh.""" import uuid -from datetime import datetime, timedelta, timezone +from datetime import date, datetime, timedelta, timezone from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm @@ -76,8 +76,13 @@ def _create_access_token(data: dict) -> str: def _create_refresh_token(data: dict) -> str: - """Create a signed refresh token using RS256.""" + """Create a signed refresh token using RS256. + + Includes a unique ``jti`` claim so that concurrent calls produce + distinct tokens even when issued within the same second. + """ to_encode = data.copy() + to_encode["jti"] = str(uuid.uuid4()) expire = datetime.now(timezone.utc) + timedelta( days=settings.REFRESH_TOKEN_EXPIRE_DAYS ) @@ -99,7 +104,7 @@ def hash_password(password: str) -> str: return pwd_context.hash(password) -def calculate_age(birth_date, today=None) -> int: +def calculate_age(birth_date: date, today: date | None = None) -> int: """Calculate age in years from a birth date. Args: @@ -174,7 +179,7 @@ async def register(req: RegisterRequest, db: AsyncSession = Depends(get_db)): date_of_birth=dob, ) db.add(user) - await db.commit() + await db.flush() # flush to get user.id before creating refresh token await db.refresh(user) # Create access token