5.8 KiB
MDM Device Link für Magic App — Design
Ziel
In der ReBreak Magic App unter „Meine iOS-Geräte“ automatisch prüfen, ob ein iOS-Gerät im NanoMDM enrolled ist, und den passenden Status in der Device-Card anzeigen.
Annahmen / Einschränkungen
- Native-App-DB (
rebreakaufrebreak-server) und NanoMDM-DB (nanomdmaufrebreak-mdm) liegen auf unterschiedlichen Hetzner-VPS. - Apple gibt die Hardware-UDID nicht an React Native / Expo Apps weiter. Die Native-App-
deviceIdistidentifierForVendor, nicht die NanoMDM-UDID. - Daher braucht
UserDeviceeine zusätzliche SpaltemdmId, die die NanoMDM-UDID speichert.
Architektur
Magic App (Tauri/Nuxt)
│
├─ GET /api/magic/devices/:deviceId/mdm → Backend
│ │
│ ├─ Prisma: UserDevice.mdmId lesen
│ │
│ └─ pg.Pool → NanoMDM DB auf rebreak-mdm
│ SELECT devices / command_results
│
└─ POST /api/magic/devices/:deviceId/mdm-link
(setzt UserDevice.mdmId, z.B. nach USB-Enrollment)
Datenmodell
model UserDevice {
// ... bestehende Felder ...
mdmId String? @map("mdm_id") // NanoMDM-UDID, nullable
}
model DeviceProtectionState {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
deviceId String @map("device_id")
platform String
protectionType String @map("protection_type")
active Boolean
lastSeenAt DateTime? @map("last_seen_at")
changedAt DateTime @default(now()) @map("changed_at")
reason String?
@@unique([userId, deviceId, protectionType])
@@map("device_protection_states")
@@schema("rebreak")
}
model DeviceProtectionStateLog {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
deviceId String @map("device_id")
protectionType String @map("protection_type")
active Boolean
occurredAt DateTime @map("occurred_at")
reason String?
source String
@@index([userId, deviceId])
@@map("device_protection_state_logs")
@@schema("rebreak")
}
Endpunkte
GET /api/magic/devices/:deviceId/mdm
Auth: Magic-Session (requireUser)
Response enrolled:
{
"success": true,
"data": {
"enrolled": true,
"company": "ReBreak",
"supervised": true,
"lockProfileInstalled": true,
"lastAppPushAt": "2026-06-11T19:09:04.363Z"
}
}
Response not enrolled:
{ "success": true, "data": { "enrolled": false } }
Wenn mdmId gesetzt ist, aber NanoMDM das Gerät nicht mehr kennt, wird mdmId automatisch auf null gesetzt.
lockProfileInstalled wird nicht aus der bloßen MDM-Enrollment abgeleitet, sondern aus dem lokal gespeicherten DeviceProtectionState mit protectionType = "nefilter". Solange die Native-App diesen Zustand noch nicht meldet, ist der Wert false.
POST /api/magic/devices/:deviceId/mdm-link
Body: { "mdmId": "00008150-001C686601F0401C" }
Setzt UserDevice.mdmId für das iOS-Gerät des aktuellen Users.
POST /api/devices/protection-state
Body:
{
"deviceId": "string",
"platform": "ios",
"protectionType": "nefilter | vpn | dns",
"active": true,
"reason": "optional",
"source": "optional"
}
Upsertet den per-Gerät-Schutz-Status und schreibt bei Änderung einen Log-Eintrag.
Magic-App UI
IosDeviceCard.vue ruft über useMdmStatus(deviceId) den Backend-Status ab:
- Enrolled: Grüne Box mit Company / Supervised / Lock-Profil / Letzter App-Push.
- Not enrolled: Gelbe Box mit Hinweis: „Nicht MDM-enrolled. Verbinde das iPhone per USB. Das Enrollment dauert ca. 2 Minuten und geht ohne Datenverlust.“
- Wenn ein iPhone per USB verbunden ist, erscheint ein Button „Mit MDM verknüpfen“, der
linkMdmDevice(deviceId, udid)aufruft.
Infra / Env
MDM_DATABASE_URLmuss im Backend-Env gesetzt sein.rebreak-mdmPostgreSQL muss auf der öffentlichen IP lauschen und den Backend-Server inpg_hba.conf+ UFW erlauben.
Dateien
Backend:
backend/prisma/schema.prismabackend/nitro.config.tsbackend/server/db/mdm.tsbackend/server/db/device-protection.tsbackend/server/api/magic/devices/[deviceId]/mdm.get.tsbackend/server/api/magic/devices/[deviceId]/mdm-link.post.tsbackend/server/api/devices/protection-state.post.tsbackend/start-staging.sh
Magic App:
apps/rebreak-magic/src-tauri/src/backend/api.rsapps/rebreak-magic/src-tauri/src/lib.rsapps/rebreak-magic/app/composables/useTauri.tsapps/rebreak-magic/app/composables/useMdmStatus.tsapps/rebreak-magic/app/components/IosDeviceCard.vueapps/rebreak-magic/app/components/DevLogDrawer.vue
Test-Ergebnis (Staging)
- Backend-Build: ✅
- Magic Rust:
cargo check✅ - Magic Nuxt-Build: ✅
- API-Test für User
charioanouar@gmail.com, iPhoneMHFLX23QM0:GET .../mdm→{ enrolled: true, company: "ReBreak", supervised: true, lockProfileInstalled: false, lastAppPushAt: "2026-06-11T19:09:04.363Z" }mdmIdgelöscht →{ enrolled: false }POST .../mdm-link→{ mdmId: "00008150-001C686601F0401C" }
Offene TODOs
nefilter,vpn,dnswerden noch nicht von den Clients gemeldet. Dafür istPOST /api/devices/protection-statevorbereitet.- Die Tabellen
device_protection_states/device_protection_state_logswurden für den schnellen Test manuell angelegt. Für Produktion muss eine Prisma-Migration erstellt und deployed werden. - Die Spalte
mdm_idwurde für den schnellen Test manuell mitALTER TABLEangelegt.