rebreak-monorepo/backend/server/api/devices/[id]/profile.mobileconfig.get.ts
chahinebrini 677b67902b feat(devices): protected device enrollment + mobileconfig generator
Backend:
- ProtectedDevice prisma model + migration add_protected_devices
- DB helpers: list/count/get/create/confirm/revoke
- mobileconfig.ts utility — XML-escape, unique UUIDs per request
- 5 endpoints under /api/devices/* (avoid /api/devices conflict with existing
  Capacitor UserDevice route by using /api/devices/protected for list)

Phase 1: backend ready. DoH-server token-routing comes in phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 04:06:49 +02:00

46 lines
1.3 KiB
TypeScript

import { getProtectedDevice } from "../../../db/protectedDevices";
import {
generateMacOSDnsProfile,
labelToSlug,
} from "../../../utils/mobileconfig";
/**
* GET /api/devices/:id/profile.mobileconfig
*
* PUBLIC — der Mac muss ohne Auth-Header zugreifen können.
* Der dnsToken im Profil IST die Device-Authentifizierung beim DoH-Server.
*
* Liefert ein macOS DNS-Over-HTTPS Konfigurationsprofil.
* Content-Type: application/x-apple-aspen-config
*/
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id");
if (!id) throw createError({ statusCode: 400, data: { error: "ID_REQUIRED" } });
const device = await getProtectedDevice(id);
if (!device || device.status === "revoked") {
throw createError({ statusCode: 404, data: { error: "DEVICE_NOT_FOUND" } });
}
const plist = generateMacOSDnsProfile({
deviceId: device.id,
dnsToken: device.dnsToken,
label: device.label,
});
const slug = labelToSlug(device.label);
const filename = `rebreak-${slug || "schutz"}.mobileconfig`;
setHeader(event, "Content-Type", "application/x-apple-aspen-config");
setHeader(
event,
"Content-Disposition",
`attachment; filename="${filename}"`,
);
// Kein Caching — Token ist sensitiv
setHeader(event, "Cache-Control", "no-store");
return plist;
});