- Backend: /api/protection/event setzt bei Vorhandensein von deviceId (Body oder x-device-id Header) auch device_protection_states. source=mdm -> protectionType=nefilter, sonst vpn. - Native App: sendet deviceId im Body von /api/protection/event. - Magic App: Lock-Profil-Status wird nach lokaler Installation ans Backend gemeldet und Backend-Status neu geladen.
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
import { getHeader } from "h3";
|
|
import { requireUser } from "../../utils/auth";
|
|
import {
|
|
appendProtectionEventDeduped,
|
|
type ProtectionSource,
|
|
} from "../../db/protectionStateLog";
|
|
import { upsertDeviceProtectionState } from "../../db/device-protection";
|
|
import type { ProtectionType } from "../../db/device-protection";
|
|
|
|
const VALID_SOURCES: ProtectionSource[] = ["vpn", "mdm", "client"];
|
|
|
|
function sourceToProtectionType(source: ProtectionSource): ProtectionType {
|
|
if (source === "mdm") return "nefilter";
|
|
return "vpn";
|
|
}
|
|
|
|
/**
|
|
* POST /api/protection/event
|
|
*
|
|
* Body: {
|
|
* active: boolean,
|
|
* source: 'vpn' | 'mdm' | 'client',
|
|
* deviceId?: string // optional, falls bekannt (z.B. native App)
|
|
* }
|
|
*
|
|
* Called from the native app (useProtectionState / lib/protection) when the
|
|
* combined protection state transitions on↔off. The client deduplicates
|
|
* locally (only fires on real transitions); the server deduplicates again
|
|
* against the last DB row for the user.
|
|
*
|
|
* Side-effect: if deviceId is provided (body or x-device-id header), the
|
|
* per-device protection state (device_protection_states) is also updated so
|
|
* that MDM / Magic-App views see the current nefilter/vpn status.
|
|
*
|
|
* Returns { success: true, written: true } if a new row was written,
|
|
* { success: true, written: false } if deduplicated (state unchanged).
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await requireUser(event);
|
|
const body = await readBody(event);
|
|
|
|
if (typeof body?.active !== "boolean") {
|
|
throw createError({ statusCode: 400, message: "active (boolean) required" });
|
|
}
|
|
|
|
const source: ProtectionSource = VALID_SOURCES.includes(body.source)
|
|
? (body.source as ProtectionSource)
|
|
: "client";
|
|
|
|
const row = await appendProtectionEventDeduped(user.id, body.active, source);
|
|
|
|
const deviceId = body?.deviceId ?? getHeader(event, "x-device-id") ?? null;
|
|
if (deviceId && typeof deviceId === "string") {
|
|
try {
|
|
await upsertDeviceProtectionState(
|
|
user.id,
|
|
deviceId,
|
|
"ios",
|
|
sourceToProtectionType(source),
|
|
body.active,
|
|
new Date(),
|
|
`protection event via ${source}`,
|
|
"native-app",
|
|
);
|
|
} catch (err: any) {
|
|
console.error("[protection/event] device_protection_states upsert failed:", err?.message ?? err);
|
|
}
|
|
}
|
|
|
|
return { success: true, written: row !== null };
|
|
});
|