feat(frontend): Geräte-ID-QR für Ed25519-Kiosk + TLS-Warnung bei Bearer
Security Audit / Python Dependency Audit (push) Canceled after 0s
Security Audit / Node.js Dependency Audit (push) Canceled after 0s
Security Audit / Frontend Build (tsc + vite) (push) Canceled after 0s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
patrickandClaude Sonnet 5.5 committed 2026-10-28 22:24:22 +01:00
1 parent 1d163cc299
commit 0635bb5b8a
1 file changed
+63 -6
+63 -6
View File
@@ -95,6 +95,16 @@ export function KioskDevicesPage() {
QRCode.toDataURL(payload, { width: 260, margin: 2 }).then(setBearerQrDataUrl)
}, [bearerResult])
// Ed25519-Geräte-ID-Ergebnis (Kiosk-App scannt {url, device_id})
const [deviceIdResult, setDeviceIdResult] = useState<{ deviceId: string; deviceName: string } | null>(null)
const [deviceIdQrDataUrl, setDeviceIdQrDataUrl] = useState('')
useEffect(() => {
if (!deviceIdResult) { setDeviceIdQrDataUrl(''); return }
const payload = JSON.stringify({ url: `${window.location.origin}/api/v1`, device_id: deviceIdResult.deviceId })
QRCode.toDataURL(payload, { width: 260, margin: 2 }).then(setDeviceIdQrDataUrl)
}, [deviceIdResult])
// Edit modal
const [editDevice, setEditDevice] = useState<KioskDevice | null>(null)
const [editName, setEditName] = useState('')
@@ -138,12 +148,13 @@ export function KioskDevicesPage() {
})
setBearerResult({ token: res.token, deviceName: res.device.name })
} else {
await api.post<KioskDevice>('/kiosk/devices', {
const res = await api.post<KioskDevice>('/kiosk/devices', {
name: createName.trim(),
location: createLocation.trim() || null,
public_key: createPublicKey.trim(),
ip_whitelist: createIpWhitelist.trim() || null,
})
setDeviceIdResult({ deviceId: res.id, deviceName: res.name })
}
setShowCreate(false)
setCreateName('')
@@ -415,14 +426,24 @@ export function KioskDevicesPage() {
onClick={() => setCreateMode('bearer')}
className={`flex-1 px-3 py-2 font-medium ${createMode === 'bearer' ? 'bg-blue-600 text-white' : 'bg-white text-gray-600 hover:bg-gray-50'}`}
>
Android-Tablet (Bearer/VPN)
Android-Tablet (Bearer/VPN) ⚠
</button>
</div>
{createMode === 'bearer' && (
{createMode === 'ed25519' && (
<div className='bg-indigo-50 border border-indigo-200 rounded-lg px-3 py-2 text-xs text-indigo-800'>
Kein Public Key nötig – nach dem Anlegen bekommst du ein Token
+ QR-Code, den die Kiosk-App beim Einrichten scannen kann.
Setzt WireGuard-VPN mit fester Geräte-IP voraus.
Auch für Android-Tablets empfohlen (seit Security-Audit
2026-09-10): Kiosk-App erzeugt selbst ein Schlüsselpaar und
zeigt den Public Key zum Einfügen hier an. Kein Token, keine
unbegrenzt gültigen Secrets.
</div>
)}
{createMode === 'bearer' && (
<div className='bg-red-50 border border-red-200 rounded-lg px-3 py-2 text-xs text-red-800'>
⚠ Nur mit echtem TLS/HTTPS auf dem Server verwenden. Ohne TLS
ist das Token unverschlüsselt mitschneidbar und unbegrenzt
gültig (Security-Audit 2026-09-10) – auf HTTP-Servern bitte
stattdessen „PC-Terminal (Ed25519)" nutzen, das Kiosk-Setup
unterstützt das jetzt auch für Tablets.
</div>
)}
<label className='block'>
@@ -515,6 +536,42 @@ export function KioskDevicesPage() {
</Modal>
)}
{/* Ed25519-Geräte-ID-Ergebnis (für QR-Scan in der Kiosk-App) */}
{deviceIdResult && (
<Modal title='Kiosk-Gerät angelegt' onClose={() => setDeviceIdResult(null)}>
<div className='space-y-4'>
<p className='text-sm text-gray-700'>
Gerät <strong>{deviceIdResult.deviceName}</strong> – QR-Code in der
Kiosk-App unter „Ersteinrichtung" scannen (nach dem Eintragen des
dort gezeigten Public Keys), oder Server-URL + Geräte-ID manuell
eintragen.
</p>
{deviceIdQrDataUrl && (
<div className='flex justify-center'>
<img src={deviceIdQrDataUrl} alt='QR-Code für Kiosk-Einrichtung' className='rounded-lg border border-gray-200' />
</div>
)}
<div>
<span className='text-xs font-medium text-gray-700'>Geräte-ID (manuell)</span>
<div className='flex gap-2 mt-1'>
<input readOnly value={deviceIdResult.deviceId} className={inputClass + ' font-mono text-xs'} />
<button
onClick={() => navigator.clipboard.writeText(deviceIdResult.deviceId)}
className='px-3 py-2 text-xs font-medium text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 whitespace-nowrap'
>
Kopieren
</button>
</div>
</div>
<div className='flex justify-end pt-2'>
<button onClick={() => setDeviceIdResult(null)} className='px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700'>
Fertig
</button>
</div>
</div>
</Modal>
)}
{/* Bearbeiten Modal */}
{editDevice && (
<Modal title={`Gerät bearbeiten – ${editDevice.name}`} onClose={() => setEditDevice(null)}>