OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

health.php

9 KB
<?php $this->layout('layouts/main', ['page_title' => $page_title, 'current_route' => $current_route, 'full_width' => true]);
$weak = $weak ?? []; $reusedGroups = $reused_groups ?? []; $old = $old ?? []; $expiring = $expiring ?? [];
$total = $total ?? 0; $hibpEnabled = $hibp_enabled ?? false; $isVaultAdmin = $is_vault_admin ?? false;
$reusedCount = array_sum(array_map('count', $reusedGroups));
?>
<style>
a, a:hover { text-decoration:none; }
.vh-wrap { max-width:1000px; margin:0 auto; padding:1rem 1.5rem 3rem; }
.vh-list { margin:0; padding:0; list-style:none; }
.vh-list li { padding:0.45rem 0.2rem; border-bottom:1px solid var(--gray-100); font-size:0.85rem; display:flex; gap:0.6rem; align-items:center; }
.vh-scope { font-size:0.7rem; color:var(--gray-400); background:var(--gray-100); border-radius:3px; padding:0.1rem 0.35rem; flex-shrink:0; }
.vh-group { border:1px solid var(--amber-200); background:var(--amber-50); border-radius:0.5rem; padding:0.5rem 0.75rem; margin-bottom:0.5rem; font-size:0.85rem; }
</style>

<div class="content-header">
    <div>
        <h1><?= $this->te('Passwort-Gesundheit') ?></h1>
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
            <li class="breadcrumb-item"><a href="/vault"><?= $this->te('Passwörter') ?></a></li>
            <li class="breadcrumb-item active"><?= $this->te('Gesundheit') ?></li>
        </ol>
    </div>
    <div class="content-header-actions">
        <a href="/vault" class="btn btn-default btn-sm"><i class="fas fa-arrow-left"></i> <?= $this->te('Zurück zum Tresor') ?></a>
    </div>
</div>

