fix(magic): real MDM supervised state, mdmId matching, MDM status for unknown USB devices
This commit is contained in:
parent
75d1b06105
commit
6245fc4573
@ -467,7 +467,7 @@ const action = computed<IosAction>(() => {
|
|||||||
|
|
||||||
if (!props.isConnected || !props.iphone) {
|
if (!props.isConnected || !props.iphone) {
|
||||||
return {
|
return {
|
||||||
label: "iPhone verbinden",
|
label: "iPhone verbinden, um ReBreak Cloud zu synchronisieren",
|
||||||
icon: "i-heroicons-link",
|
icon: "i-heroicons-link",
|
||||||
color: "primary",
|
color: "primary",
|
||||||
variant: "solid",
|
variant: "solid",
|
||||||
|
|||||||
@ -61,6 +61,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
function matchesIphone(device: ComputedDevice, iphone: IphoneDeviceState): boolean {
|
function matchesIphone(device: ComputedDevice, iphone: IphoneDeviceState): boolean {
|
||||||
|
if (device.mdmId && device.mdmId === iphone.udid) return true;
|
||||||
const modelMatch = (device.model ?? "").toLowerCase() === iphone.productType.toLowerCase();
|
const modelMatch = (device.model ?? "").toLowerCase() === iphone.productType.toLowerCase();
|
||||||
const nameMatch = (device.name ?? "").toLowerCase() === iphone.name.toLowerCase();
|
const nameMatch = (device.name ?? "").toLowerCase() === iphone.name.toLowerCase();
|
||||||
return modelMatch || nameMatch;
|
return modelMatch || nameMatch;
|
||||||
|
|||||||
@ -26,6 +26,79 @@
|
|||||||
<p class="truncate"><span class="font-medium">UDID:</span> {{ iphone.udid }}</p>
|
<p class="truncate"><span class="font-medium">UDID:</span> {{ iphone.udid }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- MDM status for this UDID -->
|
||||||
|
<div class="mt-4 rounded-xl bg-white/60 dark:bg-black/20 p-4">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<p class="text-sm font-bold text-gray-900 dark:text-white">
|
||||||
|
ReBreak Cloud-Status
|
||||||
|
</p>
|
||||||
|
<UBadge
|
||||||
|
v-if="mdmLoading"
|
||||||
|
color="neutral"
|
||||||
|
variant="subtle"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Lädt…
|
||||||
|
</UBadge>
|
||||||
|
<UBadge
|
||||||
|
v-else-if="mdmStatus?.enrolled"
|
||||||
|
color="success"
|
||||||
|
variant="subtle"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Enrolled
|
||||||
|
</UBadge>
|
||||||
|
<UBadge
|
||||||
|
v-else
|
||||||
|
color="warning"
|
||||||
|
variant="subtle"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
|
Nicht enrolled
|
||||||
|
</UBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="mdmError"
|
||||||
|
class="text-xs text-red-600 dark:text-red-400"
|
||||||
|
>
|
||||||
|
{{ mdmError }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-else-if="mdmStatus?.enrolled"
|
||||||
|
class="space-y-1.5 text-sm text-gray-700 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Enrollment</span>
|
||||||
|
<span class="text-green-600 dark:text-green-400 font-medium">Ja</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Supervised</span>
|
||||||
|
<span :class="mdmStatus.supervised ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'" class="font-medium">
|
||||||
|
{{ mdmStatus.supervised ? "Ja" : "Nein" }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">Organisation</span>
|
||||||
|
<span class="font-medium">{{ mdmStatus.company ?? "—" }}</span>
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center justify-between">
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">ReBreak App</span>
|
||||||
|
<span :class="mdmStatus.lastAppPushAt ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'" class="font-medium">
|
||||||
|
{{ mdmStatus.lastAppPushAt ? "Gepusht" : "Nicht gepusht" }}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="mdmStatus && !mdmStatus.enrolled && !mdmLoading"
|
||||||
|
class="text-sm text-amber-800 dark:text-amber-300"
|
||||||
|
>
|
||||||
|
Dieses iPhone ist noch nicht in der ReBreak Cloud. Folge den Schritten unten, um es zu verwalten.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-sm text-gray-600 dark:text-gray-300 bg-white/60 dark:bg-black/20 rounded-lg p-3">
|
<div class="mt-4 text-sm text-gray-600 dark:text-gray-300 bg-white/60 dark:bg-black/20 rounded-lg p-3">
|
||||||
<p class="font-medium mb-1">So kannst du es verwalten:</p>
|
<p class="font-medium mb-1">So kannst du es verwalten:</p>
|
||||||
<ol class="list-decimal list-inside space-y-0.5">
|
<ol class="list-decimal list-inside space-y-0.5">
|
||||||
@ -40,12 +113,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IphoneDeviceState } from "~/composables/useTauri";
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useTauri, type IphoneDeviceState, type MdmStatusByUdidData } from "~/composables/useTauri";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
iphone: IphoneDeviceState;
|
iphone: IphoneDeviceState;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { getMdmStatusByUdid } = useTauri();
|
||||||
|
|
||||||
|
const mdmLoading = ref(false);
|
||||||
|
const mdmStatus = ref<MdmStatusByUdidData | null>(null);
|
||||||
|
const mdmError = ref<string | null>(null);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
mdmLoading.value = true;
|
||||||
|
mdmError.value = null;
|
||||||
|
try {
|
||||||
|
mdmStatus.value = await getMdmStatusByUdid(props.iphone.udid);
|
||||||
|
} catch (e: any) {
|
||||||
|
mdmError.value = e?.message ?? "Cloud-Status konnte nicht geladen werden";
|
||||||
|
} finally {
|
||||||
|
mdmLoading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const productTypeMap: Record<string, string> = {
|
const productTypeMap: Record<string, string> = {
|
||||||
"iPhone18,4": "iPhone Air",
|
"iPhone18,4": "iPhone Air",
|
||||||
"iPhone17,1": "iPhone 16 Pro",
|
"iPhone17,1": "iPhone 16 Pro",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export interface ComputedDevice {
|
|||||||
platform: "mac" | "windows" | "ios" | "android" | "unknown";
|
platform: "mac" | "windows" | "ios" | "android" | "unknown";
|
||||||
model: string | null;
|
model: string | null;
|
||||||
osVersion: string | null;
|
osVersion: string | null;
|
||||||
|
mdmId: string | null;
|
||||||
status: DeviceStatus;
|
status: DeviceStatus;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
cooldownUntil: string | null;
|
cooldownUntil: string | null;
|
||||||
@ -40,6 +41,7 @@ function mapToComputedDevice(d: MagicDeviceInfo, isCurrent: boolean): ComputedDe
|
|||||||
platform: normalizePlatform(d.model ?? d.hostname),
|
platform: normalizePlatform(d.model ?? d.hostname),
|
||||||
model: d.model,
|
model: d.model,
|
||||||
osVersion: d.osVersion,
|
osVersion: d.osVersion,
|
||||||
|
mdmId: d.mdmId ?? null,
|
||||||
status: d.status as DeviceStatus,
|
status: d.status as DeviceStatus,
|
||||||
isCurrent,
|
isCurrent,
|
||||||
cooldownUntil: d.cooldownUntil,
|
cooldownUntil: d.cooldownUntil,
|
||||||
|
|||||||
@ -45,6 +45,7 @@ export interface MagicDeviceInfo {
|
|||||||
hostname: string;
|
hostname: string;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
osVersion: string | null;
|
osVersion: string | null;
|
||||||
|
mdmId: string | null;
|
||||||
magicEnrolledAt: string | null;
|
magicEnrolledAt: string | null;
|
||||||
releaseRequestedAt: string | null;
|
releaseRequestedAt: string | null;
|
||||||
releaseAvailableAt: string | null;
|
releaseAvailableAt: string | null;
|
||||||
@ -90,6 +91,13 @@ export interface MdmStatusData {
|
|||||||
lastAppPushAt: string | null;
|
lastAppPushAt: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MdmStatusByUdidData {
|
||||||
|
enrolled: boolean;
|
||||||
|
company: string | null;
|
||||||
|
supervised: boolean;
|
||||||
|
lastAppPushAt: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SuperviseStatus {
|
export interface SuperviseStatus {
|
||||||
isSupervised: boolean;
|
isSupervised: boolean;
|
||||||
organizationName?: string;
|
organizationName?: string;
|
||||||
@ -274,6 +282,10 @@ export function useTauri() {
|
|||||||
return await invokeLogged("get_mdm_status", { deviceId });
|
return await invokeLogged("get_mdm_status", { deviceId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getMdmStatusByUdid(udid: string): Promise<MdmStatusByUdidData> {
|
||||||
|
return await invokeLogged("get_mdm_status_by_udid", { udid });
|
||||||
|
}
|
||||||
|
|
||||||
async function linkMdmDevice(deviceId: string, mdmId: string): Promise<void> {
|
async function linkMdmDevice(deviceId: string, mdmId: string): Promise<void> {
|
||||||
await invokeLogged("link_mdm_device", { deviceId, mdmId });
|
await invokeLogged("link_mdm_device", { deviceId, mdmId });
|
||||||
}
|
}
|
||||||
@ -314,6 +326,7 @@ export function useTauri() {
|
|||||||
getHardwareId,
|
getHardwareId,
|
||||||
getDeviceId,
|
getDeviceId,
|
||||||
getMdmStatus,
|
getMdmStatus,
|
||||||
|
getMdmStatusByUdid,
|
||||||
linkMdmDevice,
|
linkMdmDevice,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,8 @@ pub struct MagicDeviceInfo {
|
|||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
#[serde(rename = "osVersion")]
|
#[serde(rename = "osVersion")]
|
||||||
pub os_version: Option<String>,
|
pub os_version: Option<String>,
|
||||||
|
#[serde(default, rename = "mdmId")]
|
||||||
|
pub mdm_id: Option<String>,
|
||||||
#[serde(rename = "magicEnrolledAt")]
|
#[serde(rename = "magicEnrolledAt")]
|
||||||
pub magic_enrolled_at: Option<String>,
|
pub magic_enrolled_at: Option<String>,
|
||||||
#[serde(rename = "releaseRequestedAt")]
|
#[serde(rename = "releaseRequestedAt")]
|
||||||
@ -108,6 +110,15 @@ pub struct MdmStatusData {
|
|||||||
pub last_app_push_at: Option<String>,
|
pub last_app_push_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MdmStatusByUdidData {
|
||||||
|
pub enrolled: bool,
|
||||||
|
pub company: Option<String>,
|
||||||
|
pub supervised: bool,
|
||||||
|
#[serde(rename = "lastAppPushAt")]
|
||||||
|
pub last_app_push_at: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct MdmLinkRequest {
|
pub struct MdmLinkRequest {
|
||||||
#[serde(rename = "mdmId")]
|
#[serde(rename = "mdmId")]
|
||||||
@ -378,6 +389,27 @@ impl MagicApiClient {
|
|||||||
.map(|envelope| envelope.data)
|
.map(|envelope| envelope.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_mdm_status_by_udid(
|
||||||
|
&self,
|
||||||
|
token: &str,
|
||||||
|
udid: &str,
|
||||||
|
) -> AppResult<MdmStatusByUdidData> {
|
||||||
|
let url = format!("{}/api/magic/mdm/by-udid", self.base_url);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.get(&url)
|
||||||
|
.header("Authorization", format!("Bearer {}", token))
|
||||||
|
.query(&[("udid", udid)])
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| AppError::new(format!("Network error: {}", e)))?;
|
||||||
|
|
||||||
|
Self::handle_response::<ApiEnvelope<MdmStatusByUdidData>>(response)
|
||||||
|
.await
|
||||||
|
.map(|envelope| envelope.data)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn link_mdm_device(
|
pub async fn link_mdm_device(
|
||||||
&self,
|
&self,
|
||||||
token: &str,
|
token: &str,
|
||||||
|
|||||||
@ -8,8 +8,8 @@ mod server;
|
|||||||
mod sidecar;
|
mod sidecar;
|
||||||
|
|
||||||
use backend::api::{
|
use backend::api::{
|
||||||
MagicApiClient, MagicDeviceInfo, MdmStatusData, RedeemPairingResponse, RegisterDeviceResponse,
|
MagicApiClient, MagicDeviceInfo, MdmStatusByUdidData, MdmStatusData, RedeemPairingResponse,
|
||||||
ReleaseResponse, UserProfile,
|
RegisterDeviceResponse, ReleaseResponse, UserProfile,
|
||||||
};
|
};
|
||||||
use config::{AppConfig, DesktopProtectionState, MagicSession};
|
use config::{AppConfig, DesktopProtectionState, MagicSession};
|
||||||
use error::AppResult;
|
use error::AppResult;
|
||||||
@ -52,6 +52,7 @@ pub fn run() {
|
|||||||
activate_protection,
|
activate_protection,
|
||||||
fetch_me,
|
fetch_me,
|
||||||
get_mdm_status,
|
get_mdm_status,
|
||||||
|
get_mdm_status_by_udid,
|
||||||
link_mdm_device,
|
link_mdm_device,
|
||||||
get_desktop_protection_status,
|
get_desktop_protection_status,
|
||||||
set_desktop_protection_status,
|
set_desktop_protection_status,
|
||||||
@ -235,6 +236,14 @@ async fn link_mdm_device(device_id: String, mdm_id: String) -> AppResult<()> {
|
|||||||
client.link_mdm_device(&session.access_token, &device_id, &mdm_id).await
|
client.link_mdm_device(&session.access_token, &device_id, &mdm_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_mdm_status_by_udid(udid: String) -> AppResult<MdmStatusByUdidData> {
|
||||||
|
let session = require_session()?;
|
||||||
|
let config = AppConfig::load();
|
||||||
|
let client = MagicApiClient::new(&config);
|
||||||
|
client.get_mdm_status_by_udid(&session.access_token, &udid).await
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn download_profile(profile_url: String) -> AppResult<String> {
|
async fn download_profile(profile_url: String) -> AppResult<String> {
|
||||||
let session = require_session()?;
|
let session = require_session()?;
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
model: true,
|
model: true,
|
||||||
name: true,
|
name: true,
|
||||||
osVersion: true,
|
osVersion: true,
|
||||||
|
mdmId: true,
|
||||||
lastSeenAt: true,
|
lastSeenAt: true,
|
||||||
releaseRequestedAt: true,
|
releaseRequestedAt: true,
|
||||||
},
|
},
|
||||||
@ -75,6 +76,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
cooldownUntil: d.magicCooldownUntil?.toISOString() ?? null,
|
cooldownUntil: d.magicCooldownUntil?.toISOString() ?? null,
|
||||||
status,
|
status,
|
||||||
lastSeenAt: d.lastSeenAt?.toISOString() ?? null,
|
lastSeenAt: d.lastSeenAt?.toISOString() ?? null,
|
||||||
|
mdmId: d.mdmId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -98,6 +100,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
status: "active" as const,
|
status: "active" as const,
|
||||||
lastSeenAt: d.lastSeenAt?.toISOString() ?? null,
|
lastSeenAt: d.lastSeenAt?.toISOString() ?? null,
|
||||||
cooldownUntil: null,
|
cooldownUntil: null,
|
||||||
|
mdmId: d.mdmId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -74,7 +74,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
data: {
|
data: {
|
||||||
enrolled: true,
|
enrolled: true,
|
||||||
company: "ReBreak",
|
company: "ReBreak",
|
||||||
supervised: true,
|
supervised: status.supervised,
|
||||||
lockProfileInstalled: lockState?.active ?? false,
|
lockProfileInstalled: lockState?.active ?? false,
|
||||||
lastAppPushAt: status.lastAppPushAt?.toISOString() ?? null,
|
lastAppPushAt: status.lastAppPushAt?.toISOString() ?? null,
|
||||||
},
|
},
|
||||||
|
|||||||
44
backend/server/api/magic/mdm/by-udid.get.ts
Normal file
44
backend/server/api/magic/mdm/by-udid.get.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { getMdmStatusByUdid } from "../../../db/mdm";
|
||||||
|
import { requireUser } from "../../../utils/auth";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/magic/mdm/by-udid?udid=...
|
||||||
|
*
|
||||||
|
* Looks up the NanoMDM enrollment status for an arbitrary UDID. Useful when a
|
||||||
|
* USB-connected iPhone has not yet been linked to a ReBreak user device, e.g.
|
||||||
|
* to show whether it is already enrolled in ReBreak Cloud.
|
||||||
|
*/
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireUser(event);
|
||||||
|
const query = getQuery(event);
|
||||||
|
const udid = query.udid;
|
||||||
|
|
||||||
|
if (!udid || typeof udid !== "string") {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
data: { error: "udid_required" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let status: Awaited<ReturnType<typeof getMdmStatusByUdid>>;
|
||||||
|
try {
|
||||||
|
status = await getMdmStatusByUdid(udid);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[MDM] NanoMDM DB query failed:", err);
|
||||||
|
throw createError({
|
||||||
|
statusCode: 503,
|
||||||
|
message: "mdm_db_unreachable",
|
||||||
|
data: { code: "mdm_db_unreachable" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
enrolled: status.enrolled,
|
||||||
|
company: status.company,
|
||||||
|
supervised: status.supervised,
|
||||||
|
lastAppPushAt: status.lastAppPushAt?.toISOString() ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -422,6 +422,7 @@ export interface MagicDeviceRecord {
|
|||||||
hostname: string | null;
|
hostname: string | null;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
osVersion: string | null;
|
osVersion: string | null;
|
||||||
|
mdmId: string | null;
|
||||||
magicEnrolledAt: Date;
|
magicEnrolledAt: Date;
|
||||||
releaseRequestedAt: Date | null;
|
releaseRequestedAt: Date | null;
|
||||||
magicRevokedAt: Date | null;
|
magicRevokedAt: Date | null;
|
||||||
@ -450,6 +451,7 @@ export async function listMagicDevices(
|
|||||||
magicHostname: true,
|
magicHostname: true,
|
||||||
model: true,
|
model: true,
|
||||||
osVersion: true,
|
osVersion: true,
|
||||||
|
mdmId: true,
|
||||||
magicEnrolledAt: true,
|
magicEnrolledAt: true,
|
||||||
releaseRequestedAt: true,
|
releaseRequestedAt: true,
|
||||||
magicRevokedAt: true,
|
magicRevokedAt: true,
|
||||||
@ -464,6 +466,7 @@ export async function listMagicDevices(
|
|||||||
hostname: d.magicHostname,
|
hostname: d.magicHostname,
|
||||||
model: d.model,
|
model: d.model,
|
||||||
osVersion: d.osVersion,
|
osVersion: d.osVersion,
|
||||||
|
mdmId: d.mdmId,
|
||||||
magicEnrolledAt: d.magicEnrolledAt!,
|
magicEnrolledAt: d.magicEnrolledAt!,
|
||||||
releaseRequestedAt: d.releaseRequestedAt,
|
releaseRequestedAt: d.releaseRequestedAt,
|
||||||
magicRevokedAt: d.magicRevokedAt,
|
magicRevokedAt: d.magicRevokedAt,
|
||||||
|
|||||||
@ -93,6 +93,7 @@ export async function clearUserDeviceMdmId(
|
|||||||
export interface MdmDeviceStatus {
|
export interface MdmDeviceStatus {
|
||||||
enrolled: boolean;
|
enrolled: boolean;
|
||||||
company: string | null;
|
company: string | null;
|
||||||
|
supervised: boolean;
|
||||||
tokenUpdateAt: Date | null;
|
tokenUpdateAt: Date | null;
|
||||||
lastAckAt: Date | null;
|
lastAckAt: Date | null;
|
||||||
lastAppPushAt: Date | null;
|
lastAppPushAt: Date | null;
|
||||||
@ -111,30 +112,33 @@ export async function getMdmStatusByUdid(
|
|||||||
|
|
||||||
// Defensive: only raw parameters reach the query layer below.
|
// Defensive: only raw parameters reach the query layer below.
|
||||||
const result = await pool.query<{
|
const result = await pool.query<{
|
||||||
enrolled: string;
|
unlock_token: Buffer | null;
|
||||||
token_update_at: Date | null;
|
token_update_at: Date | null;
|
||||||
last_ack: Date | null;
|
last_ack: Date | null;
|
||||||
last_app_push_at: Date | null;
|
last_app_push_at: Date | null;
|
||||||
}>(
|
}>(
|
||||||
`SELECT
|
`SELECT
|
||||||
(SELECT count(*)::text FROM devices WHERE id = $1) AS enrolled,
|
d.unlock_token,
|
||||||
(SELECT token_update_at FROM devices WHERE id = $1) AS token_update_at,
|
d.token_update_at,
|
||||||
(SELECT max(updated_at) FROM command_results WHERE id = $1) AS last_ack,
|
(SELECT max(updated_at) FROM command_results WHERE id = d.id) AS last_ack,
|
||||||
(SELECT max(r.updated_at)
|
(SELECT max(r.updated_at)
|
||||||
FROM command_results r
|
FROM command_results r
|
||||||
JOIN commands c ON c.command_uuid = r.command_uuid
|
JOIN commands c ON c.command_uuid = r.command_uuid
|
||||||
WHERE r.id = $1
|
WHERE r.id = d.id
|
||||||
AND c.request_type = 'InstallApplication'
|
AND c.request_type = 'InstallApplication'
|
||||||
AND r.status = 'Acknowledged') AS last_app_push_at`,
|
AND r.status = 'Acknowledged') AS last_app_push_at
|
||||||
|
FROM devices d
|
||||||
|
WHERE d.id = $1`,
|
||||||
[udid],
|
[udid],
|
||||||
);
|
);
|
||||||
|
|
||||||
const row = result.rows[0];
|
const row = result.rows[0];
|
||||||
const enrolled = row ? row.enrolled !== "0" : false;
|
const enrolled = !!row;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enrolled,
|
enrolled,
|
||||||
company: enrolled ? "ReBreak" : null,
|
company: enrolled ? "ReBreak" : null,
|
||||||
|
supervised: enrolled && row?.unlock_token != null,
|
||||||
tokenUpdateAt: row?.token_update_at ?? null,
|
tokenUpdateAt: row?.token_update_at ?? null,
|
||||||
lastAckAt: row?.last_ack ?? null,
|
lastAckAt: row?.last_ack ?? null,
|
||||||
lastAppPushAt: row?.last_app_push_at ?? null,
|
lastAppPushAt: row?.last_app_push_at ?? null,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user