Fix #9: Write documentation (#16)

* Start to write documentation

* Add docs favicon

* Add routes documentation

* Add doc about dependency callables

* Add information about how to make authenticated calls
This commit is contained in:
François Voron
2019-10-19 18:16:08 +02:00
committed by GitHub
parent 4153456a30
commit a5618399a1
22 changed files with 484 additions and 12 deletions

View File

@ -1,7 +1,12 @@
PIPENV_RUN := pipenv run PIPENV_RUN := pipenv run
format: isort-src:
$(PIPENV_RUN) isort -rc . $(PIPENV_RUN) isort -rc ./fastapi_users
isort-docs:
$(PIPENV_RUN) isort -rc ./docs/src -o fastapi_users
format: isort-src isort-docs
$(PIPENV_RUN) black . $(PIPENV_RUN) black .
test: test:

View File

@ -20,6 +20,9 @@ pytest-cov = "*"
pytest-mock = "*" pytest-mock = "*"
asynctest = "*" asynctest = "*"
flit = "*" flit = "*"
markdown-include = "*"
pygments = "*"
pymdown-extensions = "*"
[packages] [packages]
fastapi = "==0.42.0" fastapi = "==0.42.0"

16
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "93805b5493ec5fc7944440e833ad352af9f780705817990ef6bb96230aff601a" "sha256": "1985abdc79db7e7cbe29782026b32ae0099107ed8ac4ec22687848f14c04a512"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -379,6 +379,13 @@
], ],
"version": "==3.1.1" "version": "==3.1.1"
}, },
"markdown-include": {
"hashes": [
"sha256:72a45461b589489a088753893bc95c5fa5909936186485f4ed55caa57d10250f"
],
"index": "pypi",
"version": "==0.5.1"
},
"markupsafe": { "markupsafe": {
"hashes": [ "hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
@ -471,9 +478,10 @@
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
"sha256:a161e3b917053de87dbe469987e173e49fb454eca10ef28b48b384538cc11458" "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
], ],
"version": "==0.4.2" "version": "==0.4.3"
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
@ -529,6 +537,7 @@
"sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
"sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
], ],
"index": "pypi",
"version": "==2.4.2" "version": "==2.4.2"
}, },
"pymdown-extensions": { "pymdown-extensions": {
@ -536,6 +545,7 @@
"sha256:24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10", "sha256:24c1a0afbae101c4e2b2675ff4dd936470a90133f93398b9cbe0c855e2d2ec10",
"sha256:960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9" "sha256:960486dea995f1759dfd517aa140b3d851cd7b44d4c48d276fd2c74fc4e1bce9"
], ],
"index": "pypi",
"version": "==6.1" "version": "==6.1"
}, },
"pyparsing": { "pyparsing": {

View File

@ -28,7 +28,7 @@ This library is currently in early stage development. First working version soon
## Features ## Features
* Extensible base user model * Extensible base user model
* Ready-to-use register, login, *forgot and reset password routes ([#3](https://github.com/frankie567/fastapi-users/issues/3))* * Ready-to-use register, login, forgot and reset password routes.
* Customizable database backend * Customizable database backend
* SQLAlchemy backend included * SQLAlchemy backend included
* *MongoDB backend included ([#4](https://github.com/frankie567/fastapi-users/issues/4))* * *MongoDB backend included ([#4](https://github.com/frankie567/fastapi-users/issues/4))*

View File

@ -0,0 +1,3 @@
## Next steps
We will now configure the main **FastAPI Users** object that will expose the [API router](/configuration/router).

View File

@ -0,0 +1,3 @@
## Next steps
We will now configure an [authentication method](/configuration/authentication).

View File

@ -0,0 +1,7 @@
# Authentication
**FastAPI Users** allows you to plug in several authentication methods.
## Provided methods
* [JWT authentication](jwt.md)

View File

@ -0,0 +1,25 @@
# JWT
JSON Web Token (JWT) is an internet standard for creating access tokens based on JSON.
## Configuration
```py
from fastapi_users.authentication import JWTAuthentication
SECRET = "SECRET"
auth = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
```
As you can see, instantiation is quite simple. You just have to define a constant `SECRET` which is used to encode the token and the lifetime of token (in seconds).
## Authentication
This method expects that you provide a `Bearer` authentication with a valid JWT.
```bash
curl http://localhost:9000/protected-route -H'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI'
```
{!./configuration/_next_router.md!}

View File

@ -0,0 +1,5 @@
# MongoDB
**Coming soon**. Track the progress of this feature in [ticket #4](https://github.com/frankie567/fastapi-users/issues/4).
{!./configuration/authentication/_next_authentication.md!}

View File

@ -0,0 +1,58 @@
# SQLAlchemy
**FastAPI Users** provides the necessary tools to work with SQL databases thanks to [SQLAlchemy Core](https://docs.sqlalchemy.org/en/13/core/) and [encode/databases](https://www.encode.io/databases/) package for full async support.
## Installation
Install the database driver that corresponds to your DBMS:
```sh
pip install databases[postgresql]
```
```sh
pip install databases[mysql]
```
```sh
pip install databases[sqlite]
```
For the sake of this tutorial from now on, we'll use a simple SQLite databse.
## Setup User table
Let's create a `metadata` object and declare our User table.
```py hl_lines="4 14 15"
{!./src/sqlalchemy.py!}
```
As you can see, **FastAPI Users** provides a mixin that will include base fields for our User table. You can of course add you own fields there to fit to your needs!
## Create the tables
We'll now create an SQLAlchemy enigne and ask it to create all the defined tables.
```py hl_lines="18 19 20 21 22"
{!./src/sqlalchemy.py!}
```
!!!tip
In production, you would probably want to create the tables with Alembic, integrated with migrations, etc.
## Create the database adapter
The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. Create it like this.
```py hl_lines="24 25"
{!./src/sqlalchemy.py!}
```
Notice that we declare the `users` variable, which is the actual SQLAlchemy table behind the table class. We also use our `database` instance, which allows us to do asynchronous request to the database.
{!./configuration/authentication/_next_authentication.md!}
## What about SQLAlchemy ORM?
The primary objective was to use pure async approach as much as possible. However, we understand that ORM is convenient and useful for many developers. If this feature becomes very demanded, we will add a database adapter for SQLAlchemy ORM.

View File

@ -0,0 +1,15 @@
# Full example
Here is a full working example with JWT authentication to help get you started.
``` py tab="SQLAlchemy"
{!./src/full_sqlalchemy.py!}
```
```py tab="MongoDB"
# Coming soon
```
## What now?
You're ready to go! Be sure to check the [Usage](../usage/routes.md) section to understand how yo work with **FastAPI Users**.

View File

@ -0,0 +1,30 @@
# User model
**FastAPI Users** defines a minimal User model for authentication purposes. It is structured like this:
* `id` (`str`) Unique identifier of the user. Default to a **UUID4**.
* `email` (`str`) Email of the user. Validated by [`email-validator`](https://github.com/JoshData/python-email-validator).
* `is_active` (`bool`) Whether or not the user is active. If not, login and forgot password requests will be denied. Default to `True`.
* `is_active` (`bool`) Whether or not the user is a superuser. Useful to implement administration logic. Default to `False`.
## Use the model
The model is exposed as a Pydantic model mixin.
```py
from fastapi_users import BaseUser
class User(BaseUser):
pass
```
You can of course add you own properties there to fit to your needs!
## Next steps
Depending on your database backend, database configuration will differ a bit.
[I'm using SQLAlchemy](databases/sqlalchemy.md)
[I'm using MongoDB](databases/mongodb.md)

View File

@ -0,0 +1,56 @@
# Router
We're almost there! The last step is to configure the `FastAPIUsers` object that will wire the database adapter, the authentication class and the user model to expose the FastAPI router.
## Hooks
In order to be as unopinionated as possible, you'll have to define your logic after some actions.
### After forgot password
This hook is called after a successful forgot password request. It is called with **two arguments**: the **user** which has requested to reset their password and a ready-to-use **JWT token** that will be accepted by the reset password route.
Typically, you'll want to **send an e-mail** with the link (and the token) that allows the user to reset their password.
You can define it as an `async` or standard method.
Example:
```py
def on_after_forgot_password(user, token):
print(f'User {user.id} has forgot their password. Reset token: {token}')
```
## Configure `FastAPIUsers`
The last step is to instantiate `FastAPIUsers` object with all the elements we defined before. More precisely:
* `db`: Database adapter instance.
* `auth`: Authentication logic instance.
* `user_model`: Pydantic model of a user.
* `on_after_forgot_password`: Hook called after a forgot password request.
* `reset_password_token_secret`: Secret to encode reset password token.
* `reset_password_token_lifetime_seconds`: Lifetime of reset password token in seconds. Default to one hour.
```py
from fastapi_users import FastAPIUsers
fastapi_users = FastAPIUsers(
user_db,
auth,
User,
on_after_forgot_password,
SECRET,
)
```
And then, include the router in the FastAPI app:
```py
app = FastAPI()
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
```
## Next steps
Check out a [full example](full_example.md) that will show you the big picture.

BIN
docs/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -5,7 +5,7 @@
</p> </p>
<p align="center"> <p align="center">
<em>Ready-to-use and customizable users management for FastAPI </em> <em>Ready-to-use and customizable users management for FastAPI</em>
</p> </p>
--- ---
@ -16,14 +16,12 @@
--- ---
## Work in progress 🚧 Add quickly a registration and authentication system to your [FastAPI](https://fastapi.tiangolo.com/) project. **FastAPI Users** is designed to be as customizable and adaptable as possible.
This library is currently in early stage development. First working version soon!
## Features ## Features
* Extensible base user model * Extensible base user model
* Ready-to-use register, login, *forgot and reset password routes ([#3](https://github.com/frankie567/fastapi-users/issues/3))* * Ready-to-use register, login, forgot and reset password routes.
* Customizable database backend * Customizable database backend
* SQLAlchemy backend included * SQLAlchemy backend included
* *MongoDB backend included ([#4](https://github.com/frankie567/fastapi-users/issues/4))* * *MongoDB backend included ([#4](https://github.com/frankie567/fastapi-users/issues/4))*

17
docs/installation.md Normal file
View File

@ -0,0 +1,17 @@
# Installation
You can add **FastAPI Users** to your FastAPI project in a few easy steps. First of all, install the dependency:
```sh
pip install fastapi-users
```
...or if you're already in the future:
```sh
pipenv install fastapi-users
```
---
That's it! Now, let's have a look at our [User model](./configuration/model.md).

View File

@ -0,0 +1,55 @@
import databases
import sqlalchemy
from fastapi import FastAPI
from fastapi_users import BaseUser, FastAPIUsers
from fastapi_users.authentication import JWTAuthentication
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
DATABASE_URL = "sqlite:///./test.db"
SECRET = "SECRET"
database = databases.Database(DATABASE_URL)
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
pass
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
Base.metadata.create_all(engine)
users = UserTable.__table__
user_db = SQLAlchemyUserDatabase(database, users)
class User(BaseUser):
pass
auth = JWTAuthentication(secret=SECRET, lifetime_seconds=3600)
def on_after_forgot_password(user, token):
print(f"User {user.id} has forgot their password. Reset token: {token}")
app = FastAPI()
fastapi_users = FastAPIUsers(user_db, auth, User, on_after_forgot_password, SECRET)
app.include_router(fastapi_users.router, prefix="/users", tags=["users"])
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()

37
docs/src/sqlalchemy.py Normal file
View File

@ -0,0 +1,37 @@
import databases
import sqlalchemy
from fastapi import FastAPI
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
DATABASE_URL = "sqlite:///./test.db"
database = databases.Database(DATABASE_URL)
Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable):
pass
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
Base.metadata.create_all(engine)
users = UserTable.__table__
user_db = SQLAlchemyUserDatabase(database, users)
app = FastAPI()
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()

View File

@ -0,0 +1,36 @@
# Dependency callables
**FastAPI Users** provides dependency callables to easily inject users in your routes. They are available from your `FastAPIUsers` instance.
!!! tip
For more information about how to make an authenticated request to your API, check the documentation of your [Authentication method](../configuration/authentication/index.md).
## `get_current_user`
Get the current user (**active or not**). Will throw a `401 Unauthorized` if missing or wrong credentials.
```py
@app.get('/protected-route')
def protected_route(user: User = Depends(fastapi_users.get_current_user)):
return f'Hello, {user.email}'
```
## `get_current_active_user`
Get the current active user. Will throw a `401 Unauthorized` if missing or wrong credentials or if the user is not active.
```py
@app.get('/protected-route')
def protected_route(user: User = Depends(fastapi_users.get_current_active_user)):
return f'Hello, {user.email}'
```
## `get_current_superuser`
Get the current superuser. Will throw a `401 Unauthorized` if missing or wrong credentials or if the user is not active. Will throw a `403 Forbidden` if the user is not a superuser.
```py
@app.get('/protected-route')
def protected_route(user: User = Depends(fastapi_users.get_current_superuser)):
return f'Hello, {user.email}'
```

82
docs/usage/routes.md Normal file
View File

@ -0,0 +1,82 @@
# Routes
You'll find here the routes exposed by **FastAPI Users**. Note that you can also review them through the [interactive API docs](https://fastapi.tiangolo.com/tutorial/first-steps/#interactive-api-docs).
## `POST /register`
Register a new user.
!!! abstract "Payload"
```json
{
"email": "king.arthur@camelot.bt",
"password": "guinevere"
}
```
!!! success "`200 OK`"
```json
{
"id": "57cbb51a-ab71-4009-8802-3f54b4f2e23",
"email": "king.arthur@camelot.bt",
"is_active": true,
"is_superuser": false
}
```
!!! fail "`422 Validation Error`"
## `POST /login`
Login a user.
!!! abstract "Payload (`application/x-www-form-urlencoded`)"
```
username=king.arthur@camelot.bt&password=guinevere
```
!!! success "`200 OK`"
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI"
}
```
!!! fail "`422 Validation Error`"
!!! fail "`400 Bad Request`"
Bad credentials or the user is inactive.
## `POST /forgot-password`
Request a reset password procedure. Will generate a temporary token and call the defined `on_after_forgot_password` if the user exists.
To prevent malicious users from guessing existing users in your databse, the route will always return a `202 Accepted` response, even if the user requested does not exist.
!!! abstract "Payload"
```json
{
"email": "king.arthur@camelot.bt"
}
```
!!! success "`202 Accepted`"
## `POST /reset-password`
Reset a password. Requires the token generated by the `/forgot-password` route.
!!! abstract "Payload"
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI",
"password": "merlin"
}
```
!!! success "`200 Accepted`"
!!! fail "`422 Validation Error`"
!!! fail "`400 Bad Request`"
Bad or expired token.

View File

@ -1,6 +1,6 @@
"""Ready-to-use and customizable users management for FastAPI.""" """Ready-to-use and customizable users management for FastAPI."""
__version__ = '0.0.2' __version__ = "0.0.2"
from fastapi_users.fastapi_users import FastAPIUsers # noqa: F401 from fastapi_users.fastapi_users import FastAPIUsers # noqa: F401
from fastapi_users.models import BaseUser # noqa: F401 from fastapi_users.models import BaseUser # noqa: F401

View File

@ -8,7 +8,34 @@ theme:
accent: 'red' accent: 'red'
logo: logo:
icon: 'supervisor_account' icon: 'supervisor_account'
favicon: 'favicon.png'
repo_name: frankie567/fastapi-users repo_name: frankie567/fastapi-users
repo_url: https://github.com/frankie567/fastapi-users repo_url: https://github.com/frankie567/fastapi-users
edit_uri: "" edit_uri: ""
markdown_extensions:
- markdown_include.include:
base_path: docs
- toc:
permalink: true
- admonition
- codehilite
- pymdownx.superfences
nav:
- index.md
- installation.md
- Configuration:
- configuration/model.md
- Databases:
- configuration/databases/sqlalchemy.md
- configuration/databases/mongodb.md
- Authentication:
- configuration/authentication/index.md
- configuration/authentication/jwt.md
- configuration/router.md
- configuration/full_example.md
- Usage:
- usage/routes.md
- usage/dependency-callables.md