Skip to content

Commit

Permalink
Merge pull request #8 from everysoftware/feature/docker-tests
Browse files Browse the repository at this point in the history
Move observability to another repo
  • Loading branch information
everysoftware authored Oct 16, 2024
2 parents 2309287 + daf0f0a commit 6b18286
Show file tree
Hide file tree
Showing 38 changed files with 104 additions and 1,792 deletions.
34 changes: 34 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
APP_NAME="breezeauth"
APP_DISPLAY_NAME="Breeze Auth"
APP_ENV="dev"

DB_URL="postgresql+asyncpg://postgres:changethis@db:5432/app"
REDIS_URL="redis://default:changethis@redis:6379/0"
TEMPO_URL="http://host.docker.internal:4317"

JWT_PRIVATE_KEY="certs/jwt-private.pem"
JWT_PUBLIC_KEY="certs/jwt-public.pem"
ADMIN_PASSWORD="changethis"

GOOGLE_SSO=0
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""

YANDEX_SSO=0
YANDEX_CLIENT_ID=""
YANDEX_CLIENT_SECRET=""

TELEGRAM_SSO=0
TELEGRAM_BOT_TOKEN=""

MAIL_ENABLED=1
SMTP_HOST="smtp.gmail.com"
SMTP_PORT=465
SMTP_USERNAME=""
SMTP_PASSWORD=""
SMTP_FROM_NAME="Breeze Auth"

# Docker environment
POSTGRES_USER="postgres"
POSTGRES_PASSWORD="changethis"
POSTGRES_DB="app"
32 changes: 0 additions & 32 deletions .example.env

This file was deleted.

1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
custom: https://boosty.to/everysoftware
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ repos:
rev: v4.6.0
hooks:
- id: check-added-large-files
args:
- --maxkb=2000
- id: check-toml
- id: check-yaml
args:
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ APP_PATH = app
TESTS_PATH = tests
LOGS_SINCE = 10m

ifneq (,$(wildcard ./.env))
include .env
export
endif

.PHONY: run
run:
docker-compose up db redis -d
uvicorn $(APP_PATH):app --host 0.0.0.0 --port 8000
uvicorn $(APP_PATH):app --host 0.0.0.0 --port 8000 --log-config logging.yaml

.PHONY: up
up:
Expand Down
66 changes: 34 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,60 @@
# FastAPI Auth
# Breeze Auth

Authorization server with **Google**, **Yandex** & **Telegram SSO**, **RBAC**, **user management**, etc.
Powered by FastAPI & PostgreSQL.
Authorization server with OAuth2, permissions, user management, etc.

## Features

- Secure and usable **JWT authorization** (feat. Refresh Tokens)
- Extendable **single sign-on** via Google, Yandex & Telegram
- Confirm actions using a **one-time code** (Email & Telegram)
- Extendable **role-based access control** (supports user & superuser)
- Powerful **user** management: CRUD, search, etc.
- **Admin panel** with authorization & CRUD operations
- **Observability** with Grafana: metrics, tracing & logging
- Secure **JWT authorization**
- Scalable Google, Telegram and Yandex **single sign-on**
- **One-time code** confirmation (Email & Telegram)
- Extendable **permission system**
- Powerful **user management**: CRUD, search, roles, etc.
- **Admin panel** with authorization and CRUD operations
- **Grafana dashboard** with metrics, tracing & logging

## Installation

