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)
|
||||
}, [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)}>
|
||||
|
||||
Reference in new issue
Block a user