Refactor configuration and security features for improved development experience

- Updated the .env.example file to reflect a development environment setup with enhanced secret key requirements and local host settings.
- Modified the Docker Compose configuration to enhance security with read-only settings and no-new-privileges options.
- Updated requirements.txt to pin package versions for better dependency management.
- Enhanced the FastAPI application to include dynamic OpenAPI and documentation URLs based on the environment.
- Implemented session versioning in JWT tokens to improve security and user session management.
- Added new validation for user roles and password strength in schemas.
- Improved email sending logic to handle recipient lists more robustly and added logging for SMTP operations.
- Updated dashboard and profile templates to reflect new features and improve user experience.
This commit is contained in:
smueller
2026-07-07 16:31:10 +02:00
parent d2df1d2bed
commit 7788c74cfe
22 changed files with 509 additions and 82 deletions

View File

@@ -73,11 +73,11 @@
</label>
</fieldset>
<label>Top-Highlights (optional)
<textarea name="highlights" rows="3" placeholder="Ein Artikeltitel pro Zeile muss exakt einem gefundenen Artikel entsprechen (max. 3)">{{ filters.highlights }}</textarea>
<textarea name="highlights" rows="3" maxlength="{{ highlights_max_length }}" placeholder="Ein Artikeltitel pro Zeile muss exakt einem gefundenen Artikel entsprechen (max. 3)">{{ filters.highlights }}</textarea>
</label>
<p class="hint">Hervorgehobene Artikel erscheinen als „Top Highlights“-Box oben im Newsletter.</p>
<label>Redaktionsnotiz (optional)
<textarea name="editor_tip" rows="3" placeholder="Optionaler Hinweis der Redaktion für diese Ausgabe…">{{ filters.editor_tip }}</textarea>
<textarea name="editor_tip" rows="3" maxlength="{{ editor_tip_max_length }}" placeholder="Optionaler Hinweis der Redaktion für diese Ausgabe…">{{ filters.editor_tip }}</textarea>
</label>
<p class="hint">Wird nur angezeigt, wenn Text hinterlegt ist erscheint als eigene Box im Newsletter.</p>
{% if wiki_error %}
@@ -122,7 +122,11 @@
<p class="hint" style="margin:0 0 0.75rem;">Bitte oben die Vorschau prüfen. Beim Versenden wird genau dieser Newsletter (aktuelle Filter) an die Verteilerliste geschickt.</p>
<p class="hint" style="margin:0 0 0.75rem;">
<strong>Betreff:</strong> {{ subject }}<br>
{% if can_view_sensitive %}
<strong>Empfänger:</strong> {{ smtp.recipients if smtp.recipients else "— keine konfiguriert —" }}
{% else %}
<strong>Empfänger:</strong> Nur für Editor/Admin sichtbar
{% endif %}
{% if smtp.enabled != "true" %}<br><strong style="color:var(--tk-orange-dark);">SMTP ist deaktiviert</strong> bitte zuerst in „SMTP-Konfiguration“ aktivieren.{% endif %}
</p>
{% if user.role in ["admin", "editor"] %}
@@ -250,7 +254,7 @@
<input type="text" name="username" value="{{ smtp.username }}">
</label>
<label>Passwort
<input type="password" name="password" value="{{ smtp.password }}">
<input type="password" name="password" autocomplete="new-password" placeholder="{% if smtp.password_configured == 'true' %}•••••••• (unverändert lassen){% else %}SMTP-Passwort{% endif %}">
</label>
</div>
<label>Absender-Name
@@ -278,7 +282,11 @@
<input type="checkbox" name="schedule_enabled" value="true" {% if smtp.schedule_enabled == "true" %}checked{% endif %}>
Automatischen Versand aktivieren
</label>
<p class="hint" style="margin:0 0 0.6rem;">Ausschalten, wenn du vor dem Versenden noch Redaktionsnotiz oder Top-Highlights setzen möchtest dann manuell über „Newsletter jetzt senden“.</p>
<label class="checkbox">
<input type="checkbox" name="schedule_acknowledged" value="true" {% if smtp.schedule_acknowledged == "true" %}checked{% endif %}>
Ich bestätige den automatischen Versand ohne manuelle Freigabe (ohne Redaktionsnotiz/Highlights)
</label>
<p class="hint" style="margin:0 0 0.6rem;">Beide Häkchen sind nötig, damit der Zeitplan greift. Für manuelle Kontrolle nur den ersten Schalter nutzen und „Newsletter jetzt senden“ verwenden.</p>
<div class="form-row">
<label>Häufigkeit
<select name="schedule_frequency">
@@ -339,6 +347,7 @@
</details>
{% endif %}
{% if can_view_sensitive %}
<section class="card">
<div class="card-header">
<h3>Versandprotokoll</h3>
@@ -365,6 +374,7 @@
</table>
</div>
</section>
{% endif %}
</div>

View File

@@ -48,7 +48,7 @@
</label>
<div class="form-row">
<label>Neues Passwort
<input type="password" name="new_password" required minlength="10" autocomplete="new-password" placeholder="Mindestens 10 Zeichen">
<input type="password" name="new_password" required minlength="10" autocomplete="new-password" placeholder="Mind. 10 Zeichen, Groß/Klein/Ziffer">
</label>
<label>Neues Passwort bestätigen
<input type="password" name="confirm_password" required minlength="10" autocomplete="new-password">

View File

@@ -31,7 +31,7 @@
</div>
<div class="form-row">
<label>Passwort
<input type="password" name="password" required minlength="10" placeholder="Mindestens 10 Zeichen">
<input type="password" name="password" required minlength="10" placeholder="Mind. 10 Zeichen, Groß/Klein/Ziffer">
</label>
<label>Rolle
<select name="role">
@@ -52,15 +52,40 @@
<div class="table-wrap">
<table>
<thead>
<tr><th>E-Mail</th><th>Name</th><th>Rolle</th><th>Status</th></tr>
<tr><th>E-Mail</th><th>Name</th><th>Rolle</th><th>Status</th><th>Aktionen</th></tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>{{ u.email }}</td>
<td>{{ u.full_name }}</td>
<td><span class="badge badge-{{ u.role }}">{{ u.role }}</span></td>
<td>
{% if u.id == user.id %}
<span class="badge badge-{{ u.role }}">{{ u.role }}</span>
{% else %}
<form method="post" action="/admin/users/{{ u.id }}/role" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<select name="role" onchange="this.form.submit()">
<option value="reader" {% if u.role == 'reader' %}selected{% endif %}>reader</option>
<option value="editor" {% if u.role == 'editor' %}selected{% endif %}>editor</option>
<option value="admin" {% if u.role == 'admin' %}selected{% endif %}>admin</option>
</select>
</form>
{% endif %}
</td>
<td>{{ "Aktiv" if u.is_active else "Inaktiv" }}</td>
<td>
{% if u.id != user.id %}
<form method="post" action="/admin/users/{{ u.id }}/toggle-active" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button type="submit" class="btn-secondary btn-small">
{% if u.is_active %}Deaktivieren{% else %}Aktivieren{% endif %}
</button>
</form>
{% else %}
<span class="hint">Eigenes Konto</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
@@ -78,6 +103,12 @@
<li><strong>Admin</strong> Benutzer &amp; SMTP</li>
</ul>
</div>
<div class="sidebar-card">
<h4>Sicherheit</h4>
<ul>
<li>Rollen- oder Statusänderung beendet alle aktiven Sitzungen des Benutzers.</li>
</ul>
</div>
</aside>
</div>