refactor: v0.5.3 - Zugangsdaten in .env auslagern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-16 20:09:48 +01:00
parent 7e441a8ade
commit ee361acf33
5 changed files with 77 additions and 75 deletions

7
.env.example Normal file
View file

@ -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 <noreply@example.com>
SMTP_APP_URL=http://localhost:8080

View file

@ -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

View file

@ -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 <noreply@example.com>"
app_url: "http://localhost:8080"

View file

@ -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 <noreply@example.com>")
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"""<html><body>
@ -98,43 +134,14 @@ async def send_verification_email(db, email: str, token: str):
<p>Der Link ist 24 Stunden gueltig.</p>
</body></html>"""
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 <noreply@example.com>")
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"""<html><body>
@ -144,39 +151,10 @@ async def send_reset_email(db, email: str, token: str):
<p>Der Link ist 24 Stunden gueltig.</p>
</body></html>"""
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 <noreply@example.com>")
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 ──────────────────────────────

View file

@ -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()