From ee361acf3326fe2ff34ce66678e261194ea714ac Mon Sep 17 00:00:00 2001 From: ppfeiffer Date: Mon, 16 Feb 2026 20:09:48 +0100 Subject: [PATCH] refactor: v0.5.3 - Zugangsdaten in .env auslagern Co-Authored-By: Claude Opus 4.6 --- .env.example | 7 +++ CHANGELOG.md | 7 +++ config.yaml | 11 +---- meshbot/auth.py | 108 ++++++++++++++++++---------------------------- meshbot/config.py | 19 ++++++++ 5 files changed, 77 insertions(+), 75 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1f5a2ff --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +AUTH_SECRET_KEY=change-this-secret-key-32bytes!! +SMTP_HOST= +SMTP_PORT=465 +SMTP_USER= +SMTP_PASSWORD= +SMTP_FROM=MeshDD-Bot +SMTP_APP_URL=http://localhost:8080 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cf3d63..3e85fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.5.3] - 2026-02-16 +### Changed +- Zugangsdaten (AUTH_SECRET_KEY, SMTP-*) aus config.yaml in .env-Datei ausgelagert +- Neuer `config.env()` Helper fuer Umgebungsvariablen +- `.env.example` als Vorlage hinzugefuegt +- E-Mail-Versand in gemeinsame `_send_email()` Hilfsfunktion refaktoriert + ## [0.5.2] - 2026-02-16 ### Fixed - SMTP-Versand: TLS (Port 465) und STARTTLS (Port 587) automatisch anhand des Ports diff --git a/config.yaml b/config.yaml index 4cf6254..22df586 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,4 @@ -version: "0.5.1" +version: "0.5.3" bot: name: "MeshDD-Bot" @@ -16,13 +16,4 @@ database: path: "meshdd.db" auth: - secret_key: "change-this-secret-key-32bytes!!" session_max_age: 86400 - -smtp: - host: "" - port: 587 - user: "" - password: "" - from: "MeshDD-Bot " - app_url: "http://localhost:8080" diff --git a/meshbot/auth.py b/meshbot/auth.py index 4a12571..9e9ac30 100644 --- a/meshbot/auth.py +++ b/meshbot/auth.py @@ -27,7 +27,7 @@ def check_password(password: str, hashed: str) -> bool: # ── Session setup ──────────────────────────────────── def setup_session(app: web.Application): - secret_key = config.get("auth.secret_key", "change-this-secret-key-32bytes!!") + secret_key = config.env("AUTH_SECRET_KEY", "change-this-secret-key-32bytes!!") # EncryptedCookieStorage accepts a Fernet object directly key_bytes = secret_key.encode("utf-8")[:32].ljust(32, b"\0") fernet_key = Fernet(base64.urlsafe_b64encode(key_bytes)) @@ -87,8 +87,44 @@ def require_admin_api(request: web.Request): # ── Email sending ──────────────────────────────────── +async def _send_email(db, recipient: str, subject: str, html_body: str): + smtp_host = config.env("SMTP_HOST") + if not smtp_host: + logger.info("SMTP not configured - email to %s not sent", recipient) + await db.log_email(recipient, subject, "console", "SMTP not configured") + return + + try: + import aiosmtplib + from email.mime.text import MIMEText + from email.mime.multipart import MIMEMultipart + + msg = MIMEMultipart("alternative") + msg["Subject"] = subject + msg["From"] = config.env("SMTP_FROM", "MeshDD-Bot ") + msg["To"] = recipient + msg.attach(MIMEText(html_body, "html")) + + smtp_port = int(config.env("SMTP_PORT", "465")) + use_tls = smtp_port == 465 + await aiosmtplib.send( + msg, + hostname=smtp_host, + port=smtp_port, + username=config.env("SMTP_USER"), + password=config.env("SMTP_PASSWORD"), + use_tls=use_tls, + start_tls=not use_tls, + ) + await db.log_email(recipient, subject, "sent") + logger.info("Email sent to %s: %s", recipient, subject) + except Exception as e: + logger.error("Failed to send email to %s: %s", recipient, e) + await db.log_email(recipient, subject, "error", str(e)) + + async def send_verification_email(db, email: str, token: str): - app_url = config.get("smtp.app_url", "http://localhost:8080") + app_url = config.env("SMTP_APP_URL", "http://localhost:8080") verify_url = f"{app_url}/auth/verify?token={token}" subject = "MeshDD-Bot - E-Mail verifizieren" html_body = f""" @@ -98,43 +134,14 @@ async def send_verification_email(db, email: str, token: str):

