- backend/api/magic/register: explicit import of MAGIC_DEVICE_LIMIT and createAdGuardClient (Nitro auto-import was missing them → ReferenceError → HTTP 500 on /api/magic/register) - mac-app: default backendBaseUrl falls back to staging.rebreak.org (app.rebreak.org serves wrong TLS cert) - native MagicSheet: fallback download/dmg URLs point to staging - native settings: Magic sheet capped at detents=[0.85] so AppHeader stays visible - bundles all in-flight Magic feature work (pair create/redeem, device endpoints, schema, adguard utils, mac-app, locales)
6.2 KiB
RebreakMagic Device-Binding — API Documentation
Backend-Implementation für DNS-basiertes Device-Binding via AdGuard Home DoH.
Architektur-Überblick
RebreakMagic.app (Swift/macOS)
↓ POST /api/magic/register (JWT Auth)
↓
Backend (Nitro)
├─ DB: UserDevice (magicDnsToken, magicEnrolledAt, ...)
├─ AdGuard REST API: Create Persistent Client
└─ Response: { dnsToken, profileUrl }
↓
RebreakMagic.app → GET /api/magic/profile.mobileconfig?token=<dnsToken>
↓
macOS Configuration Profile (.mobileconfig)
↓ DNS-over-HTTPS: https://dns.rebreak.org/dns-query/{dnsToken}
↓
AdGuard Home (Hetzner) — Filtering + Logging per Client-ID
Endpoints
1. POST /api/magic/register
Registriert Mac als Magic-Client, generiert DNS-Token, provisioniert AdGuard.
Auth: Authorization: Bearer <jwt>
Body:
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"hostname": "Chahines MacBook Pro",
"model": "MacBookPro18,3",
"osVersion": "14.5"
}
Response (Success):
{
"success": true,
"data": {
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"dnsToken": "QX7g9kL2mN4pR6tV8wY0zB3cD5fG7hJ9kM2nP4qS6uW8xZ0",
"profileUrl": "/api/magic/profile.mobileconfig?token=QX7g9kL2mN4pR6tV8wY0zB3cD5fG7hJ9kM2nP4qS6uW8xZ0",
"existing": false
}
}
Response (Limit erreicht):
{
"statusCode": 409,
"message": "Magic-Device-Limit erreicht (max 3)",
"data": {
"code": "limit_reached",
"activeBindings": [
{
"deviceId": "...",
"hostname": "Mac #1",
"model": "MacBookPro18,3",
"osVersion": "14.5",
"magicEnrolledAt": "2026-06-01T10:00:00.000Z",
"releaseRequestedAt": null
}
]
}
}
cURL:
curl -X POST https://staging.rebreak.org/api/magic/register \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"hostname": "Chahines MacBook Pro",
"model": "MacBookPro18,3",
"osVersion": "14.5"
}'
2. GET /api/magic/devices
Listet alle aktiven Magic-Bindings des Users.
Auth: Authorization: Bearer <jwt>
Response:
{
"success": true,
"data": [
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"hostname": "Chahines MacBook Pro",
"model": "MacBookPro18,3",
"osVersion": "14.5",
"magicEnrolledAt": "2026-06-01T10:00:00.000Z",
"releaseRequestedAt": null,
"releaseAvailableAt": null
}
]
}
cURL:
curl https://staging.rebreak.org/api/magic/devices \
-H "Authorization: Bearer $JWT_TOKEN"
3. POST /api/magic/devices/:deviceId/request-release
Startet 24h Cooldown für Device-Freigabe.
Auth: Authorization: Bearer <jwt>
Response:
{
"success": true,
"data": {
"releaseRequestedAt": "2026-06-01T10:00:00.000Z",
"releaseAvailableAt": "2026-06-02T10:00:00.000Z"
}
}
cURL:
curl -X POST https://staging.rebreak.org/api/magic/devices/550e8400-e29b-41d4-a716-446655440000/request-release \
-H "Authorization: Bearer $JWT_TOKEN"
4. POST /api/magic/devices/:deviceId/cancel-release
Zieht Release-Request zurück.
Auth: Authorization: Bearer <jwt>
Response:
{
"success": true,
"data": { "ok": true }
}
cURL:
curl -X POST https://staging.rebreak.org/api/magic/devices/550e8400-e29b-41d4-a716-446655440000/cancel-release \
-H "Authorization: Bearer $JWT_TOKEN"
5. GET /api/magic/profile.mobileconfig?token=<dnsToken>
Generiert personalisiertes macOS Configuration Profile.
Auth: KEINE (Token in Query-Parameter)
Response-Headers:
Content-Type: application/x-apple-aspen-configContent-Disposition: attachment; filename="RebreakMagic-<deviceId>.mobileconfig"
Response-Body: XML-Plist (mobileconfig)
cURL:
curl "https://staging.rebreak.org/api/magic/profile.mobileconfig?token=QX7g9kL2mN4pR6tV8wY0zB3cD5fG7hJ9kM2nP4qS6uW8xZ0" \
-o RebreakMagic.mobileconfig
DB-Schema
UserDevice Model (Prisma Schema):
model UserDevice {
// ... existing fields ...
// RebreakMagic DNS-Device-Binding
magicDnsToken String? @unique @map("magic_dns_token")
magicEnrolledAt DateTime? @map("magic_enrolled_at")
magicRevokedAt DateTime? @map("magic_revoked_at")
magicHostname String? @map("magic_hostname")
}
Migration:
# User führt aus (NICHT auto-deployen):
pnpm prisma migrate dev --name magic_binding_fields
AdGuard-Integration
API-Endpoint: https://dns.rebreak.org/control/clients/add
Auth: Basic Auth (ADGUARD_USER, ADGUARD_PASSWORD)
Payload:
{
"name": "magic_<deviceId>",
"ids": ["<dnsToken>"],
"use_global_settings": false,
"filtering_enabled": true,
"parental_enabled": false,
"safebrowsing_enabled": true,
"blocked_services": []
}
DoH-URL-Format (embedded in mobileconfig):
https://dns.rebreak.org/dns-query/<dnsToken>
Cron-Worker
Funktion: processMagicReleases() in server/utils/magicCron.ts
Logic:
- Findet alle UserDevice mit
releaseRequestedAt < NOW() - 24hANDmagicRevokedAt IS NULL - Für jedes Device:
- DELETE AdGuard Client (
/control/clients/delete) - Setze
magicRevokedAt = NOW()
- DELETE AdGuard Client (
- Return
{ processed, errors }
Deployment: TODO — Nitro Scheduled Task oder externer Cron-Trigger
ENV-Variablen
Siehe ENV_VARS.md:
ADGUARD_BASE_URL— Default:https://dns.rebreak.orgADGUARD_USER— Admin-User für AdGuard Home REST APIADGUARD_PASSWORD— Admin-Password
TODOs (Phase 2)
- Profile-Signierung via Apple Developer Certificate (
/usr/bin/security cms -S) - Cron-Registration für
processMagicReleases()(Nitro scheduled task oder externer Cron) - Plan-basierte Limits (jetzt hardcoded
MAGIC_DEVICE_LIMIT = 3) - AdGuard Blocked-Services konfigurieren (Gambling-Filter via AdGuard-Blocklisten)
- Tests (Phase 2:
rebreak-tester) - Frontend-Integration (RN-UI + RebreakMagic.app)