mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2025-08-15 19:30:47 +08:00
@ -31,6 +31,12 @@ Register a new user. Will call the `on_after_register` [event handlers](../confi
|
|||||||
!!! fail "`400 Bad Request`"
|
!!! fail "`400 Bad Request`"
|
||||||
A user already exists with this email.
|
A user already exists with this email.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "REGISTER_USER_ALREADY_EXISTS"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `POST /login`
|
### `POST /login`
|
||||||
|
|
||||||
Login a user.
|
Login a user.
|
||||||
@ -52,6 +58,12 @@ Login a user.
|
|||||||
!!! fail "`400 Bad Request`"
|
!!! fail "`400 Bad Request`"
|
||||||
Bad credentials or the user is inactive.
|
Bad credentials or the user is inactive.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "LOGIN_BAD_CREDENTIALS"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `POST /forgot-password`
|
### `POST /forgot-password`
|
||||||
|
|
||||||
Request a reset password procedure. Will generate a temporary token and call the `on_after_forgot_password` [event handlers](../configuration/router.md#event-handlers) if the user exists.
|
Request a reset password procedure. Will generate a temporary token and call the `on_after_forgot_password` [event handlers](../configuration/router.md#event-handlers) if the user exists.
|
||||||
@ -86,6 +98,12 @@ Reset a password. Requires the token generated by the `/forgot-password` route.
|
|||||||
!!! fail "`400 Bad Request`"
|
!!! fail "`400 Bad Request`"
|
||||||
Bad or expired token.
|
Bad or expired token.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "RESET_PASSWORD_BAD_TOKEN"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Authenticated
|
## Authenticated
|
||||||
|
|
||||||
### `GET /me`
|
### `GET /me`
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import typing
|
import typing
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from enum import Enum
|
from enum import Enum, auto
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||||
@ -17,9 +17,15 @@ from fastapi_users.password import get_password_hash
|
|||||||
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorCode:
|
||||||
|
REGISTER_USER_ALREADY_EXISTS = "REGISTER_USER_ALREADY_EXISTS"
|
||||||
|
LOGIN_BAD_CREDENTIALS = "LOGIN_BAD_CREDENTIALS"
|
||||||
|
RESET_PASSWORD_BAD_TOKEN = "RESET_PASSWORD_BAD_TOKEN"
|
||||||
|
|
||||||
|
|
||||||
class Event(Enum):
|
class Event(Enum):
|
||||||
ON_AFTER_REGISTER = 1
|
ON_AFTER_REGISTER = auto()
|
||||||
ON_AFTER_FORGOT_PASSWORD = 2
|
ON_AFTER_FORGOT_PASSWORD = auto()
|
||||||
|
|
||||||
|
|
||||||
class UserRouter(APIRouter):
|
class UserRouter(APIRouter):
|
||||||
@ -80,7 +86,10 @@ def get_user_router(
|
|||||||
existing_user = await user_db.get_by_email(user.email)
|
existing_user = await user_db.get_by_email(user.email)
|
||||||
|
|
||||||
if existing_user is not None:
|
if existing_user is not None:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorCode.REGISTER_USER_ALREADY_EXISTS,
|
||||||
|
)
|
||||||
|
|
||||||
hashed_password = get_password_hash(user.password)
|
hashed_password = get_password_hash(user.password)
|
||||||
db_user = models.UserDB(
|
db_user = models.UserDB(
|
||||||
@ -98,10 +107,11 @@ def get_user_router(
|
|||||||
):
|
):
|
||||||
user = await user_db.authenticate(credentials)
|
user = await user_db.authenticate(credentials)
|
||||||
|
|
||||||
if user is None:
|
if user is None or not user.is_active:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(
|
||||||
elif not user.is_active:
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
detail=ErrorCode.LOGIN_BAD_CREDENTIALS,
|
||||||
|
)
|
||||||
|
|
||||||
return await auth.get_login_response(user, response)
|
return await auth.get_login_response(user, response)
|
||||||
|
|
||||||
@ -131,16 +141,25 @@ def get_user_router(
|
|||||||
)
|
)
|
||||||
user_id = data.get("user_id")
|
user_id = data.get("user_id")
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorCode.RESET_PASSWORD_BAD_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
user = await user_db.get(user_id)
|
user = await user_db.get(user_id)
|
||||||
if user is None or not user.is_active:
|
if user is None or not user.is_active:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorCode.RESET_PASSWORD_BAD_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
user.hashed_password = get_password_hash(password)
|
user.hashed_password = get_password_hash(password)
|
||||||
await user_db.update(user)
|
await user_db.update(user)
|
||||||
except jwt.PyJWTError:
|
except jwt.PyJWTError:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=ErrorCode.RESET_PASSWORD_BAD_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/me", response_model=models.User)
|
@router.get("/me", response_model=models.User)
|
||||||
async def me(
|
async def me(
|
||||||
|
@ -47,7 +47,6 @@ async def test_get_login_response(jwt_authentication, user):
|
|||||||
class TestGetCurrentUser:
|
class TestGetCurrentUser:
|
||||||
def test_missing_token(self, test_auth_client):
|
def test_missing_token(self, test_auth_client):
|
||||||
response = test_auth_client.get("/test-current-user")
|
response = test_auth_client.get("/test-current-user")
|
||||||
print(response.json())
|
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
|
||||||
def test_invalid_token(self, test_auth_client):
|
def test_invalid_token(self, test_auth_client):
|
||||||
|
@ -8,7 +8,7 @@ from starlette import status
|
|||||||
from starlette.testclient import TestClient
|
from starlette.testclient import TestClient
|
||||||
|
|
||||||
from fastapi_users.models import BaseUser, BaseUserDB
|
from fastapi_users.models import BaseUser, BaseUserDB
|
||||||
from fastapi_users.router import Event, get_user_router
|
from fastapi_users.router import ErrorCode, Event, get_user_router
|
||||||
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
from fastapi_users.utils import JWT_ALGORITHM, generate_jwt
|
||||||
|
|
||||||
SECRET = "SECRET"
|
SECRET = "SECRET"
|
||||||
@ -79,6 +79,7 @@ class TestRegister:
|
|||||||
json = {"email": "king.arthur@camelot.bt", "password": "guinevere"}
|
json = {"email": "king.arthur@camelot.bt", "password": "guinevere"}
|
||||||
response = test_app_client.post("/register", json=json)
|
response = test_app_client.post("/register", json=json)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS
|
||||||
assert event_handler.called is False
|
assert event_handler.called is False
|
||||||
|
|
||||||
def test_valid_body(self, test_app_client: TestClient, event_handler):
|
def test_valid_body(self, test_app_client: TestClient, event_handler):
|
||||||
@ -141,11 +142,13 @@ class TestLogin:
|
|||||||
data = {"username": "lancelot@camelot.bt", "password": "guinevere"}
|
data = {"username": "lancelot@camelot.bt", "password": "guinevere"}
|
||||||
response = test_app_client.post("/login", data=data)
|
response = test_app_client.post("/login", data=data)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
|
||||||
|
|
||||||
def test_wrong_password(self, test_app_client: TestClient):
|
def test_wrong_password(self, test_app_client: TestClient):
|
||||||
data = {"username": "king.arthur@camelot.bt", "password": "percival"}
|
data = {"username": "king.arthur@camelot.bt", "password": "percival"}
|
||||||
response = test_app_client.post("/login", data=data)
|
response = test_app_client.post("/login", data=data)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
|
||||||
|
|
||||||
def test_valid_credentials(self, test_app_client: TestClient, user: BaseUserDB):
|
def test_valid_credentials(self, test_app_client: TestClient, user: BaseUserDB):
|
||||||
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
|
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
|
||||||
@ -157,6 +160,7 @@ class TestLogin:
|
|||||||
data = {"username": "percival@camelot.bt", "password": "angharad"}
|
data = {"username": "percival@camelot.bt", "password": "angharad"}
|
||||||
response = test_app_client.post("/login", data=data)
|
response = test_app_client.post("/login", data=data)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
|
||||||
|
|
||||||
|
|
||||||
class TestForgotPassword:
|
class TestForgotPassword:
|
||||||
@ -213,8 +217,8 @@ class TestResetPassword:
|
|||||||
def test_invalid_token(self, test_app_client: TestClient):
|
def test_invalid_token(self, test_app_client: TestClient):
|
||||||
json = {"token": "foo", "password": "guinevere"}
|
json = {"token": "foo", "password": "guinevere"}
|
||||||
response = test_app_client.post("/reset-password", json=json)
|
response = test_app_client.post("/reset-password", json=json)
|
||||||
print(response.json(), response.status_code)
|
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
|
||||||
|
|
||||||
def test_valid_token_missing_user_id_payload(
|
def test_valid_token_missing_user_id_payload(
|
||||||
self, mocker, mock_user_db, test_app_client: TestClient, forgot_password_token
|
self, mocker, mock_user_db, test_app_client: TestClient, forgot_password_token
|
||||||
@ -224,6 +228,7 @@ class TestResetPassword:
|
|||||||
json = {"token": forgot_password_token(), "password": "holygrail"}
|
json = {"token": forgot_password_token(), "password": "holygrail"}
|
||||||
response = test_app_client.post("/reset-password", json=json)
|
response = test_app_client.post("/reset-password", json=json)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
|
||||||
assert mock_user_db.update.called is False
|
assert mock_user_db.update.called is False
|
||||||
|
|
||||||
def test_inactive_user(
|
def test_inactive_user(
|
||||||
@ -242,6 +247,7 @@ class TestResetPassword:
|
|||||||
}
|
}
|
||||||
response = test_app_client.post("/reset-password", json=json)
|
response = test_app_client.post("/reset-password", json=json)
|
||||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
|
||||||
assert mock_user_db.update.called is False
|
assert mock_user_db.update.called is False
|
||||||
|
|
||||||
def test_existing_user(
|
def test_existing_user(
|
||||||
|
Reference in New Issue
Block a user