feat(frontend): Geräte-ID-QR für Ed25519-Kiosk + TLS-Warnung bei Bearer
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d163cc299
commit
0635bb5b8a
1 file changed
+63
-6
@@ -95,6 +95,16 @@ export function KioskDevicesPage() {
|
|||||||
QRCode.toDataURL(payload, { width: 260, margin: 2 }).then(setBearerQrDataUrl)
|
QRCode.toDataURL(payload, { width: 260, margin: 2 }).then(setBearerQrDataUrl)
|
||||||
}, [bearerResult])
|
}, [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
|
// Edit modal
|
||||||
const [editDevice, setEditDevice] = useState<KioskDevice | null>(null)
|
const [editDevice, setEditDevice] = useState<KioskDevice | null>(null)
|
||||||
const [editName, setEditName] = useState('')
|
const [editName, setEditName] = useState('')
|
||||||
@@ -138,12 +148,13 @@ export function KioskDevicesPage() {
|
|||||||
})
|
})
|
||||||
setBearerResult({ token: res.token, deviceName: res.device.name })
|
setBearerResult({ token: res.token, deviceName: res.device.name })
|
||||||
} else {
|
} else {
|
||||||
await api.post<KioskDevice>('/kiosk/devices', {
|
const res = await api.post<KioskDevice>('/kiosk/devices', {
|
||||||
name: createName.trim(),
|
name: createName.trim(),
|
||||||
location: createLocation.trim() || null,
|
location: createLocation.trim() || null,
|
||||||
public_key: createPublicKey.trim(),
|
public_key: createPublicKey.trim(),
|
||||||
ip_whitelist: createIpWhitelist.trim() || null,
|
ip_whitelist: createIpWhitelist.trim() || null,
|
||||||
})
|
})
|
||||||
|
setDeviceIdResult({ deviceId: res.id, deviceName: res.name })
|
||||||
}
|
}
|
||||||
setShowCreate(false)
|
setShowCreate(false)
|
||||||
setCreateName('')
|
setCreateName('')
|
||||||
@@ -415,14 +426,24 @@ export function KioskDevicesPage() {
|
|||||||
onClick={() => setCreateMode('bearer')}
|
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'}`}
|
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>
|
</button>
|
||||||
</div>
|
</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'>
|
<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
|
Auch für Android-Tablets empfohlen (seit Security-Audit
|
||||||
+ QR-Code, den die Kiosk-App beim Einrichten scannen kann.
|
2026-09-10): Kiosk-App erzeugt selbst ein Schlüsselpaar und
|
||||||
Setzt WireGuard-VPN mit fester Geräte-IP voraus.
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<label className='block'>
|
<label className='block'>
|
||||||
@@ -515,6 +536,42 @@ export function KioskDevicesPage() {
|
|||||||
</Modal>
|
</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 */}
|
{/* Bearbeiten Modal */}
|
||||||
{editDevice && (
|
{editDevice && (
|
||||||
<Modal title={`Gerät bearbeiten – ${editDevice.name}`} onClose={() => setEditDevice(null)}>
|
<Modal title={`Gerät bearbeiten – ${editDevice.name}`} onClose={() => setEditDevice(null)}>
|
||||||
|
|||||||
Reference in new issue
Block a user