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`" !!! 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`

View File

@ -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(

View File

@ -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):

View File

@ -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(