Devices/Magic: - Offline-Profil-Enroll deaktiviert (410) — Lock-PW würde im Klartext im Download landen; stationärer Schutz läuft jetzt nur über Rebreak Magic - Mac-DNS-Template: ProhibitDisablement (Filter nicht abschaltbar) - Push "Neues Gerät verbunden" an mobile Geräte bei neuer Bindung - Realtime auf user_devices → Settings aktualisiert Magic-Bindings live - Geräte-Detail-Sheet (Tap auf Gerät): Status, verbunden-seit, Schutz-Donut Hard-Lock (server-gehaltenes Removal-PW, User sieht es nie): - magic_removal_password generiert/gespeichert + in Profil injiziert (Lazy-Backfill) - Reveal NUR bei Account-Löschung (user/delete) + Kündigung (stripe webhook), per Resend-Mail + in-Response - Signing config-gated (inaktiv ohne Cert; Lock greift auch unsigniert) Migrations: user_devices-Realtime-Publication + magic_removal_password-Spalten Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
import { randomUUID } from "crypto";
|
|
import {
|
|
findMagicDeviceByToken,
|
|
ensureMagicRemovalPassword,
|
|
} from "../../db/devices";
|
|
import { MAGIC_PROFILE_TEMPLATE } from "../../utils/magic-profile-template";
|
|
import {
|
|
buildRemovalPasswordPayload,
|
|
generateRemovalPassword,
|
|
signProfileIfConfigured,
|
|
} from "../../utils/magic-lock";
|
|
|
|
/**
|
|
* GET /api/magic/profile.mobileconfig?token=<dnsToken>
|
|
*
|
|
* Generiert das personalisierte, GESPERRTE DNS-Config-Profil für macOS/Windows.
|
|
* Template: ops/mdm/rebreak-mac-dns-filter.mobileconfig (inlined als TS const).
|
|
*
|
|
* Hard-Lock-Payloads:
|
|
* - DNS-Filter (com.apple.dnsSettings.managed) mit ProhibitDisablement
|
|
* - PayloadRemovalDisallowed + PayloadScope=System (im Template)
|
|
* - com.apple.profileRemovalPassword mit server-gehaltenem Passwort (hier
|
|
* injiziert) — der User sieht es NIE, nur nach Cooldown-Release (Offboarding).
|
|
*
|
|
* Signing: wenn Cert via runtimeConfig konfiguriert → CMS-signiert (grünes
|
|
* „Verifiziert"), sonst unsigniert (Lock greift trotzdem). Siehe magic-lock.ts.
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const query = getQuery(event);
|
|
const token = query.token as string | undefined;
|
|
|
|
if (!token) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: "token query parameter required",
|
|
});
|
|
}
|
|
|
|
const device = await findMagicDeviceByToken(token);
|
|
if (!device) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
message: "Invalid or revoked DNS token",
|
|
});
|
|
}
|
|
|
|
// Removal-Passwort: aus DB oder Lazy-Backfill (Devices vor dem Hard-Lock).
|
|
let removalPassword = device.magicRemovalPassword;
|
|
if (!removalPassword) {
|
|
removalPassword = generateRemovalPassword();
|
|
await ensureMagicRemovalPassword(device.id, removalPassword);
|
|
}
|
|
|
|
const deviceSlice = device.deviceId.slice(0, 8);
|
|
|
|
// Personalisierung: ServerURL → token-spezifisch, UUIDs/Identifier unique.
|
|
let personalizedProfile = MAGIC_PROFILE_TEMPLATE.replace(
|
|
"https://dns.rebreak.org/dns-query",
|
|
`https://dns.rebreak.org/dns-query/${token}`,
|
|
)
|
|
.replace("7D2E8B1A-C3D4-4E76-8B23-A4B5C6D7E8F0", randomUUID().toUpperCase())
|
|
.replace("8C3F9A2B-D4E5-4F87-9A12-B5C6D7E8F901", randomUUID().toUpperCase())
|
|
.replace(
|
|
"org.rebreak.protection.dns.filter",
|
|
`org.rebreak.protection.dns.filter.${deviceSlice}`,
|
|
)
|
|
.replace(
|
|
"org.rebreak.protection.profile",
|
|
`org.rebreak.protection.profile.${deviceSlice}`,
|
|
);
|
|
|
|
// Removal-Passwort-Payload in die PayloadContent-Array injizieren.
|
|
const removalPayload = buildRemovalPasswordPayload(removalPassword, deviceSlice);
|
|
personalizedProfile = personalizedProfile.replace(
|
|
" </array>",
|
|
`${removalPayload}\n </array>`,
|
|
);
|
|
|
|
// Optional signieren (config-gated; inaktiv ohne Cert).
|
|
const config = useRuntimeConfig(event);
|
|
const signed = signProfileIfConfigured(
|
|
personalizedProfile,
|
|
(config as any).magicSigning,
|
|
);
|
|
|
|
setHeader(event, "Content-Type", "application/x-apple-aspen-config");
|
|
setHeader(
|
|
event,
|
|
"Content-Disposition",
|
|
`attachment; filename="RebreakMagic-${deviceSlice}.mobileconfig"`,
|
|
);
|
|
|
|
return signed;
|
|
});
|