<div class="vh-wrap">
    <div class="kpi-row">
        <div class="kpi-col"><div class="small-box bg-info"><div class="inner"><h3><?= (int)$total ?></h3><p><?= $this->te('Einträge geprüft') ?></p></div><div class="icon"><i class="fas fa-key"></i></div></div></div>
        <div class="kpi-col"><div class="small-box <?= count($weak) ? 'bg-danger' : 'bg-success' ?>"><div class="inner"><h3><?= count($weak) ?></h3><p><?= $this->te('Schwache Passwörter') ?></p></div><div class="icon"><i class="fas fa-unlock"></i></div></div></div>
        <div class="kpi-col"><div class="small-box <?= $reusedCount ? 'bg-warning' : 'bg-success' ?>"><div class="inner"><h3><?= (int)$reusedCount ?></h3><p><?= $this->te('Mehrfach verwendet') ?></p></div><div class="icon"><i class="fas fa-clone"></i></div></div></div>
        <div class="kpi-col"><div class="small-box <?= count($old) ? 'bg-warning' : 'bg-success' ?>"><div class="inner"><h3><?= count($old) ?></h3><p><?= $this->te('Älter als 1 Jahr') ?></p></div><div class="icon"><i class="fas fa-hourglass-half"></i></div></div></div>
        <div class="kpi-col"><div class="small-box <?= count($expiring) ? 'bg-danger' : 'bg-success' ?>"><div class="inner"><h3><?= count($expiring) ?></h3><p><?= $this->te('Abgelaufen / läuft ab') ?></p></div><div class="icon"><i class="fas fa-calendar-times"></i></div></div></div>
    </div>

    <div class="detail-card">
        <div class="detail-card-header"><span class="detail-card-title"><i class="fas fa-shield-alt"></i> <?= $this->te('Leak-Prüfung (Have I Been Pwned)') ?></span></div>
        <div class="detail-card-body">
            <p style="font-size:0.83rem;color:var(--gray-500);margin:0 0 0.7rem;">
                <?= $this->te('Prüft anonymisiert (k-Anonymity: es verlassen nur die ersten 5 Zeichen des SHA-1-Hashes das System), ob Passwörter in bekannten Datenlecks auftauchen.') ?>
            </p>
            <?php if ($hibpEnabled): ?>
                <button type="button" class="btn btn-primary btn-sm" id="hibpBtn" data-act="vhRunHibp"><i class="fas fa-search"></i> <?= $this->te('Jetzt prüfen') ?></button>
                <div id="hibpResult" style="margin-top:0.8rem;"></div>
            <?php else: ?>
                <p style="font-size:0.83rem;color:var(--amber-800);background:var(--amber-100);border:1px solid var(--amber-300);border-radius:0.4rem;padding:0.5rem 0.75rem;">
                    <?= $this->te('Die Leak-Prüfung ist deaktiviert (es würde eine externe Anfrage an api.pwnedpasswords.com gestellt).') ?>
                </p>
            <?php endif; ?>
            <?php if ($isVaultAdmin): ?>
            <form method="POST" action="/vault/health/hibp-setting" style="margin-top:0.8rem;">
                <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
                <input type="hidden" name="enabled" value="<?= $hibpEnabled ? '0' : '1' ?>">
                <button type="submit" class="btn btn-default btn-xs">
                    <i class="fas fa-cog"></i> <?= $this->t('Leak-Prüfung') ?> <?= $hibpEnabled ? 'deaktivieren' : 'aktivieren' ?>
                </button>
            </form>
            <?php endif; ?>
        </div>
    </div>

    <?php
    $sections = [
        ['Schwache Passwörter (kurz oder zu wenige Zeichenklassen)', $weak, 'fa-unlock'],
        ['Älter als 1 Jahr (seitdem nicht geändert)', $old, 'fa-hourglass-half'],
        ['Abgelaufen oder läuft in 14 Tagen ab', $expiring, 'fa-calendar-times'],
    ];
    foreach ($sections as [$label, $list, $icon]): if (empty($list)) continue; ?>
    <div class="detail-card">
        <div class="detail-card-header"><span class="detail-card-title"><i class="fas <?= $icon ?>"></i> <?= $this->e($label) ?> (<?= count($list) ?>)</span></div>
        <div class="detail-card-body">
            <ul class="vh-list">
                <?php foreach ($list as $e): ?>
                <li>
                    <span class="vh-scope"><?= $this->e($e['_scope'] ?? '') ?></span>
                    <span><?= $this->e($e['title'] ?? '') ?></span>
                    <?php if (!empty($e['expires_at'])): ?><span style="color:var(--gray-400);font-size:0.75rem;"><?= $this->te('(Ablauf: {datum})', ['datum' => date('d.m.Y', strtotime((string)$e['expires_at']))]) ?></span><?php endif; ?>
                </li>
                <?php endforeach; ?>
            </ul>
        </div>
    </div>
    <?php endforeach; ?>

    <?php if (!empty($reusedGroups)): ?>
    <div class="detail-card">
        <div class="detail-card-header"><span class="detail-card-title"><i class="fas fa-clone"></i> <?= $this->te('Mehrfach verwendete Passwörter ({anzahl} Gruppen)', ['anzahl' => count($reusedGroups)]) ?></span></div>
        <div class="detail-card-body">
            <?php foreach ($reusedGroups as $group): ?>
            <div class="vh-group">
                <strong><?= $this->te('Gleiches Passwort bei {anzahl} Einträgen:', ['anzahl' => count($group)]) ?></strong>
                <?= $this->e(implode(', ', array_map(fn($e) => ($e['title'] ?? '') . ' (' . ($e['_scope'] ?? '') . ')', $group))) ?>
            </div>
            <?php endforeach; ?>
        </div>
    </div>
    <?php endif; ?>
</div>

<script<?= $this->scriptNonce() ?>>
function vhRunHibp() {
    var btn = document.getElementById('hibpBtn');
    var out = document.getElementById('hibpResult');
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Prüfe…';
    fetch('/vault/health/hibp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' },
        body: '_csrf=' + encodeURIComponent(<?= json_encode($this->csrf(), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP) ?>)
    })
    .then(function (r) { return r.json(); })
    .then(function (data) {
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-search"></i> Jetzt prüfen';
        if (!data.success) { out.textContent = data.message || 'Fehler.'; return; }
        if (!data.leaked.length) {
            out.innerHTML = '<div style="color:#15803d;font-size:0.85rem;">&#10003; Keines der ' + data.checked + ' geprüften Passwörter wurde in bekannten Leaks gefunden.</div>';
            return;
        }
        var html = '<div style="color:#b91c1c;font-size:0.85rem;font-weight:600;margin-bottom:0.4rem;">' + data.leaked.length + ' Passwort/Passwörter in bekannten Leaks gefunden – bitte umgehend ändern:</div><ul class="vh-list">';
        data.leaked.forEach(function (l) {
            var t = document.createElement('span'); t.textContent = l.titles.join(', ');
            html += '<li><span class="vh-scope">' + l.count.toLocaleString('de-DE') + '× geleakt</span> ' + t.innerHTML + '</li>';
        });
        out.innerHTML = html + '</ul>';
    })
    .catch(function () {
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-search"></i> Jetzt prüfen';
        out.textContent = 'Verbindungsfehler.';
    });
}
</script>
<?= $this->render('vault/_autolock', ['vault_idle_ttl' => $vault_idle_ttl ?? null]) ?>