From c198da58bc241caef32c40cdbfb78ab149448c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Voron?= Date: Sat, 18 Sep 2021 10:18:44 +0200 Subject: [PATCH] Complete OAuth2 documentation --- docs/configuration/databases/ormar.md | 6 +- docs/configuration/oauth.md | 94 ++++---------------- docs/configuration/overview.md | 6 +- docs/configuration/user-manager.md | 2 +- docs/src/db_ormar.py | 1 + docs/src/db_sqlalchemy_oauth.py | 35 ++++++++ docs/src/db_tortoise_oauth_adapter.py | 9 ++ docs/src/db_tortoise_oauth_model.py | 30 +++++++ docs/src/oauth_full_mongodb.py | 89 ------------------- docs/src/oauth_full_sqlalchemy.py | 119 -------------------------- docs/src/oauth_full_tortoise.py | 106 ----------------------- 11 files changed, 99 insertions(+), 398 deletions(-) create mode 100644 docs/src/db_sqlalchemy_oauth.py create mode 100644 docs/src/db_tortoise_oauth_adapter.py create mode 100644 docs/src/db_tortoise_oauth_model.py delete mode 100644 docs/src/oauth_full_mongodb.py delete mode 100644 docs/src/oauth_full_sqlalchemy.py delete mode 100644 docs/src/oauth_full_tortoise.py diff --git a/docs/configuration/databases/ormar.md b/docs/configuration/databases/ormar.md index 533a6026..47f55213 100644 --- a/docs/configuration/databases/ormar.md +++ b/docs/configuration/databases/ormar.md @@ -24,7 +24,7 @@ For the sake of this tutorial from now on, we'll use a simple SQLite databse. Let's declare our User ORM model. -```py hl_lines="11-15" +```py hl_lines="12-16" {!./src/db_ormar.py!} ``` @@ -37,11 +37,11 @@ there to fit to your needs! The database adapter of **FastAPI Users** makes the link between your database configuration and the users logic. It should be generated by a FastAPI dependency. -```py hl_lines="22-23" +```py hl_lines="23-24" {!./src/db_ormar.py!} ``` -Notice that we pass a reference to your [`UserDB` model](../models.md). +Notice that we pass a reference to your [`UserDB` model](../model.md). !!! warning In production, it's strongly recommended to setup a migration system to diff --git a/docs/configuration/oauth.md b/docs/configuration/oauth.md index 5a2f64ce..7c5132fc 100644 --- a/docs/configuration/oauth.md +++ b/docs/configuration/oauth.md @@ -70,26 +70,14 @@ Notice that we inherit from the `BaseOAuthAccountMixin`, which adds a `List` of You'll need to define the table for storing the OAuth account model. We provide a base one for this: -```py -from fastapi_users.db import SQLAlchemyBaseOAuthAccountTable - -class OAuthAccount(SQLAlchemyBaseOAuthAccountTable, Base): - pass +```py hl_lines="21 22" +{!./src/db_sqlalchemy_oauth.py!} ``` -Similarly, define the table for storing the User model: +When instantiating the database adapter, you should pass this table in argument:: -```py -from fastapi_users.db import SQLAlchemyBaseUserTable - -class UserTable(Base, SQLAlchemyBaseUserTable): - pass -``` - -Then, you should declare them on the database adapter: - -```py -user_db = SQLAlchemyUserDatabase(UserDB, database, UserTable.__table__, OAuthAccount.__table__) +```py hl_lines="31 34 35" +{!./src/db_sqlalchemy_oauth.py!} ``` #### MongoDB @@ -100,12 +88,8 @@ Nothing to do, the [basic configuration](./databases/mongodb.md) is enough. You'll need to define the Tortoise model for storing the OAuth account model. We provide a base one for this: -```py -from fastapi_users.db.tortoise import TortoiseBaseOAuthAccountModel - - -class OAuthAccount(TortoiseBaseOAuthAccountModel): - user = fields.ForeignKeyField("models.User", related_name="oauth_accounts") +```py hl_lines="29 30" +{!./src/db_tortoise_oauth_model.py!} ``` !!! warning @@ -113,8 +97,8 @@ class OAuthAccount(TortoiseBaseOAuthAccountModel): Then, you should declare it on the database adapter: -```py -user_db = TortoiseUserDatabase(UserDB, User, OAuthAccount) +```py hl_lines="8 9" +{!./src/db_tortoise_oauth_adapter.py!} ``` ### Generate a router @@ -122,51 +106,11 @@ user_db = TortoiseUserDatabase(UserDB, User, OAuthAccount) Once you have a `FastAPIUsers` instance, you can make it generate a single OAuth router for the given client. ```py -from fastapi import FastAPI -from fastapi_users import FastAPIUsers -from httpx_oauth.clients.google import GoogleOAuth2 - -google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") - -app = FastAPI() -fastapi_users = FastAPIUsers( - user_db, auth_backends, User, UserCreate, UserUpdate, UserDB +app.include_router( + fastapi_users.get_oauth_router(google_oauth_client, "SECRET"), + prefix="/auth/google", + tags=["auth"], ) - -google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET) - -app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"]) -``` - -### After register - -You can provide a custom function to be called after a successful registration. It is called with **two argument**: the **user** that has just registered, and the original **`Request` object**. - -Typically, you'll want to **send a welcome e-mail** or add it to your marketing analytics pipeline. - -You can define it as an `async` or standard method. - -Example: - -```py -from fastapi import FastAPI -from fastapi_users import FastAPIUsers -from httpx_oauth.clients.google import GoogleOAuth2 - - -def on_after_register(user: UserDB, request: Request): - print(f"User {user.id} has registered.") - -google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") - -app = FastAPI() -fastapi_users = FastAPIUsers( - user_db, auth_backends, User, UserCreate, UserUpdate, UserDB -) - -google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, SECRET, after_register=on_after_register) - -app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"]) ``` ### Full example @@ -177,18 +121,12 @@ app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"]) #### SQLAlchemy -``` py -{!./src/oauth_full_sqlalchemy.py!} -``` + #### MongoDB -```py -{!./src/oauth_full_mongodb.py!} -``` + #### Tortoise ORM -```py -{!./src/oauth_full_tortoise.py!} -``` + diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md index 3c174347..c3af46ab 100644 --- a/docs/configuration/overview.md +++ b/docs/configuration/overview.md @@ -4,9 +4,10 @@ The schema below shows you how the library is structured and how each part fit t ```mermaid -flowchart TB +flowchart LR FASTAPI_USERS{FastAPIUsers} USER_MANAGER{UserManager} + DATABASE_DEPENDENCY[[get_user_db]] USER_MANAGER_DEPENDENCY[[get_user_manager]] CURRENT_USER[[current_user]] subgraph MODELS[Models] @@ -37,7 +38,8 @@ flowchart TB COOKIE[CookieAuthentication] JWT[JWTAuthentication] end - DATABASE --> USER_MANAGER + DATABASE --> DATABASE_DEPENDENCY + DATABASE_DEPENDENCY --> USER_MANAGER MODELS --> USER_MANAGER MODELS --> FASTAPI_USERS diff --git a/docs/configuration/user-manager.md b/docs/configuration/user-manager.md index cd06ea35..cc87fa62 100644 --- a/docs/configuration/user-manager.md +++ b/docs/configuration/user-manager.md @@ -8,7 +8,7 @@ It's designed to be easily extensible and customizable so that you can integrate You should define your own version of the `UserManager` class to set various parameters. -```py hl_lines="13-29" +```py hl_lines="12-28" {!./src/user_manager.py!} ``` diff --git a/docs/src/db_ormar.py b/docs/src/db_ormar.py index bb699938..d9e577b2 100644 --- a/docs/src/db_ormar.py +++ b/docs/src/db_ormar.py @@ -2,6 +2,7 @@ import databases import sqlalchemy from fastapi_users.db import OrmarBaseUserModel, OrmarUserDatabase +from .models import UserDB DATABASE_URL = "sqlite:///test.db" metadata = sqlalchemy.MetaData() diff --git a/docs/src/db_sqlalchemy_oauth.py b/docs/src/db_sqlalchemy_oauth.py new file mode 100644 index 00000000..6822d4ee --- /dev/null +++ b/docs/src/db_sqlalchemy_oauth.py @@ -0,0 +1,35 @@ +import databases +import sqlalchemy +from fastapi_users.db import ( + SQLAlchemyBaseOAuthAccountTable, + SQLAlchemyBaseUserTable, + SQLAlchemyUserDatabase, +) +from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base + +from .models import UserDB + +DATABASE_URL = "sqlite:///./test.db" +database = databases.Database(DATABASE_URL) +Base: DeclarativeMeta = declarative_base() + + +class UserTable(Base, SQLAlchemyBaseUserTable): + pass + + +class OAuthAccount(SQLAlchemyBaseOAuthAccountTable, Base): + pass + + +engine = sqlalchemy.create_engine( + DATABASE_URL, connect_args={"check_same_thread": False} +) +Base.metadata.create_all(engine) + +users = UserTable.__table__ +oauth_accounts = OAuthAccount.__table__ + + +def get_user_db(): + yield SQLAlchemyUserDatabase(UserDB, database, users, oauth_accounts) diff --git a/docs/src/db_tortoise_oauth_adapter.py b/docs/src/db_tortoise_oauth_adapter.py new file mode 100644 index 00000000..c864687d --- /dev/null +++ b/docs/src/db_tortoise_oauth_adapter.py @@ -0,0 +1,9 @@ +from fastapi_users.db import TortoiseUserDatabase + +from .models import OAuthAccount, UserDB, UserModel + +DATABASE_URL = "sqlite://./test.db" + + +def get_user_db(): + yield TortoiseUserDatabase(UserDB, UserModel, OAuthAccount) diff --git a/docs/src/db_tortoise_oauth_model.py b/docs/src/db_tortoise_oauth_model.py new file mode 100644 index 00000000..9d470192 --- /dev/null +++ b/docs/src/db_tortoise_oauth_model.py @@ -0,0 +1,30 @@ +from fastapi_users import models +from fastapi_users.db import TortoiseBaseOAuthAccountModel, TortoiseBaseUserModel +from tortoise import fields +from tortoise.contrib.pydantic import PydanticModel + + +class User(models.BaseUser, models.BaseOAuthAccountMixin): + pass + + +class UserCreate(models.BaseUserCreate): + pass + + +class UserUpdate(models.BaseUserUpdate): + pass + + +class UserModel(TortoiseBaseUserModel): + pass + + +class UserDB(User, models.BaseUserDB, PydanticModel): + class Config: + orm_mode = True + orig_model = UserModel + + +class OAuthAccount(TortoiseBaseOAuthAccountModel): + user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts") diff --git a/docs/src/oauth_full_mongodb.py b/docs/src/oauth_full_mongodb.py deleted file mode 100644 index b0fd9037..00000000 --- a/docs/src/oauth_full_mongodb.py +++ /dev/null @@ -1,89 +0,0 @@ -import motor.motor_asyncio -from fastapi import FastAPI, Request -from fastapi_users import FastAPIUsers, models -from fastapi_users.authentication import JWTAuthentication -from fastapi_users.db import MongoDBUserDatabase -from httpx_oauth.clients.google import GoogleOAuth2 - -DATABASE_URL = "mongodb://localhost:27017" -SECRET = "SECRET" - - -google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") - - -class User(models.BaseUser, models.BaseOAuthAccountMixin): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(User, models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass - - -client = motor.motor_asyncio.AsyncIOMotorClient( - DATABASE_URL, uuidRepresentation="standard" -) -db = client["database_name"] -collection = db["users"] -user_db = MongoDBUserDatabase(UserDB, collection) - - -def on_after_register(user: UserDB, request: Request): - print(f"User {user.id} has registered.") - - -def on_after_forgot_password(user: UserDB, token: str, request: Request): - print(f"User {user.id} has forgot their password. Reset token: {token}") - - -def after_verification_request(user: UserDB, token: str, request: Request): - print(f"Verification requested for user {user.id}. Verification token: {token}") - - -jwt_authentication = JWTAuthentication( - secret=SECRET, lifetime_seconds=3600, tokenUrl="auth/jwt/login" -) - -app = FastAPI() -fastapi_users = FastAPIUsers( - user_db, - [jwt_authentication], - User, - UserCreate, - UserUpdate, - UserDB, -) -app.include_router( - fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"] -) -app.include_router( - fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"] -) -app.include_router( - fastapi_users.get_reset_password_router( - SECRET, after_forgot_password=on_after_forgot_password - ), - prefix="/auth", - tags=["auth"], -) -app.include_router( - fastapi_users.get_verify_router( - SECRET, after_verification_request=after_verification_request - ), - prefix="/auth", - tags=["auth"], -) -app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) - -google_oauth_router = fastapi_users.get_oauth_router( - google_oauth_client, SECRET, after_register=on_after_register -) -app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"]) diff --git a/docs/src/oauth_full_sqlalchemy.py b/docs/src/oauth_full_sqlalchemy.py deleted file mode 100644 index 83510898..00000000 --- a/docs/src/oauth_full_sqlalchemy.py +++ /dev/null @@ -1,119 +0,0 @@ -import databases -import sqlalchemy -from fastapi import FastAPI, Request -from fastapi_users import FastAPIUsers, models -from fastapi_users.authentication import JWTAuthentication -from fastapi_users.db import ( - SQLAlchemyBaseOAuthAccountTable, - SQLAlchemyBaseUserTable, - SQLAlchemyUserDatabase, -) -from httpx_oauth.clients.google import GoogleOAuth2 -from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base - -DATABASE_URL = "sqlite:///./test.db" -SECRET = "SECRET" - - -google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") - - -class User(models.BaseUser, models.BaseOAuthAccountMixin): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(User, models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB): - pass - - -database = databases.Database(DATABASE_URL) -Base: DeclarativeMeta = declarative_base() - - -class UserTable(Base, SQLAlchemyBaseUserTable): - pass - - -class OAuthAccount(SQLAlchemyBaseOAuthAccountTable, Base): - pass - - -engine = sqlalchemy.create_engine( - DATABASE_URL, connect_args={"check_same_thread": False} -) -Base.metadata.create_all(engine) - -users = UserTable.__table__ -oauth_accounts = OAuthAccount.__table__ -user_db = SQLAlchemyUserDatabase(UserDB, database, users, oauth_accounts) - - -def on_after_register(user: UserDB, request: Request): - print(f"User {user.id} has registered.") - - -def on_after_forgot_password(user: UserDB, token: str, request: Request): - print(f"User {user.id} has forgot their password. Reset token: {token}") - - -def after_verification_request(user: UserDB, token: str, request: Request): - print(f"Verification requested for user {user.id}. Verification token: {token}") - - -jwt_authentication = JWTAuthentication( - secret=SECRET, lifetime_seconds=3600, tokenUrl="auth/jwt/login" -) - -app = FastAPI() -fastapi_users = FastAPIUsers( - user_db, - [jwt_authentication], - User, - UserCreate, - UserUpdate, - UserDB, -) -app.include_router( - fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"] -) -app.include_router( - fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"] -) -app.include_router( - fastapi_users.get_reset_password_router( - SECRET, after_forgot_password=on_after_forgot_password - ), - prefix="/auth", - tags=["auth"], -) -app.include_router( - fastapi_users.get_verify_router( - SECRET, after_verification_request=after_verification_request - ), - prefix="/auth", - tags=["auth"], -) -app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) - -google_oauth_router = fastapi_users.get_oauth_router( - google_oauth_client, SECRET, after_register=on_after_register -) -app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"]) - - -@app.on_event("startup") -async def startup(): - await database.connect() - - -@app.on_event("shutdown") -async def shutdown(): - await database.disconnect() diff --git a/docs/src/oauth_full_tortoise.py b/docs/src/oauth_full_tortoise.py deleted file mode 100644 index 651b9b67..00000000 --- a/docs/src/oauth_full_tortoise.py +++ /dev/null @@ -1,106 +0,0 @@ -from fastapi import FastAPI, Request -from fastapi_users import FastAPIUsers, models -from fastapi_users.authentication import JWTAuthentication -from fastapi_users.db import ( - TortoiseBaseOAuthAccountModel, - TortoiseBaseUserModel, - TortoiseUserDatabase, -) -from httpx_oauth.clients.google import GoogleOAuth2 -from tortoise import fields -from tortoise.contrib.fastapi import register_tortoise -from tortoise.contrib.pydantic import PydanticModel - -DATABASE_URL = "sqlite://./test.db" -SECRET = "SECRET" - - -google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") - - -class UserModel(TortoiseBaseUserModel): - pass - - -class OAuthAccountModel(TortoiseBaseOAuthAccountModel): - user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts") - - -class User(models.BaseUser, models.BaseOAuthAccountMixin): - pass - - -class UserCreate(models.BaseUserCreate): - pass - - -class UserUpdate(User, models.BaseUserUpdate): - pass - - -class UserDB(User, models.BaseUserDB, PydanticModel): - class Config: - orm_mode = True - orig_model = UserModel - - -user_db = TortoiseUserDatabase(UserDB, UserModel, OAuthAccountModel) -app = FastAPI() -register_tortoise( - app, - db_url=DATABASE_URL, - modules={"models": ["oauth_full_tortoise"]}, - generate_schemas=True, -) - - -def on_after_register(user: UserDB, request: Request): - print(f"User {user.id} has registered.") - - -def on_after_forgot_password(user: UserDB, token: str, request: Request): - print(f"User {user.id} has forgot their password. Reset token: {token}") - - -def after_verification_request(user: UserDB, token: str, request: Request): - print(f"Verification requested for user {user.id}. Verification token: {token}") - - -jwt_authentication = JWTAuthentication( - secret=SECRET, lifetime_seconds=3600, tokenUrl="auth/jwt/login" -) - -fastapi_users = FastAPIUsers( - user_db, - [jwt_authentication], - User, - UserCreate, - UserUpdate, - UserDB, -) -app.include_router( - fastapi_users.get_auth_router(jwt_authentication), prefix="/auth/jwt", tags=["auth"] -) -app.include_router( - fastapi_users.get_register_router(on_after_register), prefix="/auth", tags=["auth"] -) -app.include_router( - fastapi_users.get_reset_password_router( - SECRET, after_forgot_password=on_after_forgot_password - ), - prefix="/auth", - tags=["auth"], -) -app.include_router( - fastapi_users.get_verify_router( - SECRET, after_verification_request=after_verification_request - ), - prefix="/auth", - tags=["auth"], -) -app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) - -google_oauth_router = fastapi_users.get_oauth_router( - google_oauth_client, SECRET, after_register=on_after_register -) -app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])