Merge pull request 'Feature: APP_KEY Status & Generator in den Einstellungen' (#82) from fds2610-bot/Steuerkram:main into main
Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/82
This commit is contained in:
commit
1efce29956
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
|
## 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`
|
- **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/mail-test', fn($p) => (new SettingsController())->mailTest($p));
|
||||||
$router->post('/settings/logo', fn($p) => (new SettingsController())->uploadLogo($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/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->get('/settings/bankimport', fn($p) => (new SettingsController())->bankImport($p));
|
||||||
$router->post('/settings/bankimport/suggest', fn($p) => (new SettingsController())->storeSuggestion($p));
|
$router->post('/settings/bankimport/suggest', fn($p) => (new SettingsController())->storeSuggestion($p));
|
||||||
$router->post('/settings/bankimport/create', fn($p) => (new SettingsController())->createBankImportFormat($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.');
|
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
|
public function bankImport(array $params): void
|
||||||
{
|
{
|
||||||
$formats = BankSource::all('csv');
|
$formats = BankSource::all('csv');
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,71 @@
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- KI-Kontierungsvorschlag -->
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
<div class="card-header"><div class="card-title">KI-Kontierungsvorschlag</div></div>
|
<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; });
|
.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>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue