Feature: APP_KEY Status & Generator in den Einstellungen
This commit is contained in:
parent
6d36fe7155
commit
df6dabf58b
4 changed files with 123 additions and 0 deletions
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-05-22 – Feature: APP_KEY Status & Generator in den Einstellungen
|
||||
|
||||
- Neuer Settings-Bereich unter dem Betriebsmodus zeigt Status des Verschlüsselungsschlüssels (`APP_KEY`): **Gesetzt** (grün, 64 Hex-Zeichen valide), **Ungültig** (falsches Format) oder **Fehlt** (leer)
|
||||
- Bei fehlendem oder ungültigem Schlüssel: Button „Neuen Schlüssel generieren" → POST `/settings/generate-app-key` liefert `bin2hex(random_bytes(32))`, Anzeige im Code-Block mit Copy-Button
|
||||
- Schlüssel wird nicht persistiert (Container hat keinen Schreibzugriff auf `.env`); Anleitung im UI: kopieren → in `.env` eintragen → `docker compose restart web`
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-22 – UI: Einheitliche Icon-Buttons für Bearbeiten/Ansehen/Löschen
|
||||
|
||||
- **Bleistift-Icon** (transparent, ohne Hintergrund – Stil der Direktverbindungs-Kacheln) ersetzt alle „Bearbeiten"- und „Öffnen"-Buttons in `accounts/list`, `customers/show`, `customers/list`, `suppliers/list`, `useradmin/list`, `assets/list`, `belege/list`, `invoices/list`, `invoices/show`
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ $router->post('/settings/mail-template', fn($p) => (new SettingsController())->m
|
|||
$router->post('/settings/mail-test', fn($p) => (new SettingsController())->mailTest($p));
|
||||
$router->post('/settings/logo', fn($p) => (new SettingsController())->uploadLogo($p));
|
||||
$router->post('/settings/logo/delete', fn($p) => (new SettingsController())->deleteLogo($p));
|
||||
$router->post('/settings/generate-app-key', fn($p) => (new SettingsController())->generateAppKey($p));
|
||||
$router->get('/settings/bankimport', fn($p) => (new SettingsController())->bankImport($p));
|
||||
$router->post('/settings/bankimport/suggest', fn($p) => (new SettingsController())->storeSuggestion($p));
|
||||
$router->post('/settings/bankimport/create', fn($p) => (new SettingsController())->createBankImportFormat($p));
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ class SettingsController extends BaseController
|
|||
View::redirect('/settings', 'Logo gelöscht.');
|
||||
}
|
||||
|
||||
public function generateAppKey(array $params): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['key' => bin2hex(random_bytes(32))]);
|
||||
}
|
||||
|
||||
public function bankImport(array $params): void
|
||||
{
|
||||
$formats = BankSource::all('csv');
|
||||
|
|
|
|||
|
|
@ -176,6 +176,71 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verschlüsselungs-Schlüssel (APP_KEY) -->
|
||||
<?php
|
||||
$appKey = (string)\Steuerkram\Config::get('app_key', '');
|
||||
$appKeyValid = $appKey !== '' && preg_match('/^[0-9a-f]{64}$/i', $appKey) === 1;
|
||||
$appKeyColor = $appKeyValid ? 'var(--color-success,#22c55e)' : 'var(--color-danger,#dc2626)';
|
||||
?>
|
||||
<div class="card mb-2" style="border:2px solid <?= $appKeyColor ?>">
|
||||
<div class="card-header">
|
||||
<div class="card-title">App-Verschlüsselungsschlüssel</div>
|
||||
<?php if ($appKeyValid): ?>
|
||||
<span class="badge badge-green">Gesetzt</span>
|
||||
<?php elseif ($appKey !== ''): ?>
|
||||
<span class="badge badge-red">Ungültig</span>
|
||||
<?php else: ?>
|
||||
<span class="badge badge-red">Fehlt</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-sm" style="margin:0 0 .75rem 0">
|
||||
<?php if ($appKeyValid): ?>
|
||||
<span style="color:#166534">✓ <code>APP_KEY</code> ist gültig (64-stellige Hex-Zeichenkette).</span><br>
|
||||
Wird zum Verschlüsseln sensibler Felder (PayPal-Signature, FinTS-PIN) genutzt.
|
||||
<strong>Niemals ändern</strong> – sonst sind alle bestehenden verschlüsselten Werte verloren.
|
||||
<?php elseif ($appKey !== ''): ?>
|
||||
<span style="color:#dc2626">✗ <code>APP_KEY</code> ist gesetzt, aber kein gültiges Format (erwartet: 64 Hex-Zeichen).</span><br>
|
||||
Bitte prüfen und korrigieren.
|
||||
<?php else: ?>
|
||||
<span style="color:#dc2626">✗ <code>APP_KEY</code> ist nicht gesetzt.</span><br>
|
||||
Ohne Schlüssel können keine Bank-Direktverbindungen mit PIN/Secret gespeichert werden.
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<?php if (!$appKeyValid): ?>
|
||||
<button type="button" id="btn-generate-app-key" class="btn btn-primary btn-sm">
|
||||
Neuen Schlüssel generieren
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="app-key-result" style="display:none;margin-top:.75rem">
|
||||
<div class="text-sm" style="margin-bottom:.4rem;font-weight:600">Neuer Schlüssel:</div>
|
||||
<div style="display:flex;gap:.5rem;align-items:stretch">
|
||||
<code id="app-key-value"
|
||||
style="flex:1;padding:.5rem .6rem;background:#f1f5f9;border:1px solid var(--color-border,#e2e8f0);border-radius:var(--radius);font-family:monospace;font-size:.8rem;word-break:break-all;user-select:all"></code>
|
||||
<button type="button" id="btn-copy-app-key" class="btn btn-secondary btn-sm" title="In Zwischenablage kopieren">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2"/>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm" style="margin-top:.75rem;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:var(--radius);padding:.6rem .75rem">
|
||||
<strong>So aktivierst du den Schlüssel:</strong>
|
||||
<ol style="margin:.4rem 0 0 1.2rem;padding:0">
|
||||
<li>Schlüssel oben kopieren.</li>
|
||||
<li>Auf dem Host in <code>.env</code> die Zeile <code>APP_KEY=...</code> mit dem neuen Wert ergänzen.</li>
|
||||
<li>Container neu starten: <code>docker compose restart web</code></li>
|
||||
</ol>
|
||||
Der Schlüssel wird <strong>nur einmal angezeigt</strong> und nirgends gespeichert.
|
||||
Sichere ihn auch im Backup mit – ohne ihn sind alle verschlüsselten Werte verloren.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KI-Kontierungsvorschlag -->
|
||||
<div class="card mb-2">
|
||||
<div class="card-header"><div class="card-title">KI-Kontierungsvorschlag</div></div>
|
||||
|
|
@ -544,4 +609,47 @@ document.getElementById('btn-mail-text-save')?.addEventListener('click', functio
|
|||
})
|
||||
.finally(function () { btn.disabled = false; });
|
||||
});
|
||||
|
||||
document.getElementById('btn-generate-app-key')?.addEventListener('click', function () {
|
||||
const btn = this;
|
||||
const result = document.getElementById('app-key-result');
|
||||
const out = document.getElementById('app-key-value');
|
||||
btn.disabled = true;
|
||||
fetch('/settings/generate-app-key', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data && data.key) {
|
||||
out.textContent = data.key;
|
||||
result.style.display = 'block';
|
||||
btn.style.display = 'none';
|
||||
} else {
|
||||
alert('Schlüssel konnte nicht erzeugt werden.');
|
||||
btn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
alert('Netzwerkfehler beim Erzeugen des Schlüssels.');
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('btn-copy-app-key')?.addEventListener('click', function () {
|
||||
const out = document.getElementById('app-key-value');
|
||||
const txt = out.textContent || '';
|
||||
if (!txt) return;
|
||||
const btn = this;
|
||||
const restore = btn.innerHTML;
|
||||
navigator.clipboard.writeText(txt)
|
||||
.then(() => {
|
||||
btn.textContent = '✓';
|
||||
setTimeout(() => { btn.innerHTML = restore; }, 1500);
|
||||
})
|
||||
.catch(() => {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(out);
|
||||
const sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue