mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
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:
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Configurazione applicazione – legge variabili d'ambiente tramite pydantic-settings.
|
||||
"""
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# ── Applicazione ──────────────────────────────────────────────────────────
|
||||
app_env: Literal["development", "staging", "production"] = "development"
|
||||
app_debug: bool = True
|
||||
app_host: str = "0.0.0.0"
|
||||
app_port: int = 8000
|
||||
app_base_url: str = "http://localhost:8000"
|
||||
|
||||
# ── Sicurezza / JWT ───────────────────────────────────────────────────────
|
||||
secret_key: str = "change-me-in-production"
|
||||
algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 15
|
||||
refresh_token_expire_days: int = 30
|
||||
|
||||
# Chiave AES-256-GCM per cifratura credenziali IMAP/SMTP (hex 64 chars = 32 bytes)
|
||||
encryption_key: str = "0" * 64
|
||||
|
||||
# ── Database ──────────────────────────────────────────────────────────────
|
||||
database_url: str = "postgresql+asyncpg://pecflow:pecflow_dev_password@db:5432/pecflow"
|
||||
database_url_sync: str = "postgresql://pecflow:pecflow_dev_password@db:5432/pecflow"
|
||||
|
||||
# ── Redis ─────────────────────────────────────────────────────────────────
|
||||
redis_url: str = "redis://redis:6379/0"
|
||||
|
||||
# ── MinIO ─────────────────────────────────────────────────────────────────
|
||||
minio_endpoint: str = "minio:9000"
|
||||
minio_access_key: str = "minioadmin"
|
||||
minio_secret_key: str = "minioadmin"
|
||||
minio_bucket: str = "pecflow"
|
||||
minio_use_ssl: bool = False
|
||||
|
||||
# ── CORS ──────────────────────────────────────────────────────────────────
|
||||
cors_origins: str = "http://localhost:3000,http://localhost:5173"
|
||||
|
||||
# ── Rate Limiting ─────────────────────────────────────────────────────────
|
||||
rate_limit_auth: str = "10/minute"
|
||||
rate_limit_default: str = "100/minute"
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────────
|
||||
log_level: str = "INFO"
|
||||
log_json: bool = False
|
||||
|
||||
@field_validator("encryption_key")
|
||||
@classmethod
|
||||
def validate_encryption_key(cls, v: str) -> str:
|
||||
if len(v) != 64:
|
||||
raise ValueError(
|
||||
"ENCRYPTION_KEY deve essere una stringa hex di 64 caratteri (32 bytes)"
|
||||
)
|
||||
try:
|
||||
bytes.fromhex(v)
|
||||
except ValueError:
|
||||
raise ValueError("ENCRYPTION_KEY deve essere una stringa esadecimale valida")
|
||||
return v
|
||||
|
||||
@property
|
||||
def cors_origins_list(self) -> list[str]:
|
||||
return [origin.strip() for origin in self.cors_origins.split(",")]
|
||||
|
||||
@property
|
||||
def is_production(self) -> bool:
|
||||
return self.app_env == "production"
|
||||
|
||||
@property
|
||||
def encryption_key_bytes(self) -> bytes:
|
||||
return bytes.fromhex(self.encryption_key)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
"""Restituisce istanza singleton delle impostazioni (cachata)."""
|
||||
return Settings()
|
||||
Reference in New Issue
Block a user