Skip to content

Commit

Permalink
Require current password when updating password (#621)
Browse files Browse the repository at this point in the history
  • Loading branch information
meln1k authored Sep 13, 2024
1 parent d0ed477 commit fa64056
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 2 deletions.
2 changes: 1 addition & 1 deletion fixbackend/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class UserCreate(schemas.BaseUserCreate):


class UserUpdate(schemas.BaseUserUpdate):
pass
current_password: Optional[str] = None


class OAuthProviderAuthUrl(BaseModel):
Expand Down
21 changes: 20 additions & 1 deletion fixbackend/auth/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from starlette.responses import Response

from fixbackend.auth.models import User
from fixbackend.auth.schemas import OTPConfig, UserCreate
from fixbackend.auth.schemas import OTPConfig, UserCreate, UserUpdate
from fixbackend.auth.user_repository import UserRepository
from fixbackend.auth.user_verifier import AuthEmailSender
from fixbackend.config import Config
Expand Down Expand Up @@ -299,6 +299,25 @@ async def validate_password(self, password: str, user: Union[UserCreate, User])
if not re.search(r"[0-9]", password):
raise fastapi_users.InvalidPasswordException(reason="Password must contain at least one digit.")

async def update(
self,
user_update: UserUpdate, # type: ignore
user: User,
safe: bool = False,
request: Optional[Request] = None,
) -> User:
if user_update.password:
if not user_update.current_password:
raise exceptions.InvalidPasswordException(reason="Current password is required to update password.")

db_pwd_hash = user.hashed_password
user_pwd_hash = self.password_helper.hash(user_update.current_password)

if not secrets.compare_digest(db_pwd_hash, user_pwd_hash):
raise exceptions.InvalidPasswordException(reason="Current password is incorrect.")

return await super().update(user_update, user, safe)


def get_password_helper(deps: FixDependency) -> PasswordHelperProtocol | None:
return deps.service(ServiceNames.password_helper, PasswordHelper)
Expand Down
14 changes: 14 additions & 0 deletions tests/fixbackend/auth/router_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ async def test_registration_flow(
assert isinstance(event1, WorkspaceCreated)
assert str(event1.workspace_id) == workspace_json["id"]

# password can be reset only with providing a current one
response = await api_client.patch(
"/api/users/me", json={"password": "foobar@foo.com"}, cookies={session_cookie_name: auth_cookie}
)
assert response.status_code == 400

# password can be reset with providing a current one
response = await api_client.patch(
"/api/users/me",
json={"password": "FooBar123456789123456789", "current_password": registration_json["password"]},
cookies={session_cookie_name: auth_cookie},
)
assert response.status_code == 200


@pytest.mark.asyncio
async def test_mfa_flow(
Expand Down

0 comments on commit fa64056

Please sign in to comment.