diff --git a/backend/app/utils.py b/backend/app/utils.py index 29fcfc1471..9c14c82049 100644 --- a/backend/app/utils.py +++ b/backend/app/utils.py @@ -118,6 +118,9 @@ def verify_password_reset_token(token: str) -> str | None: decoded_token = jwt.decode( token, settings.SECRET_KEY, algorithms=[security.ALGORITHM] ) - return str(decoded_token["sub"]) + subject = decoded_token.get("sub") + if not isinstance(subject, str) or not subject: + return None + return subject except InvalidTokenError: return None diff --git a/backend/tests/api/routes/test_login.py b/backend/tests/api/routes/test_login.py index 96677a25f6..a12a621328 100644 --- a/backend/tests/api/routes/test_login.py +++ b/backend/tests/api/routes/test_login.py @@ -1,9 +1,11 @@ from unittest.mock import patch +import jwt from fastapi.testclient import TestClient from pwdlib.hashers.bcrypt import BcryptHasher from sqlmodel import Session +from app.core import security from app.core.config import settings from app.core.security import get_password_hash, verify_password from app.crud import create_user @@ -126,6 +128,27 @@ def test_reset_password_invalid_token( assert response["detail"] == "Invalid token" +def test_reset_password_token_without_subject_claim( + client: TestClient, superuser_token_headers: dict[str, str] +) -> None: + token_without_sub = jwt.encode( + {"nbf": 0, "exp": 4102444800}, + settings.SECRET_KEY, + algorithm=security.ALGORITHM, + ) + data = {"new_password": "changethis", "token": token_without_sub} + r = client.post( + f"{settings.API_V1_STR}/reset-password/", + headers=superuser_token_headers, + json=data, + ) + response = r.json() + + assert "detail" in response + assert r.status_code == 400 + assert response["detail"] == "Invalid token" + + def test_login_with_bcrypt_password_upgrades_to_argon2( client: TestClient, db: Session ) -> None: