Native model and generic ID (#971)

* Use a generic Protocol model for User instead of Pydantic

* Remove UserDB Pydantic schema

* Harmonize schema variable naming to avoid confusions

* Revamp OAuth account model management

* Revamp AccessToken DB strategy to adopt generic model approach

* Make ID a generic instead of forcing UUIDs

* Improve generic typing

* Improve Strategy typing

* Tweak base DB typing

* Don't set Pydantic schemas on FastAPIUsers class: pass it directly on router creation

* Add IntegerIdMixin and export related classes

* Start to revamp doc for V10

* Revamp OAuth documentation

* Fix code highlights

* Write the 9.x.x ➡️ 10.x.x migration doc

* Fix pyproject.toml
This commit is contained in:
François Voron
2022-05-05 14:51:19 +02:00
committed by GitHub
parent b7734fc8b0
commit 72aa68c462
124 changed files with 2144 additions and 2114 deletions

View File

@ -7,15 +7,11 @@ FastAPI Users provides an optional OAuth2 authentication support. It relies on [
You should install the library with the optional dependencies for OAuth:
```sh
pip install 'fastapi-users[sqlalchemy2,oauth]'
pip install 'fastapi-users[sqlalchemy,oauth]'
```
```sh
pip install 'fastapi-users[mongodb,oauth]'
```
```sh
pip install 'fastapi-users[tortoise-orm,oauth]'
pip install 'fastapi-users[beanie,oauth]'
```
## Configuration
@ -30,78 +26,44 @@ from httpx_oauth.clients.google import GoogleOAuth2
google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET")
```
### Setup the models
The user models differ a bit from the standard one as we have to have a way to store the OAuth information (access tokens, account ids...).
```py
from fastapi_users import models
class User(models.BaseUser, models.BaseOAuthAccountMixin):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(models.BaseUserUpdate):
pass
class UserDB(User, models.BaseUserDB):
pass
```
Notice that we inherit from the `BaseOAuthAccountMixin`, which adds a `List` of `BaseOAuthAccount` objects. This object is structured like this:
* `id` (`UUID4`) Unique identifier of the OAuth account information. Defaults to a **UUID4**.
* `oauth_name` (`str`) Name of the OAuth service. It corresponds to the `name` property of the OAuth client.
* `access_token` (`str`) Access token.
* `expires_at` (`Optional[int]`) - Timestamp at which the access token is expired.
* `refresh_token` (`Optional[str]`) On services that support it, a token to get a fresh access token.
* `account_id` (`str`) - Identifier of the OAuth account on the corresponding service.
* `account_email` (`str`) - Email address of the OAuth account on the corresponding service.
### Setup the database adapter
#### SQLAlchemy
You'll need to define the SQLAlchemy model for storing OAuth accounts. We provide a base one for this:
```py hl_lines="19-24"
```py hl_lines="5 17-18 22 39-40"
--8<-- "docs/src/db_sqlalchemy_oauth.py"
```
Notice that we also manually added a `relationship` on the `UserTable` so that SQLAlchemy can properly retrieve the OAuth accounts of the user.
When instantiating the database adapter, you should pass this SQLAlchemy model:
Besides, when instantiating the database adapter, we need pass this SQLAlchemy model as third argument.
```py hl_lines="41-42"
--8<-- "docs/src/db_sqlalchemy_oauth.py"
!!! tip "Primary key is defined as UUID"
By default, we use UUID as a primary key ID for your user. If you want to use another type, like an auto-incremented integer, you can use `SQLAlchemyBaseOAuthAccountTable` as base class and define your own `id` and `user_id` column.
```py
class OAuthAccount(SQLAlchemyBaseOAuthAccountTable[int], Base):
id = Column(Integer, primary_key=True)
@declared_attr
def user_id(cls):
return Column(Integer, ForeignKey("user.id", ondelete="cascade"), nullable=False)
```
Notice that `SQLAlchemyBaseOAuthAccountTable` expects a generic type to define the actual type of ID you use.
#### Beanie
The advantage of MongoDB is that you can easily embed sub-objects in a single document. That's why the configuration for Beanie is quite simple. All we need to do is to define another class to structure an OAuth account object.
```py hl_lines="5 15-16 20"
--8<-- "docs/src/db_beanie_oauth.py"
```
#### MongoDB
Nothing to do, the [basic configuration](./databases/mongodb.md) is enough.
#### Tortoise ORM
You'll need to define the Tortoise model for storing the OAuth account model. We provide a base one for this:
```py hl_lines="29 30"
--8<-- "docs/src/db_tortoise_oauth_model.py"
```
!!! warning
Note that you should define the foreign key yourself, so that you can point it the user model in your namespace.
Then, you should declare it on the database adapter:
```py hl_lines="8 9"
--8<-- "docs/src/db_tortoise_oauth_adapter.py"
```
It's worth to note that `OAuthAccount` is **not a Beanie document** but a Pydantic model that we'll embed inside the `User` document, through the `oauth_accounts` array.
### Generate a router
@ -109,9 +71,9 @@ Once you have a `FastAPIUsers` instance, you can make it generate a single OAuth
```py
app.include_router(
fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"),
prefix="/auth/google",
tags=["auth"],
fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"),
prefix="/auth/google",
tags=["auth"],
)
```
@ -149,10 +111,10 @@ app.include_router(
--8<-- "examples/sqlalchemy-oauth/app/db.py"
```
=== "app/models.py"
=== "app/schemas.py"
```py
--8<-- "examples/sqlalchemy-oauth/app/models.py"
--8<-- "examples/sqlalchemy-oauth/app/schemas.py"
```
=== "app/users.py"
@ -161,82 +123,42 @@ app.include_router(
--8<-- "examples/sqlalchemy-oauth/app/users.py"
```
#### MongoDB
#### Beanie
[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/mongodb-oauth)
[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/beanie-oauth)
=== "requirements.txt"
```
--8<-- "examples/mongodb-oauth/requirements.txt"
--8<-- "examples/beanie-oauth/requirements.txt"
```
=== "main.py"
```py
--8<-- "examples/mongodb-oauth/main.py"
--8<-- "examples/beanie-oauth/main.py"
```
=== "app/app.py"
```py
--8<-- "examples/mongodb-oauth/app/app.py"
--8<-- "examples/beanie-oauth/app/app.py"
```
=== "app/db.py"
```py
--8<-- "examples/mongodb-oauth/app/db.py"
--8<-- "examples/beanie-oauth/app/db.py"
```
=== "app/models.py"
=== "app/schemas.py"
```py
--8<-- "examples/mongodb-oauth/app/models.py"
--8<-- "examples/beanie-oauth/app/schemas.py"
```
=== "app/users.py"
```py
--8<-- "examples/mongodb-oauth/app/users.py"
```
#### Tortoise ORM
[Open :material-open-in-new:](https://github.com/fastapi-users/fastapi-users/tree/master/examples/tortoise-oauth)
=== "requirements.txt"
```
--8<-- "examples/tortoise-oauth/requirements.txt"
```
=== "main.py"
```py
--8<-- "examples/tortoise-oauth/main.py"
```
=== "app/app.py"
```py
--8<-- "examples/tortoise-oauth/app/app.py"
```
=== "app/db.py"
```py
--8<-- "examples/tortoise-oauth/app/db.py"
```
=== "app/models.py"
```py
--8<-- "examples/tortoise-oauth/app/models.py"
```
=== "app/users.py"
```py
--8<-- "examples/tortoise-oauth/app/users.py"
--8<-- "examples/beanie-oauth/app/users.py"
```