WIP checkpoint: Address review feedback: Upgrade auth to RS256 signing, age-verification field, refresh-token sessions
This commit is contained in:
@@ -26,9 +26,8 @@ class RefreshToken(Base):
|
|||||||
|
|
||||||
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||||
# RS256 JWTs are ~420+ chars; Text avoids length issues and is fine for
|
# RS256 JWTs are ~420+ chars; 512 is sufficient and matches the migration.
|
||||||
# a UUID-indexed lookup by hash in the future.
|
token = Column(String(512), unique=True, index=True, nullable=False)
|
||||||
token = Column(String, unique=True, index=True, nullable=False)
|
|
||||||
expires_at = Column(DateTime(timezone=True), nullable=False)
|
expires_at = Column(DateTime(timezone=True), nullable=False)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Authentication router - login, register, token refresh."""
|
"""Authentication router - login, register, token refresh."""
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
@@ -76,8 +76,13 @@ def _create_access_token(data: dict) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _create_refresh_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 = data.copy()
|
||||||
|
to_encode["jti"] = str(uuid.uuid4())
|
||||||
expire = datetime.now(timezone.utc) + timedelta(
|
expire = datetime.now(timezone.utc) + timedelta(
|
||||||
days=settings.REFRESH_TOKEN_EXPIRE_DAYS
|
days=settings.REFRESH_TOKEN_EXPIRE_DAYS
|
||||||
)
|
)
|
||||||
@@ -99,7 +104,7 @@ def hash_password(password: str) -> str:
|
|||||||
return pwd_context.hash(password)
|
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.
|
"""Calculate age in years from a birth date.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -174,7 +179,7 @@ async def register(req: RegisterRequest, db: AsyncSession = Depends(get_db)):
|
|||||||
date_of_birth=dob,
|
date_of_birth=dob,
|
||||||
)
|
)
|
||||||
db.add(user)
|
db.add(user)
|
||||||
await db.commit()
|
await db.flush() # flush to get user.id before creating refresh token
|
||||||
await db.refresh(user)
|
await db.refresh(user)
|
||||||
|
|
||||||
# Create access token
|
# Create access token
|
||||||
|
|||||||
Reference in New Issue
Block a user