Skip to content

Commit

Permalink
✨ fastapi-users cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
juftin committed Aug 23, 2023
1 parent 73c859f commit 20ef9c4
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# zoo

An asynchronous zoo API, powered by FastAPI and SQLModel.
An asynchronous zoo API, powered by [FastAPI](https://fastapi.tiangolo.com/),
[SQLAlchemy](https://www.sqlalchemy.org/), [Pydantic](https://docs.pydantic.dev/latest/),
and [Alembic](https://alembic.sqlalchemy.org/en/latest/).
File renamed without changes.
File renamed without changes.
File renamed without changes.
24 changes: 24 additions & 0 deletions zoo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
Zoo CLI
"""

import asyncio
import json
import logging
import pathlib
from dataclasses import dataclass

import click
import uvicorn
from click import Context
from fastapi_users.exceptions import UserAlreadyExists

from zoo._version import __application__, __version__
from zoo.app import ZooFastAPI, app
from zoo.config import ZooSettings, app_config
from zoo.models.users import create_user

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -58,6 +62,26 @@ def openapi(context: ZooContext) -> None:
json_file.write_text(json.dumps(openapi_body, indent=2))


@cli.command()
@click.option("-e", "--email", help="User email to create", type=str, required=True)
@click.option(
"-p", "--password", default="admin", help="Password to create", type=str, required=True
)
@click.pass_context
def users(context: Context, email: str, password: str) -> None:
"""
Create users
"""
_ = context
logger.info("Creating user: %s", email)
try:
user = asyncio.run(create_user(email=email, password=password))
logger.info("Created user: %s", user.id)
except UserAlreadyExists:
logger.info("User already exists: %s", email)
context.exit(1)


if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
if not app_config.DOCKER:
Expand Down
34 changes: 32 additions & 2 deletions zoo/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@

from fastapi import Depends, FastAPI
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin
from fastapi_users.authentication import AuthenticationBackend, BearerTransport
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
CookieTransport,
JWTStrategy,
)
from fastapi_users.authentication.strategy import AccessTokenDatabase, DatabaseStrategy
from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase
from fastapi_users_db_sqlalchemy.access_token import (
Expand All @@ -18,13 +23,15 @@
)
from sqlalchemy.ext.asyncio import AsyncSession

from zoo._version import __application__
from zoo.config import app_config
from zoo.db import get_async_session
from zoo.models.base import Base, CreatedUpdatedMixin, UpdatedAtMixin
from zoo.schemas.users import UserCreate, UserRead

auth_endpoint = "auth"
jwt_endpoint = "jwt"
cookie_endpoint = "cookie"


class User(SQLAlchemyBaseUserTableUUID, CreatedUpdatedMixin, Base):
Expand Down Expand Up @@ -72,6 +79,15 @@ async def get_user_manager(user_db=Depends(get_user_db)) -> AsyncGenerator[UserM
yield UserManager(user_db=user_db)


def get_jwt_strategy() -> JWTStrategy:
"""
Get a DatabaseStrategy using the AccessTokenDatabase
"""
return JWTStrategy(
secret=app_config.DATABASE_SECRET, lifetime_seconds=app_config.JWT_EXPIRATION
)


def get_database_strategy(
access_token_db: AccessTokenDatabase = Depends(get_access_token_db),
) -> DatabaseStrategy:
Expand All @@ -82,14 +98,23 @@ def get_database_strategy(


bearer_transport = BearerTransport(tokenUrl=f"{auth_endpoint}/{jwt_endpoint}/login")
cookie_transport = CookieTransport(cookie_name=f"{__application__}-auth", cookie_max_age=3600)


auth_backend = AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=get_database_strategy,
)
cookie_auth_backend = AuthenticationBackend(
name="cookie",
transport=cookie_transport,
get_strategy=get_jwt_strategy,
)

fastapi_users = FastAPIUsers[User, uuid.UUID](
get_user_manager=get_user_manager, auth_backends=[auth_backend]
get_user_manager=get_user_manager,
auth_backends=[auth_backend, cookie_auth_backend],
)

current_active_user = fastapi_users.current_user(active=True)
Expand Down Expand Up @@ -148,6 +173,11 @@ def bootstrap_fastapi_users(app: FastAPI) -> None:
prefix=f"/{auth_endpoint}/{jwt_endpoint}",
tags=[auth_endpoint],
)
app.include_router(
fastapi_users.get_auth_router(cookie_auth_backend),
prefix=f"/{auth_endpoint}/{cookie_endpoint}",
tags=[auth_endpoint],
)
app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix=f"/{auth_endpoint}",
Expand Down

0 comments on commit 20ef9c4

Please sign in to comment.