import { randomBytes } from "crypto"; import { getProfile } from "../../db/profile"; import { getPlanLimits } from "../../utils/plan-features"; import { countActiveProtectedDevices, createProtectedDevice, } from "../../db/protectedDevices"; /** * POST /api/devices/enroll * * Legend-only. User klickt "Mac hinzufügen" in der App. * Legt ein ProtectedDevice (status=pending) an und gibt die Download-URL * für das mobileconfig-Profil zurück. * * Body: { platform: "mac" | "windows" | "ios" | "android", label: string } * Response: { deviceId, dnsToken, downloadUrl } */ export default defineEventHandler(async (event) => { const user = await requireUser(event); const profile = await getProfile(user.id); const limits = getPlanLimits(profile?.plan ?? "free"); // maxProtectedDevices=0 → Feature nicht verfügbar (free/pro) if (limits.maxProtectedDevices === 0) { throw createError({ statusCode: 403, data: { error: "LEGEND_REQUIRED" }, }); } const body = await readBody(event); const platform = body?.platform as string | undefined; const label = body?.label as string | undefined; const VALID_PLATFORMS = ["mac", "windows", "ios", "android"]; if (!platform || !VALID_PLATFORMS.includes(platform)) { throw createError({ statusCode: 400, data: { error: "INVALID_PLATFORM", validValues: VALID_PLATFORMS }, }); } if (!label || typeof label !== "string" || label.trim().length === 0) { throw createError({ statusCode: 400, data: { error: "LABEL_REQUIRED" } }); } const trimmedLabel = label.trim().slice(0, 100); // Limit: max. maxProtectedDevices active+pending Devices const activeCount = await countActiveProtectedDevices(user.id); if (activeCount >= limits.maxProtectedDevices) { throw createError({ statusCode: 409, data: { error: "plan_limit", resource: "protected_devices", current: activeCount, limit: limits.maxProtectedDevices, }, }); } // 32-char hex token — kryptografisch sicher const dnsToken = randomBytes(16).toString("hex"); const device = await createProtectedDevice({ userId: user.id, dnsToken, platform, label: trimmedLabel, }); const config = useRuntimeConfig(event); const apiBase = (config.public as any)?.apiBase ?? "https://api.rebreak.org"; // Platform-aware download URL: Windows gets .reg, everything else .mobileconfig const profileExt = platform === "windows" ? "reg" : "mobileconfig"; const downloadUrl = `${apiBase}/api/devices/${device.id}/profile.${profileExt}`; return { success: true, data: { deviceId: device.id, dnsToken: device.dnsToken, downloadUrl, }, }; });