import { randomBytes } from 'crypto'; import { countActiveMagicBindings, listMagicDevices } from '../../db/devices'; import { requireUser } from '../../utils/auth'; /** * POST /api/magic/register * * Body: { deviceId: string, hostname: string, model?: string, osVersion?: string } * * Mac-App ruft nach Login auf. Registriert das Device als Magic-Client, * generiert DNS-Token und provisioniert AdGuard Persistent Client. * * Idempotent: wenn bereits gebunden → return existing token. * Wenn Limit erreicht → 409 mit activeBindings-Liste. */ export default defineEventHandler(async (event) => { const user = await requireUser(event); const body = await readBody(event); const { deviceId, hostname, model, osVersion } = body as { deviceId?: string; hostname?: string; model?: string; osVersion?: string; }; if (!deviceId || !hostname) { throw createError({ statusCode: 400, message: 'deviceId und hostname required', }); } const db = usePrisma(); // 1. Prüfe ob Device bereits als Magic-Client gebunden ist (idempotent) const existing = await db.userDevice.findUnique({ where: { userId_deviceId: { userId: user.id, deviceId } }, select: { id: true, userId: true, magicDnsToken: true, magicEnrolledAt: true, magicRevokedAt: true, }, }); // Wenn Token existiert und nicht revoked → return existing if ( existing?.magicDnsToken && existing.magicEnrolledAt && !existing.magicRevokedAt ) { return { success: true, data: { deviceId, dnsToken: existing.magicDnsToken, profileUrl: `/api/magic/profile.mobileconfig?token=${existing.magicDnsToken}`, existing: true, }, }; } // 2. Limit-Check (nur wenn kein vorheriges Binding existiert) if (!existing || !existing.magicEnrolledAt) { const activeCount = await countActiveMagicBindings(user.id); if (activeCount >= MAGIC_DEVICE_LIMIT) { const activeBindings = await listMagicDevices(user.id); throw createError({ statusCode: 409, message: `Magic-Device-Limit erreicht (max ${MAGIC_DEVICE_LIMIT})`, data: { code: 'limit_reached', activeBindings, }, }); } } // 3. Generiere DNS-Token (48 char base64url-safe) const dnsToken = randomBytes(36).toString('base64url'); // 4. Provisioniere AdGuard Client const adguardClientName = `magic_${deviceId}`; try { await createAdGuardClient(adguardClientName, dnsToken, { use_global_settings: false, filtering_enabled: true, parental_enabled: false, safebrowsing_enabled: true, blocked_services: [], // TODO: Gambling-Filter via AdGuard Blocked-Services }); } catch (err: any) { console.error('[Magic] AdGuard provisioning failed:', err); throw createError({ statusCode: 502, message: 'DNS-Provisioning fehlgeschlagen', }); } // 5. Upsert UserDevice (platform="macos") const device = await db.userDevice.upsert({ where: { userId_deviceId: { userId: user.id, deviceId } }, create: { userId: user.id, deviceId, platform: 'macos', model: model ?? null, name: hostname, osVersion: osVersion ?? null, magicDnsToken: dnsToken, magicEnrolledAt: new Date(), magicHostname: hostname, }, update: { magicDnsToken: dnsToken, magicEnrolledAt: new Date(), magicRevokedAt: null, // Clear falls vorher revoked magicHostname: hostname, model: model ?? undefined, osVersion: osVersion ?? undefined, lastSeenAt: new Date(), }, select: { deviceId: true, magicDnsToken: true, }, }); return { success: true, data: { deviceId: device.deviceId, dnsToken: device.magicDnsToken, profileUrl: `/api/magic/profile.mobileconfig?token=${device.magicDnsToken}`, existing: false, }, }; });