1. Clone the repository:
1. Install [observability preset](https://github.com/everysoftware/fastapi-obs)
2. Clone the repository:

```bash
git clone https://github.com/everysoftware/fastapi-auth
```
```bash
git clone https://github.com/everysoftware/fastapi-auth
```

2. Generate RSA keys:
3. Generate RSA keys:

```bash
openssl genrsa -out certs/private.pem 2048
openssl rsa -in certs/private.pem -pubout -out certs/public.pem
```
```bash
openssl genrsa -out certs/private.pem 2048
openssl rsa -in certs/private.pem -pubout -out certs/public.pem
```

3. Create a `.env` file. Use the `.env.example` as a reference.
4. Run the application:
4. Create a `.env` file. Use the `.env.example` as a reference.
5. Run the application:

```bash
make up
```
```bash
make up
```

## Screenshots

### Swagger UI

![img.png](assets/swagger_auth.png)
![img_1.png](assets/swagger_oauth.png)
![Swagger Auth](assets/swagger_auth.png)
![Swagger OAuth](assets/swagger_oauth.png)

### Consent example
### Consents

![img.png](assets/consent.png)
![Google](assets/google_consent.png)
![Telegram](assets/telegram_consent.png)
![Yandex](assets/yandex_consent.png)

### Admin Panel

![img_2.png](assets/admin_panel.png)
![Admin Panel](assets/admin_panel.png)

### Dashboards

![img_3.png](assets/dashboard_metrics.png)
![img_4.png](assets/dashboards_logs.png)
![Metrics](assets/dashboard_metrics.png)
![Logs](assets/dashboards_logs.png)

**Made with love by Ivan Stasevich ❤️**
**Made with ❤️**
3 changes: 1 addition & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .main import app
from .workers import MyUvicornWorker

__all__ = ["MyUvicornWorker", "app"]
__all__ = ["app"]
3 changes: 1 addition & 2 deletions app/db/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from sqlalchemy.orm import mapped_column, Mapped

from app.db import utils
from app.db.types import ID


# https://docs.sqlalchemy.org/en/20/core/defaults.html
Expand All @@ -17,7 +16,7 @@ class Mixin:


class IDMixin(Mixin):
id: Mapped[ID] = mapped_column(
id: Mapped[uuid.UUID] = mapped_column(
primary_key=True,
default=uuid.uuid4,
sort_order=-100,
Expand Down
4 changes: 2 additions & 2 deletions app/db/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
from app.schemas import BackendBase


class AlchemyRepository(ABC):
class BaseAlchemyRepository(ABC):
session: AsyncSession

def __init__(self, session: AsyncSession):
self.session = session


class AlchemyGenericRepository[M: BaseOrm, S: BackendBase](AlchemyRepository):
class AlchemyRepository[M: BaseOrm, S: BackendBase](BaseAlchemyRepository):
model_type: type[M]
schema_type: type[S]

Expand Down
4 changes: 2 additions & 2 deletions app/db/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def sort_params(self) -> list[SortParam]:
return [SortParam.from_str(i) for i in sort]


class Page[IT: BackendBase](BackendBase):
items: list[IT]
class Page[T: BackendBase](BackendBase):
items: list[T]

@computed_field # type: ignore
@property
Expand Down
2 changes: 1 addition & 1 deletion app/obs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


class ObservabilitySettings(BackendSettings):
export_url: str = "http://tempo:4317"
tempo_url: str = "http://host.docker.internal:4317"
5 changes: 2 additions & 3 deletions app/obs/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def filter(self, record: logging.LogRecord) -> bool:

main_log = logging.getLogger("fastapiapp")
main_log.setLevel(logging.INFO)
if not (default_handler := logging.getHandlerByName("default")):
raise RuntimeError("Incorrect log config: no default handler")
main_log.addHandler(default_handler)
if default_handler := logging.getHandlerByName("default"):
main_log.addHandler(default_handler)
main_log.propagate = False
2 changes: 1 addition & 1 deletion app/obs/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ def setup_obs(app: FastAPI) -> None:
instrument(
app,
async_engine,
export_url=settings.obs.export_url,
export_url=settings.obs.tempo_url,
app_name=settings.app_name,
)
6 changes: 2 additions & 4 deletions app/sso_accounts/repositories.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from sqlalchemy import select

from app.db.repository import AlchemyGenericRepository
from app.db.repository import AlchemyRepository
from app.db.schemas import PageParams, Page
from app.db.types import ID
from app.sso_accounts.models import SSOAccountOrm
from app.sso_accounts.schemas import SSOAccountRead


class SSOAccountRepository(
AlchemyGenericRepository[SSOAccountOrm, SSOAccountRead]
):
class SSOAccountRepository(AlchemyRepository[SSOAccountOrm, SSOAccountRead]):
model_type = SSOAccountOrm
schema_type = SSOAccountRead

Expand Down
4 changes: 2 additions & 2 deletions app/users/repositories.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from sqlalchemy import select

from app.db.repository import AlchemyGenericRepository
from app.db.repository import AlchemyRepository
from app.users.models import UserOrm
from app.users.schemas import UserRead


class UserRepository(AlchemyGenericRepository[UserOrm, UserRead]):
class UserRepository(AlchemyRepository[UserOrm, UserRead]):
model_type = UserOrm
schema_type = UserRead

Expand Down
File renamed without changes
Binary file added assets/telegram_consent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/yandex_consent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 14 additions & 49 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,23 @@ services:
- "5432:5432"

redis:
build:
context: .
dockerfile: docker/redis.Dockerfile
image: redis:latest
env_file:
- .env
volumes:
- redis_data:/var/lib/redis/data
restart: unless-stopped
ports:
- "6379:6379"

loki:
image: grafana/loki:3.0.0
command: -config.file=/etc/loki/local-config.yaml
ports:
- "3100:3100"
# Set credentials & logging
command: >
sh -c '
mkdir -p /usr/local/etc/redis &&
echo "bind 0.0.0.0" > /usr/local/etc/redis/redis.conf &&
echo "appendonly yes" >> /usr/local/etc/redis/redis.conf &&
echo "user default on >$REDIS_PASSWORD ~* +@all" > /usr/local/etc/redis/users.acl &&
redis-server /usr/local/etc/redis/redis.conf --aclfile /usr/local/etc/redis/users.acl
'
volumes:
- redis_data:/var/lib/redis/data

fastapi:
build:
Expand All @@ -52,47 +53,11 @@ services:
depends_on:
- db
- redis
- loki
logging: *default-logging
volumes:
- "./app:/opt/app/app"

prometheus:
image: prom/prometheus:v2.51.2
ports:
- "9090:9090"
volumes:
- ./obs/prometheus:/workspace
command:
- --config.file=/workspace/prometheus.yml
- --enable-feature=exemplar-storage
depends_on:
- loki
logging: *default-logging

tempo:
image: grafana/tempo:2.4.1
command: [ "--target=all", "--storage.trace.backend=local", "--storage.trace.local.path=/var/tempo", "--auth.enabled=false" ]
ports:
- "4317:4317"
- "4318:4318"
depends_on:
- loki
logging: *default-logging

grafana:
image: grafana/grafana:10.4.2
ports:
- "3000:3000"
volumes:
- ./obs/grafana/:/etc/grafana/provisioning/datasources
- ./obs/dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml
- ./obs/dashboards:/etc/grafana/dashboards
depends_on:
- loki
- tempo
- prometheus
logging: *default-logging
extra_hosts:
- "host.docker.internal:host-gateway"

volumes:
pg_data:
Expand Down
Loading

0 comments on commit 6b18286

Please sign in to comment.