Add error codes on routes (#34)

Fix #33
This commit is contained in:
François Voron
2019-10-31 10:10:53 +01:00
committed by GitHub
parent b512197094
commit 658161518a
4 changed files with 56 additions and 14 deletions

View File

@ -31,6 +31,12 @@ Register a new user. Will call the `on_after_register` [event handlers](../confi
!!! fail "`400 Bad Request`"
A user already exists with this email.
```json
{
"detail": "REGISTER_USER_ALREADY_EXISTS"
}
```
### `POST /login`
Login a user.
@ -52,6 +58,12 @@ Login a user.
!!! fail "`400 Bad Request`"
Bad credentials or the user is inactive.
```json
{
"detail": "LOGIN_BAD_CREDENTIALS"
}
```
### `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.
@ -86,6 +98,12 @@ Reset a password. Requires the token generated by the `/forgot-password` route.
!!! fail "`400 Bad Request`"
Bad or expired token.
```json
{
"detail": "RESET_PASSWORD_BAD_TOKEN"
}
```
## Authenticated
### `GET /me`

View File

@ -1,7 +1,7 @@
import asyncio
import typing
from collections import defaultdict
from enum import Enum
from enum import Enum, auto
import jwt
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
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):
ON_AFTER_REGISTER = 1
ON_AFTER_FORGOT_PASSWORD = 2
ON_AFTER_REGISTER = auto()
ON_AFTER_FORGOT_PASSWORD = auto()
class UserRouter(APIRouter):
@ -80,7 +86,10 @@ def get_user_router(
existing_user = await user_db.get_by_email(user.email)
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)
db_user = models.UserDB(
@ -98,10 +107,11 @@ def get_user_router(
):
user = await user_db.authenticate(credentials)
if user is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
elif not user.is_active:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
if user is None or not user.is_active:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ErrorCode.LOGIN_BAD_CREDENTIALS,
)
return await auth.get_login_response(user, response)
@ -131,16 +141,25 @@ def get_user_router(
)
user_id = data.get("user_id")
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)
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)
await user_db.update(user)
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)
async def me(

View File

@ -47,7 +47,6 @@ async def test_get_login_response(jwt_authentication, user):
class TestGetCurrentUser:
def test_missing_token(self, test_auth_client):
response = test_auth_client.get("/test-current-user")
print(response.json())
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_invalid_token(self, test_auth_client):

View File

@ -8,7 +8,7 @@ from starlette import status
from starlette.testclient import TestClient
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
SECRET = "SECRET"
@ -79,6 +79,7 @@ class TestRegister:
json = {"email": "king.arthur@camelot.bt", "password": "guinevere"}
response = test_app_client.post("/register", json=json)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == ErrorCode.REGISTER_USER_ALREADY_EXISTS
assert event_handler.called is False
def test_valid_body(self, test_app_client: TestClient, event_handler):
@ -141,11 +142,13 @@ class TestLogin:
data = {"username": "lancelot@camelot.bt", "password": "guinevere"}
response = test_app_client.post("/login", data=data)
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):
data = {"username": "king.arthur@camelot.bt", "password": "percival"}
response = test_app_client.post("/login", data=data)
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):
data = {"username": "king.arthur@camelot.bt", "password": "guinevere"}
@ -157,6 +160,7 @@ class TestLogin:
data = {"username": "percival@camelot.bt", "password": "angharad"}
response = test_app_client.post("/login", data=data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == ErrorCode.LOGIN_BAD_CREDENTIALS
class TestForgotPassword:
@ -213,8 +217,8 @@ class TestResetPassword:
def test_invalid_token(self, test_app_client: TestClient):
json = {"token": "foo", "password": "guinevere"}
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.json()["detail"] == ErrorCode.RESET_PASSWORD_BAD_TOKEN
def test_valid_token_missing_user_id_payload(
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"}
response = test_app_client.post("/reset-password", json=json)
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
def test_inactive_user(
@ -242,6 +247,7 @@ class TestResetPassword:
}
response = test_app_client.post("/reset-password", json=json)
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
def test_existing_user(