Skip to content

PATCH /users/me bypasses password verification and allows unverified email changes #10

@gzark1

Description

@gzark1

Description

The PATCH /users/me endpoint allows users to change their password and email without proper security checks, bypassing the secure /users/me/change-password endpoint.

Steps to Reproduce

  1. Register a user with email test@example.com and password OldPassword123
  2. Login to get JWT token
  3. Call PATCH /users/me with {"password": "NewPassword456"} (no old password required)
  4. Try logging in with old password - fails
  5. Try logging in with new password - succeeds

Expected Behavior

  • Password change should require old password verification (like /users/me/change-password does)
  • Email change should require verification of new email ownership

Actual Behavior

  • Password can be changed without providing old password
  • Email can be changed without verifying ownership of new address
  • Both changes persist to database immediately

Root Cause

fastapi-users provides PATCH /users/me with UserUpdate schema that includes password: Optional[str] and email: Optional[EmailStr]. The custom /users/me/change-password endpoint was added with proper old password
verification, but the PATCH endpoint still exists and bypasses this security measure.

Files involved:

  • backend/api/users.py:36-40 - mounts fastapi-users router
  • backend/schemas/user.py:42-62 - UserUpdate schema

Possible Solutions

  1. Option A: Override UserUpdate to exclude password field
    class UserUpdate(schemas.BaseUserUpdate):
        password: None = None
    
        @field_validator("password", mode="before")
        @classmethod
        def force_password_none(cls, v):
            return None
  2. Option B: Block both password and email changes via PATCH
  class UserUpdate(schemas.BaseUserUpdate):
      password: Optional[str] = None
      email: Optional[str] = None

      @field_validator("password", "email", mode="before")
      @classmethod
      def block_sensitive_fields(cls, v):
          return None

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions