modified: web/app.py

modified:   web/blueprints/auth.py
	modified:   web/blueprints/site_admin.py
	modified:   web/config.py
	modified:   web/panel_db.py
	modified:   web/templates/admin/audit_log.html
	modified:   web/templates/admin/dashboard.html
	new file:   web/templates/auth/consent.html
This commit is contained in:
simon
2026-04-15 11:05:21 +02:00
parent 179a0e1042
commit bdf83bd275
8 changed files with 333 additions and 2 deletions

View File

@@ -97,8 +97,10 @@
'admin.login': 'badge bg-warning text-dark',
'admin.login_failed': 'badge bg-danger',
'admin.view_users': 'badge bg-dark border border-secondary',
'admin.view_user': 'badge bg-dark border border-secondary',
'admin.view_group': 'badge bg-dark border border-secondary',
'admin.view_group_members': 'badge bg-dark border border-secondary',
'admin.view_audit_log': 'badge bg-dark border border-secondary',
'invite.created': 'badge bg-primary',
'invite.accepted': 'badge bg-success',
'invite.revoked': 'badge bg-secondary',
@@ -116,6 +118,8 @@
'user.deleted': 'badge bg-danger',
'mail.settings_saved': 'badge bg-info text-dark',
'mail.settings_deleted': 'badge bg-danger',
'consent.given': 'badge bg-success',
'consent.declined': 'badge bg-warning text-dark',
'audit.purged': 'badge bg-danger',
} %}
<span class="{{ action_class.get(row.action, 'badge bg-secondary') }} font-monospace" style="font-size:.75em">

View File

