fix(settings): valida csv_delimiter/decimal_separator/auto_logout_minutes (#23)
PUT /api/settings/ non validava nulla: un csv_delimiter vuoto o di piu' caratteri arrivava intatto nel DB e faceva fallire csv.writer con TypeError dentro close_run (500, run mai chiuso). Aggiunta validazione per chiave nota (csv_delimiter 1 carattere fra , ; TAB |, csv_decimal_separator ',' o '.', auto_logout_minutes vuoto o intero 1-480, company_name/company_logo_path non vuoti) -> 422 sui valori fuori dominio. Chiavi non note passano senza validazione (store generico). Difesa in profondita' in production_export_service._csv_format: un csv_delimiter gia' sporco nel DB (scritto prima di questa validazione) non fa piu' fallire close_run, usa ';' e logga un WARNING. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,42 @@ from src.backend.models.orm.user import User
|
||||
|
||||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||
|
||||
# Domains for the settings keys the backend actually reads. Values outside these
|
||||
# would only fail later (csv.writer TypeError on close_run, etc) — reject up front.
|
||||
_VALID_CSV_DELIMITERS = {",", ";", "\t", "|"}
|
||||
_VALID_CSV_DECIMAL_SEPARATORS = {",", "."}
|
||||
|
||||
|
||||
def _validate_setting_value(key: str, value: str) -> None:
|
||||
"""Raise 422 for a value that would break the code reading this key.
|
||||
|
||||
Unrecognised keys pass through unvalidated: the settings store is generic.
|
||||
"""
|
||||
if key == "csv_delimiter":
|
||||
if value not in _VALID_CSV_DELIMITERS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="csv_delimiter deve essere un carattere singolo fra ',' ';' TAB '|'",
|
||||
)
|
||||
elif key == "csv_decimal_separator":
|
||||
if value not in _VALID_CSV_DECIMAL_SEPARATORS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="csv_decimal_separator deve essere ',' o '.'",
|
||||
)
|
||||
elif key == "auto_logout_minutes":
|
||||
if value != "" and (not value.isdigit() or not (1 <= int(value) <= 480)):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="auto_logout_minutes deve essere vuoto (disabilitato) o un intero fra 1 e 480",
|
||||
)
|
||||
elif key in ("company_name", "company_logo_path"):
|
||||
if not value.strip() or len(value) > 200:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"{key} deve essere una stringa non vuota di massimo 200 caratteri",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def get_settings(
|
||||
@@ -49,6 +85,8 @@ async def update_settings(
|
||||
updated_keys = []
|
||||
|
||||
for key, value in settings_data.items():
|
||||
_validate_setting_value(key, value)
|
||||
|
||||
# Check if setting exists
|
||||
result = await db.execute(
|
||||
select(SystemSetting).where(SystemSetting.setting_key == key)
|
||||
|
||||
Reference in New Issue
Block a user