Der Link ist 24 Stunden gueltig.

""" - smtp_host = config.get("smtp.host", "") - if not smtp_host: + if not config.env("SMTP_HOST"): logger.info("SMTP not configured - verification link: %s", verify_url) - await db.log_email(email, subject, "console", "SMTP not configured") - return - try: - import aiosmtplib - from email.mime.text import MIMEText - from email.mime.multipart import MIMEMultipart - - msg = MIMEMultipart("alternative") - msg["Subject"] = subject - msg["From"] = config.get("smtp.from", "MeshDD-Bot ") - msg["To"] = email - msg.attach(MIMEText(html_body, "html")) - - smtp_port = config.get("smtp.port", 587) - use_tls = smtp_port == 465 - await aiosmtplib.send( - msg, - hostname=smtp_host, - port=smtp_port, - username=config.get("smtp.user", ""), - password=config.get("smtp.password", ""), - use_tls=use_tls, - start_tls=not use_tls, - ) - await db.log_email(email, subject, "sent") - logger.info("Verification email sent to %s", email) - except Exception as e: - logger.error("Failed to send email to %s: %s", email, e) - await db.log_email(email, subject, "error", str(e)) + await _send_email(db, email, subject, html_body) async def send_reset_email(db, email: str, token: str): - app_url = config.get("smtp.app_url", "http://localhost:8080") + app_url = config.env("SMTP_APP_URL", "http://localhost:8080") reset_url = f"{app_url}/auth/reset-password?token={token}" subject = "MeshDD-Bot - Passwort zuruecksetzen" html_body = f""" @@ -144,39 +151,10 @@ async def send_reset_email(db, email: str, token: str):

Der Link ist 24 Stunden gueltig.

""" - smtp_host = config.get("smtp.host", "") - if not smtp_host: + if not config.env("SMTP_HOST"): logger.info("SMTP not configured - reset link: %s", reset_url) - await db.log_email(email, subject, "console", "SMTP not configured") - return - try: - import aiosmtplib - from email.mime.text import MIMEText - from email.mime.multipart import MIMEMultipart - - msg = MIMEMultipart("alternative") - msg["Subject"] = subject - msg["From"] = config.get("smtp.from", "MeshDD-Bot ") - msg["To"] = email - msg.attach(MIMEText(html_body, "html")) - - smtp_port = config.get("smtp.port", 587) - use_tls = smtp_port == 465 - await aiosmtplib.send( - msg, - hostname=smtp_host, - port=smtp_port, - username=config.get("smtp.user", ""), - password=config.get("smtp.password", ""), - use_tls=use_tls, - start_tls=not use_tls, - ) - await db.log_email(email, subject, "sent") - logger.info("Reset email sent to %s", email) - except Exception as e: - logger.error("Failed to send reset email to %s: %s", email, e) - await db.log_email(email, subject, "error", str(e)) + await _send_email(db, email, subject, html_body) # ── Auth route handlers ────────────────────────────── diff --git a/meshbot/config.py b/meshbot/config.py index 2d2ceaf..cde629b 100644 --- a/meshbot/config.py +++ b/meshbot/config.py @@ -7,12 +7,26 @@ import yaml logger = logging.getLogger(__name__) CONFIG_PATH = os.environ.get("CONFIG_PATH", os.path.join(os.path.dirname(os.path.dirname(__file__)), "config.yaml")) +ENV_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".env") _config = {} _mtime = 0.0 _callbacks = [] +def _load_env(): + if not os.path.exists(ENV_PATH): + return + with open(ENV_PATH, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, value = line.partition("=") + os.environ.setdefault(key.strip(), value.strip()) + logger.info("Environment loaded from %s", ENV_PATH) + + def _load(): global _config, _mtime with open(CONFIG_PATH, "r") as f: @@ -61,5 +75,10 @@ def get(key: str, default=None): return val +def env(key: str, default: str = "") -> str: + return os.environ.get(key, default) + + # Load on import +_load_env() _load()