@@ -129,4 +129,112 @@
</div>
</div>
</div>
<!-- ── Recent Audit Activity ──────────────────────────────── -->
<div class="row g-3 mt-1">
<div class="col-12">
<div class="card border-secondary">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="bi bi-journal-text me-2"></i>Recent Audit Activity</span>
<div class="d-flex gap-2 align-items-center">
{% if retention_days > 0 %}
<span class="badge bg-secondary small">
<i class="bi bi-clock-history me-1"></i>Retention: {{ retention_days }}d
</span>
{% endif %}
<a href="{{ url_for('site_admin.audit_log') }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-arrow-right-circle me-1"></i>Full log
</a>
</div>
</div>
<div class="card-body p-0">
<table class="table table-dark table-hover table-sm align-middle mb-0">
<thead class="table-secondary text-dark">
<tr>
<th style="width:155px">Timestamp (UTC)</th>
<th style="width:130px">Actor</th>
<th style="width:180px">Action</th>
<th style="width:120px">Group</th>
<th>Details</th>
<th style="width:110px">IP Address</th>
</tr>
</thead>
<tbody>
{% for row in recent_audit %}
{% set action_class = {
'user.login': 'badge bg-success',
'user.login_failed': 'badge bg-danger',
'user.password_changed': 'badge bg-warning text-dark',
'session.logout': 'badge bg-secondary',
'admin.login': 'badge bg-warning text-dark',
'admin.login_failed': 'badge bg-danger',
'admin.view_users': 'badge bg-dark border border-secondary',
'admin.view_user': 'badge bg-dark border border-secondary',
'admin.view_group': 'badge bg-dark border border-secondary',
'admin.view_group_members': 'badge bg-dark border border-secondary',
'admin.view_audit_log': 'badge bg-dark border border-secondary',
'invite.created': 'badge bg-primary',
'invite.accepted': 'badge bg-success',
'invite.revoked': 'badge bg-secondary',
'invite.resent': 'badge bg-info text-dark',
'member.added': 'badge bg-primary',
'member.removed': 'badge bg-danger',
'member.role_changed': 'badge bg-warning text-dark',
'group.created': 'badge bg-success',
'group.updated': 'badge bg-secondary',
'group.deleted': 'badge bg-danger',
'db.credentials_changed': 'badge bg-warning text-dark',
'db.credentials_deleted': 'badge bg-danger',
'user.updated': 'badge bg-secondary',
'user.deleted': 'badge bg-danger',
'mail.settings_saved': 'badge bg-info text-dark',
'mail.settings_deleted': 'badge bg-danger',
'consent.given': 'badge bg-success',
'consent.declined': 'badge bg-warning text-dark',
'audit.purged': 'badge bg-danger',
} %}
<tr>
<td class="text-muted small">{{ row.created_at | fmt_dt }}</td>
<td>
{% if row.actor_username %}
<span class="text-info">{{ row.actor_username }}</span>
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td>
<span class="{{ action_class.get(row.action, 'badge bg-secondary') }} font-monospace" style="font-size:.75em">
{{ row.action }}
</span>
</td>
<td class="small">
{% if row.group_name %}
<span class="badge bg-dark border border-secondary">{{ row.group_name }}</span>
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td class="small text-muted font-monospace">
{% if row.details %}
{% set d = row.details if row.details is mapping else {} %}
{% for k, v in d.items() %}
<span class="me-2"><strong>{{ k }}:</strong> {{ v }}</span>
{% endfor %}
{% else %}—{% endif %}
</td>
<td class="text-muted small font-monospace">{{ row.ip_address or '—' }}</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted py-3">
<i class="bi bi-journal-x me-2"></i>No audit events yet.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCLogger Privacy Consent</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
body { background: #0d1117; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.consent-card { width: 100%; max-width: 600px; }
</style>
</head>
<body>
<div class="consent-card p-4">
<div class="text-center mb-4">
<i class="bi bi-shield-check fs-1 text-warning"></i>
<h3 class="fw-bold mt-2">Privacy Policy Consent</h3>
<p class="text-muted small">Version {{ policy_version }}</p>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for cat, msg in messages %}
<div class="alert alert-{{ cat }}">{{ msg }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="card border-warning mb-4">
<div class="card-header bg-transparent border-warning text-warning fw-semibold">
<i class="bi bi-file-earmark-text me-2"></i>What data we process
</div>
<div class="card-body small text-secondary">
<p>To operate MCLogger we process the following personal data:</p>
<ul class="mb-2">
<li><strong>Account data</strong> — username, e-mail address, hashed password (no plain-text storage)</li>
<li><strong>Session &amp; security data</strong> — login timestamps, IP addresses (stored for up to 90 days in the audit log)</li>
<li><strong>Minecraft server data</strong> — player names, UUIDs, chat messages, commands &amp; block interactions logged by the Minecraft plugin</li>
<li><strong>Audit events</strong> — records of actions you perform in the panel (logins, member changes, configuration edits)</li>
</ul>
<p class="mb-0">
<strong>Legal basis:</strong> Art. 6 (1)(b) GDPR — performance of a contract / provision of the service.<br>
<strong>Retention:</strong> Audit log entries containing IP addresses are automatically deleted after 90 days.
Account data is retained for as long as your account exists.
</p>
</div>
</div>
<div class="card border-secondary mb-4">
<div class="card-body small text-secondary">
<p class="mb-1">
<strong>Your rights (GDPR Art. 1521):</strong> You may request access to, rectification or deletion of your
personal data, as well as data portability, at any time by contacting
<a href="mailto:simon@devanturas.net" class="text-warning">simon@devanturas.net</a>.
</p>
<p class="mb-0">
Read the full <a href="{{ url_for('privacy_policy') }}" target="_blank" class="text-warning">Privacy Policy</a>.
</p>
</div>
</div>
<form method="post">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<div class="d-flex gap-3">
<button type="submit" name="action" value="accept" class="btn btn-warning w-100 fw-semibold">
<i class="bi bi-check-circle-fill me-1"></i>I accept the Privacy Policy
</button>
<button type="submit" name="action" value="decline"
class="btn btn-outline-secondary w-100"
onclick="return confirm('Declining will log you out. Are you sure?')">
<i class="bi bi-x-circle me-1"></i>Decline &amp; Logout
</button>
</div>
<p class="text-muted text-center mt-3 small">
By accepting you confirm that you have read and understood the Privacy Policy
(version {{ policy_version }}).
</p>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>