From ebba983a1d791ed461a3e3ccd4d7f069d51b77d7 Mon Sep 17 00:00:00 2001 From: 0xWheatyz Date: Sat, 14 Mar 2026 14:22:30 -0400 Subject: [PATCH] fix(auth): ensure JWT sub claim is RFC 7519 compliant string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change TokenPayload.sub type from int to str per JWT RFC 7519 - Add user_id property to TokenPayload for int conversion - Update token creation to serialize user_id as string - Update token consumers to use payload.user_id - Change dashboard port from 3000 to 8080 - Add pydantic[email] for email validation - Update default admin email to admin@sparc.dev 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- SPARC/api.py | 2 +- SPARC/auth.py | 13 +++++++++---- docker-compose.yml | 2 +- requirements.txt | 1 + scripts/init_database.py | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SPARC/api.py b/SPARC/api.py index 3b4130b..cf76493 100644 --- a/SPARC/api.py +++ b/SPARC/api.py @@ -236,7 +236,7 @@ async def refresh_token(request: RefreshRequest): ) db = get_db_client() - user = db.get_user_by_id(payload.sub) + user = db.get_user_by_id(payload.user_id) if not user: raise HTTPException( diff --git a/SPARC/auth.py b/SPARC/auth.py index 285054a..4a5a28f 100644 --- a/SPARC/auth.py +++ b/SPARC/auth.py @@ -24,12 +24,17 @@ security = HTTPBearer() class TokenPayload(BaseModel): """JWT token payload.""" - sub: int # user_id + sub: str # user_id as string (JWT RFC 7519 requires sub to be a string) email: str role: str exp: datetime type: str # "access" or "refresh" + @property + def user_id(self) -> int: + """Get user_id as integer.""" + return int(self.sub) + class TokenResponse(BaseModel): """Token response model.""" @@ -61,7 +66,7 @@ def create_access_token(user_id: int, email: str, role: str) -> str: """ expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) payload = { - "sub": user_id, + "sub": str(user_id), "email": email, "role": role, "exp": expire, @@ -83,7 +88,7 @@ def create_refresh_token(user_id: int, email: str, role: str) -> str: """ expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) payload = { - "sub": user_id, + "sub": str(user_id), "email": email, "role": role, "exp": expire, @@ -166,7 +171,7 @@ async def get_current_user( ) db = get_db_client() - user = db.get_user_by_id(payload.sub) + user = db.get_user_by_id(payload.user_id) if not user: raise HTTPException( diff --git a/docker-compose.yml b/docker-compose.yml index 05c74b1..7aa4e63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,7 +53,7 @@ services: build: ./frontend container_name: sparc-dashboard ports: - - "3000:80" + - "8080:80" depends_on: - api restart: unless-stopped diff --git a/requirements.txt b/requirements.txt index fb29367..7e87235 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ openai psycopg2-binary fastapi uvicorn[standard] +pydantic[email] httpx numpy pandas diff --git a/scripts/init_database.py b/scripts/init_database.py index c9c7ea6..607ca1f 100644 --- a/scripts/init_database.py +++ b/scripts/init_database.py @@ -19,7 +19,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from SPARC import config from SPARC.database import DatabaseClient -DEFAULT_ADMIN_EMAIL = "admin@sparc.local" +DEFAULT_ADMIN_EMAIL = "admin@sparc.dev" def generate_password(length: int = 16) -> str: