Shared authentication for ZnDraw using fastapi-users
Project description
zndraw-auth
Shared authentication package for the ZnDraw ecosystem using fastapi-users.
Installation
pip install zndraw-auth
# or with uv
uv add zndraw-auth
Quick Start
from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI
from zndraw_auth import (
User,
UserCreate,
UserRead,
auth_backend,
create_db_and_tables,
current_active_user,
fastapi_users,
)
@asynccontextmanager
async def lifespan(app: FastAPI):
await create_db_and_tables()
yield
app = FastAPI(lifespan=lifespan)
# Include auth routers
app.include_router(
fastapi_users.get_auth_router(auth_backend),
prefix="/auth/jwt",
tags=["auth"],
)
app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
@app.get("/protected")
async def protected_route(user: User = Depends(current_active_user)):
return {"message": f"Hello {user.email}!"}
Extending with Custom Models (e.g., zndraw-joblib)
Other packages can import Base and get_async_session to define models that share the same database and have foreign key relationships to User.
Example: Adding a Job model in zndraw-joblib
# zndraw_joblib/models.py
import uuid
from typing import TYPE_CHECKING
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from zndraw_auth import Base
if TYPE_CHECKING:
from zndraw_auth import User
class Job(Base):
"""A compute job owned by a user."""
__tablename__ = "job"
id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
name: Mapped[str] = mapped_column(String(255))
status: Mapped[str] = mapped_column(String(50), default="pending")
# Foreign key to User from zndraw-auth (cascade delete when user is deleted)
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id", ondelete="cascade"))
# Relationship (optional, for ORM navigation)
user: Mapped["User"] = relationship("User", lazy="selectin")
Example: Using the shared session in endpoints
# zndraw_joblib/routes.py
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from zndraw_auth import User, current_active_user, get_async_session
from .models import Job
router = APIRouter(prefix="/jobs", tags=["jobs"])
@router.post("/")
async def create_job(
name: str,
user: Annotated[User, Depends(current_active_user)],
session: Annotated[AsyncSession, Depends(get_async_session)],
):
"""Create a new job for the current user."""
job = Job(name=name, user_id=user.id)
session.add(job)
await session.commit()
await session.refresh(job)
return {"id": str(job.id), "name": job.name, "status": job.status}
@router.get("/")
async def list_jobs(
user: Annotated[User, Depends(current_active_user)],
session: Annotated[AsyncSession, Depends(get_async_session)],
):
"""List all jobs for the current user."""
result = await session.execute(
select(Job).where(Job.user_id == user.id)
)
jobs = result.scalars().all()
return [{"id": str(j.id), "name": j.name, "status": j.status} for j in jobs]
@router.get("/{job_id}")
async def get_job(
job_id: UUID,
user: Annotated[User, Depends(current_active_user)],
session: Annotated[AsyncSession, Depends(get_async_session)],
):
"""Get a specific job (must belong to current user)."""
result = await session.execute(
select(Job).where(Job.id == job_id, Job.user_id == user.id)
)
job = result.scalar_one_or_none()
if not job:
raise HTTPException(status_code=404, detail="Job not found")
return {"id": str(job.id), "name": job.name, "status": job.status}
Example: App setup with multiple routers
# main.py (in zndraw-fastapi or combined app)
from contextlib import asynccontextmanager
from fastapi import FastAPI
from zndraw_auth import (
UserCreate,
UserRead,
auth_backend,
create_db_and_tables,
fastapi_users,
)
from zndraw_joblib.routes import router as jobs_router
@asynccontextmanager
async def lifespan(app: FastAPI):
# Creates tables for User AND Job (all models using Base)
await create_db_and_tables()
yield
app = FastAPI(lifespan=lifespan)
# Auth routes from zndraw-auth
app.include_router(
fastapi_users.get_auth_router(auth_backend),
prefix="/auth/jwt",
tags=["auth"],
)
app.include_router(
fastapi_users.get_register_router(UserRead, UserCreate),
prefix="/auth",
tags=["auth"],
)
# Job routes from zndraw-joblib
app.include_router(jobs_router)
Configuration
Settings are loaded from environment variables with the ZNDRAW_AUTH_ prefix:
| Variable | Default | Description |
|---|---|---|
ZNDRAW_AUTH_SECRET_KEY |
CHANGE-ME-IN-PRODUCTION |
JWT signing secret |
ZNDRAW_AUTH_TOKEN_LIFETIME_SECONDS |
3600 |
JWT token lifetime |
ZNDRAW_AUTH_DATABASE_URL |
sqlite+aiosqlite:///./zndraw_auth.db |
Database connection URL |
ZNDRAW_AUTH_RESET_PASSWORD_TOKEN_SECRET |
CHANGE-ME-RESET |
Password reset token secret |
ZNDRAW_AUTH_VERIFICATION_TOKEN_SECRET |
CHANGE-ME-VERIFY |
Email verification token secret |
ZNDRAW_AUTH_DEFAULT_ADMIN_EMAIL |
None |
Email for the default admin user |
ZNDRAW_AUTH_DEFAULT_ADMIN_PASSWORD |
None |
Password for the default admin user |
Dev Mode vs Production Mode
The system has two operating modes based on admin configuration:
Dev Mode (default - no admin configured):
- All newly registered users are automatically granted superuser privileges
- Useful for development and testing
Production Mode (admin configured):
- Set
ZNDRAW_AUTH_DEFAULT_ADMIN_EMAILandZNDRAW_AUTH_DEFAULT_ADMIN_PASSWORD - The configured admin user is created/promoted on startup
- New users are created as regular users (not superusers)
# Production mode example
export ZNDRAW_AUTH_DEFAULT_ADMIN_EMAIL=admin@example.com
export ZNDRAW_AUTH_DEFAULT_ADMIN_PASSWORD=secure-password
Exports
from zndraw_auth import (
# SQLAlchemy Base (for extending with your own models)
Base,
# User model
User,
# Database utilities
create_db_and_tables,
get_async_session,
get_user_db,
# Pydantic schemas
UserCreate,
UserRead,
UserUpdate,
# Settings
AuthSettings,
get_auth_settings,
# User manager (for custom lifecycle hooks)
UserManager,
get_user_manager,
# FastAPIUsers instance (for including routers)
fastapi_users,
auth_backend,
# Dependencies for Depends()
current_active_user, # Requires authenticated active user
current_superuser, # Requires superuser
current_optional_user, # User | None (optional auth)
)
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zndraw_auth-0.2.0.tar.gz.
File metadata
- Download URL: zndraw_auth-0.2.0.tar.gz
- Upload date:
- Size: 7.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5275ce1cd77f7c5d2e633809424320b77f362e5164bccbfde39bd83b8bd606ab
|
|
| MD5 |
06f16c32611185c0569591480cef48ea
|
|
| BLAKE2b-256 |
b56b781133e0222f2aa198367f8f4f225a020554e671f0d9b27462cccf9b8341
|
File details
Details for the file zndraw_auth-0.2.0-py3-none-any.whl.
File metadata
- Download URL: zndraw_auth-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4be8415f10651ed4dcc252dec5732ba25213a882c141b9cf8cc92b782ad4fbde
|
|
| MD5 |
c9fad0efaa902ba896d4486b1aa4d292
|
|
| BLAKE2b-256 |
9d7d2a860965254afda17de2514325b915fb07678566cebc3f3cb4db0b53fb2c
|