M8: Security-Headers, Rate-Limits und Audit-Log
Erste drei Bausteine der Haertung: - Security-Headers (X-Content-Type-Options, X-Frame-Options, Referrer- Policy, Permissions-Policy, HSTS bei HTTPS) laufen automatisch ueber app_send_security_headers() am Ende von app/bootstrap.php fuer jede dynamische Seite; landing.php war als einzige Seite ganz ohne PHP und bekam einen minimalen Bootstrap-Aufruf. Bewusst kein CSP, da die bestehenden Templates durchgaengig auf Inline-style-Attribute setzen. - DB-gestuetzte Rate-Limits (neue Tabelle rate_limit_attempts) fuer Login (10/15min je E-Mail, 20/15min je IP), Registrierung (5/h je IP) und Passwort-Reset-Anfrage (5/h je E-Mail, 10/h je IP); bei ausgereiztem Reset-Limit erscheint dieselbe generische Meldung wie im Erfolgsfall, um kein Konto-Enumeration-Signal zu geben. - Zentrales Audit-Log (neue Tabelle audit_log) fuer Mitgliederverwaltung, Zugangsvergabe/-entzug, Storno, Mandant-Einstellungen, Hinweise, CSV-Import, Jahresbonus-Verteilung und Live-Mailversand; sichtbar fuer Owner/Admin auf mandant-einstellungen.php. Live getestet: Rate-Limit greift nach 10 Fehlversuchen, Audit-Log-Eintrag mit korrekten Metadaten und Nutzernamen ueber einen isolierten Test- Mandanten geprueft. Alle Regressionstests weiterhin gruen (26/26 Smoke, 104 Golden-Master-Assertions). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/functions.php';
|
||||
require_once __DIR__ . '/app/audit.php';
|
||||
|
||||
$pdo = app_db_pdo();
|
||||
$user = saas_require_login();
|
||||
@@ -16,6 +17,7 @@ if ($canManageSettings) {
|
||||
if ($result['ok']) {
|
||||
$saved = true;
|
||||
$settings = $result['settings'];
|
||||
app_audit_log($pdo, (int)$user['tenant_id'], (int)$user['user_id'], 'tenant_settings.updated', 'tenant', (int)$user['tenant_id']);
|
||||
} else {
|
||||
$errors = $result['errors'];
|
||||
$settings = [
|
||||
@@ -34,6 +36,8 @@ if ($canManageSettings) {
|
||||
} else {
|
||||
$settings = saas_fetch_tenant_settings($pdo, (int)$user['tenant_id']);
|
||||
}
|
||||
|
||||
$auditLog = app_fetch_audit_log($pdo, (int)$user['tenant_id'], 50);
|
||||
}
|
||||
|
||||
include 'header.php';
|
||||
@@ -112,6 +116,23 @@ include 'nav.php';
|
||||
<li><a href="konto.php" class="button alt">Zurück</a></li>
|
||||
</ul>
|
||||
</form>
|
||||
|
||||
<h3>Protokoll</h3>
|
||||
<p>Die letzten Admin-Aktionen für diesen Mandanten.</p>
|
||||
<table>
|
||||
<tr><th>Datum</th><th>Wer</th><th>Aktion</th><th>Betrifft</th></tr>
|
||||
<?php if ($auditLog === []): ?>
|
||||
<tr><td colspan="4">Noch keine protokollierten Aktionen.</td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($auditLog as $eintrag): ?>
|
||||
<tr>
|
||||
<td><?php echo saas_html($eintrag['created_at']); ?></td>
|
||||
<td><?php echo saas_html($eintrag['actor_name'] ?? '—'); ?></td>
|
||||
<td><?php echo saas_html($eintrag['action']); ?></td>
|
||||
<td><?php echo saas_html($eintrag['subject_type']); ?><?php echo $eintrag['subject_id'] !== null ? ' #' . (int)$eintrag['subject_id'] : ''; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user