mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 20:55:41 +02:00
58a233236c
- 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
91 lines
4.0 KiB
Python
91 lines
4.0 KiB
Python
"""
|
||
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()
|