Revamp OAuth documentation

This commit is contained in:
François Voron
2022-05-05 10:54:29 +02:00
parent 617246a438
commit 43c73cdbcb
35 changed files with 202 additions and 549 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: You should install the library with the optional dependencies for OAuth:
```sh ```sh
pip install 'fastapi-users[sqlalchemy2,oauth]' pip install 'fastapi-users[sqlalchemy,oauth]'
``` ```
```sh ```sh
pip install 'fastapi-users[mongodb,oauth]' pip install 'fastapi-users[beanie,oauth]'
```
```sh
pip install 'fastapi-users[tortoise-orm,oauth]'
``` ```
## Configuration ## Configuration
@ -30,78 +26,44 @@ from httpx_oauth.clients.google import GoogleOAuth2
google_oauth_client = GoogleOAuth2("CLIENT_ID", "CLIENT_SECRET") 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 ### Setup the database adapter
#### SQLAlchemy #### SQLAlchemy
You'll need to define the SQLAlchemy model for storing OAuth accounts. We provide a base one for this: 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" --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. 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" !!! tip "Primary key is defined as UUID"
--8<-- "docs/src/db_sqlalchemy_oauth.py" 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 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.
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"
```
### Generate a router ### Generate a router
@ -149,10 +111,10 @@ app.include_router(
--8<-- "examples/sqlalchemy-oauth/app/db.py" --8<-- "examples/sqlalchemy-oauth/app/db.py"
``` ```
=== "app/models.py" === "app/schemas.py"
```py ```py
--8<-- "examples/sqlalchemy-oauth/app/models.py" --8<-- "examples/sqlalchemy-oauth/app/schemas.py"
``` ```
=== "app/users.py" === "app/users.py"
@ -161,82 +123,42 @@ app.include_router(
--8<-- "examples/sqlalchemy-oauth/app/users.py" --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" === "requirements.txt"
``` ```
--8<-- "examples/mongodb-oauth/requirements.txt" --8<-- "examples/beanie-oauth/requirements.txt"
``` ```
=== "main.py" === "main.py"
```py ```py
--8<-- "examples/mongodb-oauth/main.py" --8<-- "examples/beanie-oauth/main.py"
``` ```
=== "app/app.py" === "app/app.py"
```py ```py
--8<-- "examples/mongodb-oauth/app/app.py" --8<-- "examples/beanie-oauth/app/app.py"
``` ```
=== "app/db.py" === "app/db.py"
```py ```py
--8<-- "examples/mongodb-oauth/app/db.py" --8<-- "examples/beanie-oauth/app/db.py"
``` ```
=== "app/models.py" === "app/schemas.py"
```py ```py
--8<-- "examples/mongodb-oauth/app/models.py" --8<-- "examples/beanie-oauth/app/schemas.py"
``` ```
=== "app/users.py" === "app/users.py"
```py ```py
--8<-- "examples/mongodb-oauth/app/users.py" --8<-- "examples/beanie-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"
``` ```

View File

@ -1,7 +1,7 @@
import contextlib import contextlib
from app.db import get_async_session, get_user_db from app.db import get_async_session, get_user_db
from app.models import UserCreate from app.schemas import UserCreate
from app.users import get_user_manager from app.users import get_user_manager
from fastapi_users.manager import UserAlreadyExists from fastapi_users.manager import UserAlreadyExists

View File

@ -0,0 +1,24 @@
from typing import List
import motor.motor_asyncio
from beanie import PydanticObjectId
from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
from pydantic import Field
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
class OAuthAccount(BaseOAuthAccount):
pass
class User(BeanieBaseUser[PydanticObjectId]):
oauth_accounts: List[OAuthAccount] = Field(default_factory=list)
async def get_user_db():
yield BeanieUserDatabase(User)

View File

@ -1,29 +1,27 @@
from typing import AsyncGenerator from typing import AsyncGenerator, List
from fastapi import Depends from fastapi import Depends
from fastapi_users.db import ( from fastapi_users.db import (
SQLAlchemyBaseOAuthAccountTable, SQLAlchemyBaseOAuthAccountTableUUID,
SQLAlchemyBaseUserTable, SQLAlchemyBaseUserTableUUID,
SQLAlchemyUserDatabase, SQLAlchemyUserDatabase,
) )
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.orm import relationship, sessionmaker
from .db import UserDB
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base() Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable): class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
oauth_accounts = relationship("OAuthAccountTable")
class OAuthAccountTable(SQLAlchemyBaseOAuthAccountTable, Base):
pass pass
class User(SQLAlchemyBaseUserTableUUID, Base):
oauth_accounts: List[OAuthAccount] = relationship("OAuthAccount", lazy="joined")
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
@ -39,4 +37,4 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_user_db(session: AsyncSession = Depends(get_async_session)): async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(UserDB, session, UserTable, OAuthAccountTable) yield SQLAlchemyUserDatabase(session, User, OAuthAccount)

View File

@ -1,8 +1,8 @@
from beanie import init_beanie
from fastapi import Depends, FastAPI from fastapi import Depends, FastAPI
from tortoise.contrib.fastapi import register_tortoise
from app.db import DATABASE_URL from app.db import User, db
from app.models import UserDB from app.schemas import UserCreate, UserRead, UserUpdate
from app.users import ( from app.users import (
auth_backend, auth_backend,
current_active_user, current_active_user,
@ -15,18 +15,26 @@ app = FastAPI()
app.include_router( app.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
) )
app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"]) app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
app.include_router( app.include_router(
fastapi_users.get_reset_password_router(), fastapi_users.get_reset_password_router(),
prefix="/auth", prefix="/auth",
tags=["auth"], tags=["auth"],
) )
app.include_router( app.include_router(
fastapi_users.get_verify_router(), fastapi_users.get_verify_router(UserRead),
prefix="/auth", prefix="/auth",
tags=["auth"], tags=["auth"],
) )
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) app.include_router(
fastapi_users.get_users_router(UserRead, UserUpdate),
prefix="/users",
tags=["users"],
)
app.include_router( app.include_router(
fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"), fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"),
prefix="/auth/google", prefix="/auth/google",
@ -35,13 +43,15 @@ app.include_router(
@app.get("/authenticated-route") @app.get("/authenticated-route")
async def authenticated_route(user: UserDB = Depends(current_active_user)): async def authenticated_route(user: User = Depends(current_active_user)):
return {"message": f"Hello {user.email}!"} return {"message": f"Hello {user.email}!"}
register_tortoise( @app.on_event("startup")
app, async def on_startup():
db_url=DATABASE_URL, await init_beanie(
modules={"models": ["app.models"]}, database=db,
generate_schemas=True, document_models=[
) User,
],
)

View File

@ -0,0 +1,24 @@
from typing import List
import motor.motor_asyncio
from beanie import PydanticObjectId
from fastapi_users.db import BaseOAuthAccount, BeanieBaseUser, BeanieUserDatabase
from pydantic import Field
DATABASE_URL = "mongodb://localhost:27017"
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
class OAuthAccount(BaseOAuthAccount):
pass
class User(BeanieBaseUser[PydanticObjectId]):
oauth_accounts: List[OAuthAccount] = Field(default_factory=list)
async def get_user_db():
yield BeanieUserDatabase(User)

View File

@ -0,0 +1,14 @@
from beanie import PydanticObjectId
from fastapi_users import schemas
class UserRead(schemas.BaseUser[PydanticObjectId]):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass

View File

@ -1,6 +1,7 @@
import os import os
from typing import Optional from typing import Optional
from beanie import PydanticObjectId
from fastapi import Depends, Request from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers from fastapi_users import BaseUserManager, FastAPIUsers
from fastapi_users.authentication import ( from fastapi_users.authentication import (
@ -8,41 +9,38 @@ from fastapi_users.authentication import (
BearerTransport, BearerTransport,
JWTStrategy, JWTStrategy,
) )
from fastapi_users.db import MongoDBUserDatabase from fastapi_users.db import BeanieUserDatabase, ObjectIDIDMixin
from httpx_oauth.clients.google import GoogleOAuth2 from httpx_oauth.clients.google import GoogleOAuth2
from app.db import get_user_db from app.db import User, get_user_db
from app.models import User, UserCreate, UserDB, UserUpdate
SECRET = "SECRET" SECRET = "SECRET"
google_oauth_client = GoogleOAuth2( google_oauth_client = GoogleOAuth2(
os.environ["GOOGLE_OAUTH_CLIENT_ID"], os.getenv("GOOGLE_OAUTH_CLIENT_ID", ""),
os.environ["GOOGLE_OAUTH_CLIENT_SECRET"], os.getenv("GOOGLE_OAUTH_CLIENT_SECRET", ""),
) )
class UserManager(BaseUserManager[UserCreate, UserDB]): class UserManager(ObjectIDIDMixin, BaseUserManager[User, PydanticObjectId]):
user_db_model = UserDB
reset_password_token_secret = SECRET reset_password_token_secret = SECRET
verification_token_secret = SECRET verification_token_secret = SECRET
async def on_after_register(self, user: UserDB, request: Optional[Request] = None): async def on_after_register(self, user: User, request: Optional[Request] = None):
print(f"User {user.id} has registered.") print(f"User {user.id} has registered.")
async def on_after_forgot_password( async def on_after_forgot_password(
self, user: UserDB, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
): ):
print(f"User {user.id} has forgot their password. Reset token: {token}") print(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify( async def on_after_request_verify(
self, user: UserDB, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
): ):
print(f"Verification requested for user {user.id}. Verification token: {token}") print(f"Verification requested for user {user.id}. Verification token: {token}")
async def get_user_manager(user_db: MongoDBUserDatabase = Depends(get_user_db)): async def get_user_manager(user_db: BeanieUserDatabase = Depends(get_user_db)):
yield UserManager(user_db) yield UserManager(user_db)
@ -58,13 +56,7 @@ auth_backend = AuthenticationBackend(
transport=bearer_transport, transport=bearer_transport,
get_strategy=get_jwt_strategy, get_strategy=get_jwt_strategy,
) )
fastapi_users = FastAPIUsers(
get_user_manager, fastapi_users = FastAPIUsers[User, PydanticObjectId](get_user_manager, [auth_backend])
[auth_backend],
User,
UserCreate,
UserUpdate,
UserDB,
)
current_active_user = fastapi_users.current_user(active=True) current_active_user = fastapi_users.current_user(active=True)

View File

@ -0,0 +1,4 @@
import uvicorn
if __name__ == "__main__":
uvicorn.run("app.app:app", host="0.0.0.0", log_level="info")

View File

@ -0,0 +1,3 @@
fastapi
fastapi-users[beanie]
uvicorn[standard]

View File

@ -1,37 +0,0 @@
from fastapi import Depends, FastAPI
from app.models import UserDB
from app.users import (
auth_backend,
current_active_user,
fastapi_users,
google_oauth_client,
)
app = FastAPI()
app.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"])
app.include_router(
fastapi_users.get_reset_password_router(),
prefix="/auth",
tags=["auth"],
)
app.include_router(
fastapi_users.get_verify_router(),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
app.include_router(
fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"),
prefix="/auth/google",
tags=["auth"],
)
@app.get("/authenticated-route")
async def authenticated_route(user: UserDB = Depends(current_active_user)):
return {"message": f"Hello {user.email}!"}

View File

@ -1,17 +0,0 @@
import os
import motor.motor_asyncio
from fastapi_users.db import MongoDBUserDatabase
from app.models import UserDB
DATABASE_URL = os.environ["DATABASE_URL"]
client = motor.motor_asyncio.AsyncIOMotorClient(
DATABASE_URL, uuidRepresentation="standard"
)
db = client["database_name"]
collection = db["users"]
async def get_user_db():
yield MongoDBUserDatabase(UserDB, collection)

View File

@ -1,17 +0,0 @@
from fastapi_users import models, schemas
class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass
class UserDB(User, schemas.BaseUserDB):
pass

View File

@ -1,4 +0,0 @@
import uvicorn
if __name__ == "__main__":
uvicorn.run("app.app:app", host="0.0.0.0", port=5000, log_level="info")

View File

@ -1,3 +0,0 @@
fastapi
fastapi-users[mongodb,oauth]
uvicorn[standard]

View File

@ -1,7 +1,7 @@
from fastapi import Depends, FastAPI from fastapi import Depends, FastAPI
from app.db import create_db_and_tables from app.db import User, create_db_and_tables
from app.models import UserDB from app.schemas import UserCreate, UserRead, UserUpdate
from app.users import ( from app.users import (
auth_backend, auth_backend,
current_active_user, current_active_user,
@ -14,18 +14,26 @@ app = FastAPI()
app.include_router( app.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
) )
app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"]) app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
app.include_router( app.include_router(
fastapi_users.get_reset_password_router(), fastapi_users.get_reset_password_router(),
prefix="/auth", prefix="/auth",
tags=["auth"], tags=["auth"],
) )
app.include_router( app.include_router(
fastapi_users.get_verify_router(), fastapi_users.get_verify_router(UserRead),
prefix="/auth", prefix="/auth",
tags=["auth"], tags=["auth"],
) )
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"]) app.include_router(
fastapi_users.get_users_router(UserRead, UserUpdate),
prefix="/users",
tags=["users"],
)
app.include_router( app.include_router(
fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"), fastapi_users.get_oauth_router(google_oauth_client, auth_backend, "SECRET"),
prefix="/auth/google", prefix="/auth/google",
@ -34,7 +42,7 @@ app.include_router(
@app.get("/authenticated-route") @app.get("/authenticated-route")
async def authenticated_route(user: UserDB = Depends(current_active_user)): async def authenticated_route(user: User = Depends(current_active_user)):
return {"message": f"Hello {user.email}!"} return {"message": f"Hello {user.email}!"}

View File

@ -1,29 +1,27 @@
from typing import AsyncGenerator from typing import AsyncGenerator, List
from fastapi import Depends from fastapi import Depends
from fastapi_users.db import ( from fastapi_users.db import (
SQLAlchemyBaseOAuthAccountTable, SQLAlchemyBaseOAuthAccountTableUUID,
SQLAlchemyBaseUserTable, SQLAlchemyBaseUserTableUUID,
SQLAlchemyUserDatabase, SQLAlchemyUserDatabase,
) )
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.orm import relationship, sessionmaker
from app.models import UserDB
DATABASE_URL = "sqlite+aiosqlite:///./test.db" DATABASE_URL = "sqlite+aiosqlite:///./test.db"
Base: DeclarativeMeta = declarative_base() Base: DeclarativeMeta = declarative_base()
class UserTable(Base, SQLAlchemyBaseUserTable): class OAuthAccount(SQLAlchemyBaseOAuthAccountTableUUID, Base):
oauth_accounts = relationship("OAuthAccountTable")
class OAuthAccountTable(SQLAlchemyBaseOAuthAccountTable, Base):
pass pass
class User(SQLAlchemyBaseUserTableUUID, Base):
oauth_accounts: List[OAuthAccount] = relationship("OAuthAccount", lazy="joined")
engine = create_async_engine(DATABASE_URL) engine = create_async_engine(DATABASE_URL)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
@ -39,4 +37,4 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async def get_user_db(session: AsyncSession = Depends(get_async_session)): async def get_user_db(session: AsyncSession = Depends(get_async_session)):
yield SQLAlchemyUserDatabase(UserDB, session, UserTable, OAuthAccountTable) yield SQLAlchemyUserDatabase(session, User, OAuthAccount)

View File

@ -1,17 +0,0 @@
from fastapi_users import models, schemas
class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass
class UserDB(User, schemas.BaseUserDB):
pass

View File

@ -0,0 +1,15 @@
import uuid
from fastapi_users import schemas
class UserRead(schemas.BaseUser[uuid.UUID]):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass

View File

@ -1,8 +1,9 @@
import os import os
import uuid
from typing import Optional from typing import Optional
from fastapi import Depends, Request from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin
from fastapi_users.authentication import ( from fastapi_users.authentication import (
AuthenticationBackend, AuthenticationBackend,
BearerTransport, BearerTransport,
@ -11,33 +12,30 @@ from fastapi_users.authentication import (
from fastapi_users.db import SQLAlchemyUserDatabase from fastapi_users.db import SQLAlchemyUserDatabase
from httpx_oauth.clients.google import GoogleOAuth2 from httpx_oauth.clients.google import GoogleOAuth2
from app.db import get_user_db from app.db import User, get_user_db
from app.models import User, UserCreate, UserDB, UserUpdate
SECRET = "SECRET" SECRET = "SECRET"
google_oauth_client = GoogleOAuth2( google_oauth_client = GoogleOAuth2(
os.getenv("GOOGLE_OAUTH_CLIENT_ID", ""), os.getenv("GOOGLE_OAUTH_CLIENT_ID", ""),
os.getenv("GOOGLE_OAUTH_CLIENT_SECRET", ""), os.getenv("GOOGLE_OAUTH_CLIENT_SECRET", ""),
) )
class UserManager(BaseUserManager[UserCreate, UserDB]): class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
user_db_model = UserDB
reset_password_token_secret = SECRET reset_password_token_secret = SECRET
verification_token_secret = SECRET verification_token_secret = SECRET
async def on_after_register(self, user: UserDB, request: Optional[Request] = None): async def on_after_register(self, user: User, request: Optional[Request] = None):
print(f"User {user.id} has registered.") print(f"User {user.id} has registered.")
async def on_after_forgot_password( async def on_after_forgot_password(
self, user: UserDB, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
): ):
print(f"User {user.id} has forgot their password. Reset token: {token}") print(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify( async def on_after_request_verify(
self, user: UserDB, token: str, request: Optional[Request] = None self, user: User, token: str, request: Optional[Request] = None
): ):
print(f"Verification requested for user {user.id}. Verification token: {token}") print(f"Verification requested for user {user.id}. Verification token: {token}")
@ -58,13 +56,7 @@ auth_backend = AuthenticationBackend(
transport=bearer_transport, transport=bearer_transport,
get_strategy=get_jwt_strategy, get_strategy=get_jwt_strategy,
) )
fastapi_users = FastAPIUsers(
get_user_manager, fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
[auth_backend],
User,
UserCreate,
UserUpdate,
UserDB,
)
current_active_user = fastapi_users.current_user(active=True) current_active_user = fastapi_users.current_user(active=True)

View File

@ -1,4 +1,4 @@
fastapi fastapi
fastapi-users[sqlalchemy2,oauth] fastapi-users[sqlalchemy]
uvicorn[standard] uvicorn[standard]
aiosqlite aiosqlite

View File

@ -1,9 +0,0 @@
from fastapi_users.db import TortoiseUserDatabase
from app.models import OAuthAccount, UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel, OAuthAccount)

View File

@ -1,30 +0,0 @@
from fastapi_users import models, schemas
from fastapi_users.db import TortoiseBaseOAuthAccountModel, TortoiseBaseUserModel
from tortoise import fields
from tortoise.contrib.pydantic import PydanticModel
class User(schemas.BaseUser, schemas.BaseOAuthAccountMixin):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, schemas.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel
class OAuthAccount(TortoiseBaseOAuthAccountModel):
user = fields.ForeignKeyField("models.UserModel", related_name="oauth_accounts")

View File

@ -1,70 +0,0 @@
import os
from typing import Optional
from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
JWTStrategy,
)
from fastapi_users.db import TortoiseUserDatabase
from httpx_oauth.clients.google import GoogleOAuth2
from app.db import get_user_db
from app.models import User, UserCreate, UserDB, UserUpdate
SECRET = "SECRET"
google_oauth_client = GoogleOAuth2(
os.environ["GOOGLE_OAUTH_CLIENT_ID"],
os.environ["GOOGLE_OAUTH_CLIENT_SECRET"],
)
class UserManager(BaseUserManager[UserCreate, UserDB]):
user_db_model = UserDB
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: UserDB, request: Optional[Request] = None):
print(f"User {user.id} has registered.")
async def on_after_forgot_password(
self, user: UserDB, token: str, request: Optional[Request] = None
):
print(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify(
self, user: UserDB, token: str, request: Optional[Request] = None
):
print(f"Verification requested for user {user.id}. Verification token: {token}")
async def get_user_manager(user_db: TortoiseUserDatabase = Depends(get_user_db)):
yield UserManager(user_db)
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
auth_backend = AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=get_jwt_strategy,
)
fastapi_users = FastAPIUsers(
get_user_manager,
[auth_backend],
User,
UserCreate,
UserUpdate,
UserDB,
)
current_active_user = fastapi_users.current_user(active=True)

View File

@ -1,4 +0,0 @@
import uvicorn
if __name__ == "__main__":
uvicorn.run("app.app:app", host="0.0.0.0", port=5000, log_level="info")

View File

@ -1,3 +0,0 @@
fastapi
fastapi-users[tortoise-orm,oauth]
uvicorn[standard]

View File

@ -1,37 +0,0 @@
from fastapi import Depends, FastAPI
from tortoise.contrib.fastapi import register_tortoise
from app.db import DATABASE_URL
from app.models import UserDB
from app.users import auth_backend, current_active_user, fastapi_users
app = FastAPI()
app.include_router(
fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"]
)
app.include_router(fastapi_users.get_register_router(), prefix="/auth", tags=["auth"])
app.include_router(
fastapi_users.get_reset_password_router(),
prefix="/auth",
tags=["auth"],
)
app.include_router(
fastapi_users.get_verify_router(),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
@app.get("/authenticated-route")
async def authenticated_route(user: UserDB = Depends(current_active_user)):
return {"message": f"Hello {user.email}!"}
register_tortoise(
app,
db_url=DATABASE_URL,
modules={"models": ["app.models"]},
generate_schemas=True,
)

View File

@ -1,9 +0,0 @@
from fastapi_users.db import TortoiseUserDatabase
from app.models import UserDB, UserModel
DATABASE_URL = "sqlite://./test.db"
async def get_user_db():
yield TortoiseUserDatabase(UserDB, UserModel)

View File

@ -1,25 +0,0 @@
from fastapi_users import models, schemas
from fastapi_users.db import TortoiseBaseUserModel
from tortoise.contrib.pydantic import PydanticModel
class User(schemas.BaseUser):
pass
class UserCreate(schemas.BaseUserCreate):
pass
class UserUpdate(schemas.BaseUserUpdate):
pass
class UserModel(TortoiseBaseUserModel):
pass
class UserDB(User, schemas.BaseUserDB, PydanticModel):
class Config:
orm_mode = True
orig_model = UserModel

View File

@ -1,62 +0,0 @@
from typing import Optional
from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
JWTStrategy,
)
from fastapi_users.db import TortoiseUserDatabase
from app.db import get_user_db
from app.models import User, UserCreate, UserDB, UserUpdate
SECRET = "SECRET"
class UserManager(BaseUserManager[UserCreate, UserDB]):
user_db_model = UserDB
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def on_after_register(self, user: UserDB, request: Optional[Request] = None):
print(f"User {user.id} has registered.")
async def on_after_forgot_password(
self, user: UserDB, token: str, request: Optional[Request] = None
):
print(f"User {user.id} has forgot their password. Reset token: {token}")
async def on_after_request_verify(
self, user: UserDB, token: str, request: Optional[Request] = None
):
print(f"Verification requested for user {user.id}. Verification token: {token}")
async def get_user_manager(user_db: TortoiseUserDatabase = Depends(get_user_db)):
yield UserManager(user_db)
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
auth_backend = AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=get_jwt_strategy,
)
fastapi_users = FastAPIUsers(
get_user_manager,
[auth_backend],
User,
UserCreate,
UserUpdate,
UserDB,
)
current_active_user = fastapi_users.current_user(active=True)

View File

@ -1,4 +0,0 @@
import uvicorn
if __name__ == "__main__":
uvicorn.run("app.app:app", host="0.0.0.0", port=5000, log_level="info")

View File

@ -1,3 +0,0 @@
fastapi
fastapi-users[tortoise-orm]
uvicorn[standard]