mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2025-11-02 12:21:53 +08:00
Fix #561: Update a user with an email already existing in DB raises an error
This commit is contained in:
@ -10,6 +10,7 @@ class ErrorCode:
|
|||||||
VERIFY_USER_BAD_TOKEN = "VERIFY_USER_BAD_TOKEN"
|
VERIFY_USER_BAD_TOKEN = "VERIFY_USER_BAD_TOKEN"
|
||||||
VERIFY_USER_ALREADY_VERIFIED = "VERIFY_USER_ALREADY_VERIFIED"
|
VERIFY_USER_ALREADY_VERIFIED = "VERIFY_USER_ALREADY_VERIFIED"
|
||||||
VERIFY_USER_TOKEN_EXPIRED = "VERIFY_USER_TOKEN_EXPIRED"
|
VERIFY_USER_TOKEN_EXPIRED = "VERIFY_USER_TOKEN_EXPIRED"
|
||||||
|
UPDATE_USER_EMAIL_ALREADY_EXISTS = "UPDATE_USER_EMAIL_ALREADY_EXISTS"
|
||||||
|
|
||||||
|
|
||||||
async def run_handler(handler: Callable, *args, **kwargs):
|
async def run_handler(handler: Callable, *args, **kwargs):
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from fastapi_users import models
|
|||||||
from fastapi_users.authentication import Authenticator
|
from fastapi_users.authentication import Authenticator
|
||||||
from fastapi_users.db import BaseUserDatabase
|
from fastapi_users.db import BaseUserDatabase
|
||||||
from fastapi_users.password import get_password_hash
|
from fastapi_users.password import get_password_hash
|
||||||
from fastapi_users.router.common import run_handler
|
from fastapi_users.router.common import ErrorCode, run_handler
|
||||||
|
|
||||||
|
|
||||||
def get_users_router(
|
def get_users_router(
|
||||||
@ -35,6 +35,20 @@ def get_users_router(
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
async def _check_unique_email(
|
||||||
|
updated_user: user_update_model, # type: ignore
|
||||||
|
) -> None:
|
||||||
|
updated_user = cast(
|
||||||
|
models.BaseUserUpdate, updated_user
|
||||||
|
) # Prevent mypy complain
|
||||||
|
if updated_user.email:
|
||||||
|
user = await user_db.get_by_email(updated_user.email)
|
||||||
|
if user is not None:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS,
|
||||||
|
)
|
||||||
|
|
||||||
async def _update_user(
|
async def _update_user(
|
||||||
user: models.BaseUserDB, update_dict: Dict[str, Any], request: Request
|
user: models.BaseUserDB, update_dict: Dict[str, Any], request: Request
|
||||||
):
|
):
|
||||||
@ -55,7 +69,11 @@ def get_users_router(
|
|||||||
):
|
):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@router.patch("/me", response_model=user_model)
|
@router.patch(
|
||||||
|
"/me",
|
||||||
|
response_model=user_model,
|
||||||
|
dependencies=[Depends(get_current_active_user), Depends(_check_unique_email)],
|
||||||
|
)
|
||||||
async def update_me(
|
async def update_me(
|
||||||
request: Request,
|
request: Request,
|
||||||
updated_user: user_update_model, # type: ignore
|
updated_user: user_update_model, # type: ignore
|
||||||
@ -81,7 +99,7 @@ def get_users_router(
|
|||||||
@router.patch(
|
@router.patch(
|
||||||
"/{id:uuid}",
|
"/{id:uuid}",
|
||||||
response_model=user_model,
|
response_model=user_model,
|
||||||
dependencies=[Depends(get_current_superuser)],
|
dependencies=[Depends(get_current_superuser), Depends(_check_unique_email)],
|
||||||
)
|
)
|
||||||
async def update_user(
|
async def update_user(
|
||||||
id: UUID4, updated_user: user_update_model, request: Request # type: ignore
|
id: UUID4, updated_user: user_update_model, request: Request # type: ignore
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import pytest
|
|||||||
from fastapi import FastAPI, Request, status
|
from fastapi import FastAPI, Request, status
|
||||||
|
|
||||||
from fastapi_users.authentication import Authenticator
|
from fastapi_users.authentication import Authenticator
|
||||||
from fastapi_users.router import get_users_router
|
from fastapi_users.router import ErrorCode, get_users_router
|
||||||
from tests.conftest import MockAuthentication, User, UserDB, UserUpdate
|
from tests.conftest import MockAuthentication, User, UserDB, UserUpdate
|
||||||
|
|
||||||
SECRET = "SECRET"
|
SECRET = "SECRET"
|
||||||
@ -144,6 +144,28 @@ class TestUpdateMe:
|
|||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
assert after_update.called is False
|
assert after_update.called is False
|
||||||
|
|
||||||
|
async def test_existing_email(
|
||||||
|
self,
|
||||||
|
test_app_client: Tuple[httpx.AsyncClient, bool],
|
||||||
|
user: UserDB,
|
||||||
|
verified_user: UserDB,
|
||||||
|
after_update,
|
||||||
|
):
|
||||||
|
client, requires_verification = test_app_client
|
||||||
|
response = await client.patch(
|
||||||
|
"/me",
|
||||||
|
json={"email": verified_user.email},
|
||||||
|
headers={"Authorization": f"Bearer {user.id}"},
|
||||||
|
)
|
||||||
|
if requires_verification:
|
||||||
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
assert after_update.called is False
|
||||||
|
else:
|
||||||
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
data = cast(Dict[str, Any], response.json())
|
||||||
|
assert data["detail"] == ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS
|
||||||
|
assert after_update.called is False
|
||||||
|
|
||||||
async def test_empty_body(
|
async def test_empty_body(
|
||||||
self,
|
self,
|
||||||
test_app_client: Tuple[httpx.AsyncClient, bool],
|
test_app_client: Tuple[httpx.AsyncClient, bool],
|
||||||
@ -685,6 +707,25 @@ class TestUpdateUser:
|
|||||||
data = cast(Dict[str, Any], response.json())
|
data = cast(Dict[str, Any], response.json())
|
||||||
assert data["email"] == "king.arthur@tintagel.bt"
|
assert data["email"] == "king.arthur@tintagel.bt"
|
||||||
|
|
||||||
|
async def test_existing_email_verified_superuser(
|
||||||
|
self,
|
||||||
|
test_app_client: Tuple[httpx.AsyncClient, bool],
|
||||||
|
user: UserDB,
|
||||||
|
verified_user: UserDB,
|
||||||
|
verified_superuser: UserDB,
|
||||||
|
after_update,
|
||||||
|
):
|
||||||
|
client, _ = test_app_client
|
||||||
|
response = await client.patch(
|
||||||
|
f"/{user.id}",
|
||||||
|
json={"email": verified_user.email},
|
||||||
|
headers={"Authorization": f"Bearer {verified_superuser.id}"},
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
data = cast(Dict[str, Any], response.json())
|
||||||
|
assert data["detail"] == ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS
|
||||||
|
assert after_update.called is False
|
||||||
|
|
||||||
async def test_valid_body_verified_superuser(
|
async def test_valid_body_verified_superuser(
|
||||||
self,
|
self,
|
||||||
test_app_client: Tuple[httpx.AsyncClient, bool],
|
test_app_client: Tuple[httpx.AsyncClient, bool],
|
||||||
|
|||||||
Reference in New Issue
Block a user