Files
kguerineau b12ebea0e8 refactor: refactorisation, validation et corrections mail/DNS
Refactorisation
- Hook useApi centralisé (hooks/useApi.js) — supprime 200 lignes dupliquées
- Helpers backend : get_or_404, _sync_domain_from_state, _sync_zone_from_state, _stable_id
- Constantes de rôles RBAC dans auth.py (ROLE_GLOBAL_ADMIN, etc.)
- handle_apply_domain découpé en _sync_domain_row / _sync_mailboxes / _sync_aliases (169→32 lignes)
- oidc_callback découpé en _exchange_oidc_token / _provision_oidc_user (159→54 lignes)
- Extraction composants : AccountModal, AliasModal, DnssecResultModal, DnssecManageModal
- Suppression MailingLists.jsx (inutilisé)

Corrections
- Notification de connexion différée après validation TOTP
- Création domaine : validation complète avant tout appel API (quota bal > quota global bloqué)
- Création domaine : service Mail non tenté si création DNS échoue
- Alias goto : validation email bloquante (frontend + backend)
- Alias : vérification conflit avec compte mail existant (409)
- delete_domain : notifie l'agent mail pour suppression dans netadmin_mail
- Clés DNSSEC archivées dans key_dir/old/ avant activation et à la suppression de zone
- _get_ds_records : exclut le dossier old/ (évitait la lecture de doublons)
- Modale TOTP : remplace window.confirm par une vraie modale
- "Mon compte" accessible sans rôle
- Suppression champ "Serveur API" de la sidebar
2026-04-24 15:36:29 +02:00

178 lines
5.0 KiB
Python

"""
config.py — Chargement centralisé de la configuration NetAdmin
"""
import os
import secrets
from pathlib import Path
from dataclasses import dataclass, field
from typing import List
try:
import yaml
_HAS_YAML = True
except ImportError:
_HAS_YAML = False
print("[WARN] PyYAML not installed — using defaults. Run: pip install pyyaml")
@dataclass
class ServerConfig:
host: str = "0.0.0.0"
port: int = 8000
cors_origins: List[str] = field(default_factory=lambda: ["*"])
behind_proxy: bool = False
@dataclass
class RedisTLSConfig:
enabled: bool = True
ca_cert: str = "/etc/netadmin/certs/ca.crt"
client_cert: str = "/etc/netadmin/certs/backend.crt"
client_key: str = "/etc/netadmin/certs/backend.key"
check_hostname: bool = True
@dataclass
class RedisConfig:
host: str = "localhost"
port: int = 6380
password: str = ""
db: int = 0
ack_timeout: int = 10
tls: RedisTLSConfig = field(default_factory=RedisTLSConfig)
@dataclass
class SmtpConfig:
enabled: bool = False
host: str = "localhost"
port: int = 587
username: str = ""
password: str = ""
from_addr: str = "netadmin@localhost"
from_name: str = "NetAdmin"
use_tls: bool = True # STARTTLS
use_ssl: bool = False # SSL/TLS direct (port 465)
# Notification triggers
notify_login: bool = True # send email on successful login
notify_role_added: bool = True # send email when a role is assigned
@dataclass
class SyncConfig:
interval: int = 300 # seconds between polls, 0 = disabled
@dataclass
class DnsConfig:
default_ns: list = field(default_factory=lambda: ["ns1.infolix.fr", "ns2.infolix.fr"])
default_soa_admin: str = "hostmaster"
dnssec_enabled: bool = False # whether DNSSEC signing is available on the agent
keygen_cmd: str = "dnssec-keygen"
key_dir: str = "/etc/bind/keys"
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 3306
user: str = "netadmin"
password: str = ""
name: str = "netadmin"
@dataclass
class MailConfig:
pass # mail data stored in shared netadmin.db
@dataclass
class OidcConfig:
enabled: bool = False
issuer: str = ""
client_id: str = ""
client_secret: str = ""
scopes: List[str] = field(default_factory=lambda: ["openid", "email", "profile"])
auto_provision: bool = True
redirect_uri: str = "http://localhost:3000/auth/callback"
use_pkce: bool = True
admin_group: str = ""
@dataclass
class AuthConfig:
session_secret: str = ""
session_ttl_hours: int = 8
oidc: OidcConfig = field(default_factory=OidcConfig)
# db_path removed — auth tables live in the shared netadmin.db
@dataclass
class AppConfig:
server: ServerConfig = field(default_factory=ServerConfig)
redis: RedisConfig = field(default_factory=RedisConfig)
sync: SyncConfig = field(default_factory=SyncConfig)
smtp: SmtpConfig = field(default_factory=SmtpConfig)
dns: DnsConfig = field(default_factory=DnsConfig)
mail_default_aliases: dict = field(default_factory=lambda: {
"abuse": "postmaster@localhost",
"hostmaster": "postmaster@localhost",
"postmaster": "admin@localhost",
"webmaster": "admin@localhost",
})
database: DatabaseConfig = field(default_factory=DatabaseConfig)
mail: MailConfig = field(default_factory=MailConfig)
auth: AuthConfig = field(default_factory=AuthConfig)
def _merge(obj, data: dict):
if not isinstance(data, dict):
return
for key, value in data.items():
if not hasattr(obj, key):
continue
current = getattr(obj, key)
if hasattr(current, '__dataclass_fields__'):
_merge(current, value)
else:
setattr(obj, key, value)
def _find_config_file():
for path in [
os.environ.get("NETADMIN_CONFIG", ""),
"/etc/netadmin/config.yaml",
str(Path(__file__).parent / "config.yaml"),
]:
if path and Path(path).exists():
return Path(path)
return None
def _load() -> AppConfig:
config = AppConfig()
config_file = _find_config_file()
if config_file is None:
print("[INFO] No config.yaml found — using defaults")
return _finalise(config)
print(f"[INFO] Loading configuration from: {config_file}")
if not _HAS_YAML:
return _finalise(config)
try:
with open(config_file) as f:
raw = yaml.safe_load(f) or {}
_merge(config, raw)
except Exception as e:
print(f"[ERROR] Failed to parse {config_file}: {e}")
return _finalise(config)
def _finalise(config: AppConfig) -> AppConfig:
if not config.auth.session_secret:
config.auth.session_secret = secrets.token_hex(32)
print("[INFO] session_secret not set — generated a random one (sessions won't survive restarts)")
return config
cfg: AppConfig = _load()