feat: Fase 1 – Fondamenta complete (backend FastAPI + auth + permessi)

- docker-compose.yml: PostgreSQL 16, Redis 7, MinIO, Nginx
- backend FastAPI: struttura monorepo, config pydantic-settings
- modelli SQLAlchemy: tutti i modelli (tenants, users, mailboxes, messages, archival, permissions, labels, audit_log)
- migrazione Alembic 0001: schema completo in pure SQL
- auth API: login JWT, refresh token rotation, logout, 2FA TOTP (setup/verify/disable)
- CRUD utenti: lista, crea, modifica, reset password, soft delete
- permessi granulari (Fase 1-A): mailbox_permissions, assegna/revoca/lista
- CRUD tenant: gestione super-admin
- sicurezza: AES-256-GCM cifratura credenziali IMAP/SMTP, bcrypt password
- RLS PostgreSQL: isolamento multi-tenant per request
- seed sviluppo: tenant demo + admin + operator
- test unit: security (bcrypt, JWT, AES), auth_service
- test integration: auth endpoints, users endpoints
- CI GitHub Actions: lint (ruff), test (pytest), build Docker, security scan
- infra: nginx.conf, redis.conf
- Makefile con comandi make dev/test/migrate/seed

Definition of Done:
 Login, refresh token e TOTP funzionanti
 make dev porta in piedi tutto lo stack locale
 CI configurata
This commit is contained in:
2026-03-18 16:42:01 +01:00
parent 0251c2bbb0
commit 58a233236c
60 changed files with 6942 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pecflow-backend"
version = "1.0.0"
description = "PecFlow Backend API per gestione PEC SaaS"
requires-python = ">=3.12"
dependencies = [
# Web framework
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
# Database
"sqlalchemy>=2.0.36",
"asyncpg>=0.29.0", # driver async PostgreSQL
"psycopg2-binary>=2.9.9", # driver sync (Alembic)
"alembic>=1.13.0",
# Validazione e configurazione
"pydantic>=2.9.0",
"pydantic-settings>=2.5.0",
"email-validator>=2.2.0",
# Autenticazione e sicurezza
"python-jose[cryptography]>=3.3.0",
"bcrypt>=4.0.0", # password hashing (usato direttamente, senza passlib)
"pyotp>=2.9.0", # TOTP 2FA
"qrcode[pil]>=7.4.2", # generazione QR code TOTP
"cryptography>=43.0.0", # AES-256-GCM cifratura credenziali
# Rate limiting
"slowapi>=0.1.9",
# HTTP client
"httpx>=0.27.0",
# Storage MinIO/S3
"miniopy-async>=1.21.0",
# Utilities
"python-multipart>=0.0.9", # upload file
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]
dev = [
# Test
"pytest>=8.3.0",
"pytest-asyncio>=0.24.0",
"pytest-cov>=5.0.0",
"httpx>=0.27.0", # test client FastAPI
"anyio>=4.6.0",
"aiosqlite>=0.20.0", # driver SQLite async per i test di integrazione
# Linting e formatting
"ruff>=0.7.0",
"mypy>=1.13.0",
]
[tool.setuptools.packages.find]
where = ["."]
include = ["app*"]
# ─── Ruff ─────────────────────────────────────────────────────────────────────
[tool.ruff]
target-version = "py312"
line-length = 100
src = ["app", "tests"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = ["E501", "B008", "B904"]
[tool.ruff.lint.isort]
known-first-party = ["app"]
# ─── MyPy ─────────────────────────────────────────────────────────────────────
[tool.mypy]
python_version = "3.12"
strict = false
ignore_missing_imports = true
plugins = ["pydantic.mypy"]
# ─── Pytest ───────────────────────────────────────────────────────────────────
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
filterwarnings = [
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning",
]
# ─── Coverage ─────────────────────────────────────────────────────────────────
[tool.coverage.run]
source = ["app"]
omit = ["tests/*", "alembic/*"]
[tool.coverage.report]
show_missing = true
fail_under = 70