Skip to content

Commit a867515

Browse files
committed
✅ Add new tests to check password updating
1 parent 69a0165 commit a867515

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

backend/tests/api/routes/test_login.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from unittest.mock import patch
22

33
from fastapi.testclient import TestClient
4+
from pwdlib.hashers.bcrypt import BcryptHasher
45
from sqlmodel import Session
56

67
from app.core.config import settings
7-
from app.core.security import verify_password
8+
from app.core.security import get_password_hash, verify_password
89
from app.crud import create_user
9-
from app.models import UserCreate
10+
from app.models import User, UserCreate
1011
from app.utils import generate_password_reset_token
1112
from tests.utils.user import user_authentication_headers
1213
from tests.utils.utils import random_email, random_lower_string
@@ -117,3 +118,68 @@ def test_reset_password_invalid_token(
117118
assert "detail" in response
118119
assert r.status_code == 400
119120
assert response["detail"] == "Invalid token"
121+
122+
123+
def test_login_with_bcrypt_password_upgrades_to_argon2(
124+
client: TestClient, db: Session
125+
) -> None:
126+
"""Test that logging in with a bcrypt password hash upgrades it to argon2."""
127+
email = random_email()
128+
password = random_lower_string()
129+
130+
# Create a bcrypt hash directly (simulating legacy password)
131+
bcrypt_hasher = BcryptHasher()
132+
bcrypt_hash = bcrypt_hasher.hash(password)
133+
assert bcrypt_hash.startswith("$2") # bcrypt hashes start with $2
134+
135+
user = User(email=email, hashed_password=bcrypt_hash, is_active=True)
136+
db.add(user)
137+
db.commit()
138+
db.refresh(user)
139+
140+
assert user.hashed_password.startswith("$2")
141+
142+
login_data = {"username": email, "password": password}
143+
r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data)
144+
assert r.status_code == 200
145+
tokens = r.json()
146+
assert "access_token" in tokens
147+
148+
db.refresh(user)
149+
150+
# Verify the hash was upgraded to argon2
151+
assert user.hashed_password.startswith("$argon2")
152+
153+
verified, updated_hash = verify_password(password, user.hashed_password)
154+
assert verified
155+
# Should not need another update since it's already argon2
156+
assert updated_hash is None
157+
158+
159+
def test_login_with_argon2_password_keeps_hash(client: TestClient, db: Session) -> None:
160+
"""Test that logging in with an argon2 password hash does not update it."""
161+
email = random_email()
162+
password = random_lower_string()
163+
164+
# Create an argon2 hash (current default)
165+
argon2_hash = get_password_hash(password)
166+
assert argon2_hash.startswith("$argon2")
167+
168+
# Create user with argon2 hash
169+
user = User(email=email, hashed_password=argon2_hash, is_active=True)
170+
db.add(user)
171+
db.commit()
172+
db.refresh(user)
173+
174+
original_hash = user.hashed_password
175+
176+
login_data = {"username": email, "password": password}
177+
r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data)
178+
assert r.status_code == 200
179+
tokens = r.json()
180+
assert "access_token" in tokens
181+
182+
db.refresh(user)
183+
184+
assert user.hashed_password == original_hash
185+
assert user.hashed_password.startswith("$argon2")

backend/tests/crud/test_user.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from fastapi.encoders import jsonable_encoder
2+
from pwdlib.hashers.bcrypt import BcryptHasher
23
from sqlmodel import Session
34

45
from app import crud
5-
from app.core.security import verify_password
6+
from app.core.security import get_password_hash, verify_password
67
from app.models import User, UserCreate, UserUpdate
78
from tests.utils.utils import random_email, random_lower_string
89

@@ -90,3 +91,40 @@ def test_update_user(db: Session) -> None:
9091
assert user.email == user_2.email
9192
verified, _ = verify_password(new_password, user_2.hashed_password)
9293
assert verified
94+
95+
96+
def test_authenticate_user_with_bcrypt_upgrades_to_argon2(db: Session) -> None:
97+
"""Test that a user with bcrypt password hash gets upgraded to argon2 on login."""
98+
email = random_email()
99+
password = random_lower_string()
100+
101+
# Create a bcrypt hash directly (simulating legacy password)
102+
bcrypt_hasher = BcryptHasher()
103+
bcrypt_hash = bcrypt_hasher.hash(password)
104+
assert bcrypt_hash.startswith("$2") # bcrypt hashes start with $2
105+
106+
# Create user with bcrypt hash directly in the database
107+
user = User(email=email, hashed_password=bcrypt_hash)
108+
db.add(user)
109+
db.commit()
110+
db.refresh(user)
111+
112+
# Verify the hash is bcrypt before authentication
113+
assert user.hashed_password.startswith("$2")
114+
115+
# Authenticate - this should upgrade the hash to argon2
116+
authenticated_user = crud.authenticate(session=db, email=email, password=password)
117+
assert authenticated_user
118+
assert authenticated_user.email == email
119+
120+
db.refresh(authenticated_user)
121+
122+
# Verify the hash was upgraded to argon2
123+
assert authenticated_user.hashed_password.startswith("$argon2")
124+
125+
verified, updated_hash = verify_password(
126+
password, authenticated_user.hashed_password
127+
)
128+
assert verified
129+
# Should not need another update since it's already argon2
130+
assert updated_hash is None

0 commit comments

Comments
 (0)