chahinebrini 6962e09403 feat(devices): Windows 11 DoH protection — reg-file endpoint + tests
- Add server/utils/regfile.ts: generateWindowsDohRegFile() producing
  UTF-16 LE + BOM .reg content for DohWellKnownServers registry path.
  label and dohTemplate values are properly escape'd (\, ", \n, \r, \t).
- Add GET /api/devices/:id/profile.reg — public, windows-platform-gated,
  returns octet-stream with Content-Disposition attachment.
- Update enroll.post.ts: downloadUrl is now platform-aware
  (windows → .reg, all others → .mobileconfig).
- Add tests/devices/regfile.test.ts: 13 tests covering BOM, CRLF,
  token embed, subkey naming, AutoUpgradeFlag, label escaping (", \, \n),
  and labelToSlug edge cases. All 111 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 04:48:51 +02:00

49 lines
1.6 KiB
TypeScript

import { getProtectedDevice } from "../../../db/protectedDevices";
import { labelToSlug } from "../../../utils/mobileconfig";
import { generateWindowsDohRegFile } from "../../../utils/regfile";
/**
* GET /api/devices/:id/profile.reg
*
* PUBLIC — Windows muss ohne Auth-Header zugreifen können.
* Der dnsToken im Profil IST die Device-Authentifizierung beim DoH-Server.
*
* Liefert ein Windows-Registry-File (.reg) das DoH für den ReBreak-DNS-Server
* in Windows 11 einrichtet.
*
* Content-Type: application/octet-stream
* Encoding: UTF-16 LE mit BOM (required by regedit.exe)
*/
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" } });
}
if (device.platform !== "windows") {
throw createError({
statusCode: 400,
data: { error: "WRONG_PLATFORM", expected: "windows", actual: device.platform },
});
}
const regBuffer = generateWindowsDohRegFile({
deviceId: device.id,
dnsToken: device.dnsToken,
label: device.label,
});
const slug = labelToSlug(device.label);
const filename = `rebreak-${slug || "schutz"}.reg`;
setHeader(event, "Content-Type", "application/octet-stream");
setHeader(event, "Content-Disposition", `attachment; filename="${filename}"`);
setHeader(event, "Cache-Control", "no-store");
return regBuffer;
});