147 lines
3.4 KiB
TypeScript
147 lines
3.4 KiB
TypeScript
import { usePrisma } from "../utils/prisma";
|
|
import pg from "pg";
|
|
|
|
const { Pool } = pg;
|
|
|
|
let _mdmPool: pg.Pool | null = null;
|
|
|
|
/**
|
|
* Lazily initialised pg.Pool against the NanoMDM Postgres.
|
|
* Connection string comes from runtimeConfig.mdmDatabaseUrl.
|
|
*/
|
|
function useMdmPool(): pg.Pool {
|
|
if (_mdmPool) return _mdmPool;
|
|
|
|
const config = useRuntimeConfig();
|
|
const connectionString = (config as any).mdmDatabaseUrl;
|
|
|
|
if (!connectionString) {
|
|
throw new Error("MDM_DATABASE_URL not configured");
|
|
}
|
|
|
|
_mdmPool = new Pool({
|
|
connectionString,
|
|
// NanoMDM queries are point lookups — keep pool small.
|
|
max: 5,
|
|
connectionTimeoutMillis: 5000,
|
|
queryTimeout: 5000,
|
|
});
|
|
|
|
return _mdmPool;
|
|
}
|
|
|
|
export interface UserDeviceMdmRecord {
|
|
id: string;
|
|
userId: string;
|
|
deviceId: string;
|
|
platform: string;
|
|
mdmId: string | null;
|
|
}
|
|
|
|
const USER_DEVICE_MDM_SELECT = {
|
|
id: true,
|
|
userId: true,
|
|
deviceId: true,
|
|
platform: true,
|
|
mdmId: true,
|
|
} as const;
|
|
|
|
/**
|
|
* Find a user's iOS device by Capacitor deviceId.
|
|
*/
|
|
export async function getUserDeviceByDeviceId(
|
|
userId: string,
|
|
deviceId: string,
|
|
platform: string = "ios",
|
|
): Promise<UserDeviceMdmRecord | null> {
|
|
const db = usePrisma();
|
|
return db.userDevice.findFirst({
|
|
where: { userId, deviceId, platform },
|
|
select: USER_DEVICE_MDM_SELECT,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Persist the NanoMDM UDID for a user's device.
|
|
*/
|
|
export async function setUserDeviceMdmId(
|
|
userId: string,
|
|
deviceId: string,
|
|
mdmId: string,
|
|
): Promise<void> {
|
|
const db = usePrisma();
|
|
await db.userDevice.updateMany({
|
|
where: { userId, deviceId, platform: "ios" },
|
|
data: { mdmId },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clear the stored NanoMDM UDID (e.g. device no longer enrolled).
|
|
*/
|
|
export async function clearUserDeviceMdmId(
|
|
userId: string,
|
|
deviceId: string,
|
|
): Promise<void> {
|
|
const db = usePrisma();
|
|
await db.userDevice.updateMany({
|
|
where: { userId, deviceId, platform: "ios" },
|
|
data: { mdmId: null },
|
|
});
|
|
}
|
|
|
|
export interface MdmDeviceStatus {
|
|
enrolled: boolean;
|
|
company: string | null;
|
|
supervised: boolean;
|
|
tokenUpdateAt: Date | null;
|
|
lastAckAt: Date | null;
|
|
lastAppPushAt: Date | null;
|
|
}
|
|
|
|
/**
|
|
* Query NanoMDM Postgres for a device by UDID.
|
|
*
|
|
* Throws if the MDM DB is unreachable — callers should treat this as an
|
|
* infra/runtime error and not cache a negative result.
|
|
*/
|
|
export async function getMdmStatusByUdid(
|
|
udid: string,
|
|
): Promise<MdmDeviceStatus> {
|
|
const pool = useMdmPool();
|
|
|
|
// Defensive: only raw parameters reach the query layer below.
|
|
const result = await pool.query<{
|
|
unlock_token: Buffer | null;
|
|
token_update_at: Date | null;
|
|
last_ack: Date | null;
|
|
last_app_push_at: Date | null;
|
|
}>(
|
|
`SELECT
|
|
d.unlock_token,
|
|
d.token_update_at,
|
|
(SELECT max(updated_at) FROM command_results WHERE id = d.id) AS last_ack,
|
|
(SELECT max(r.updated_at)
|
|
FROM command_results r
|
|
JOIN commands c ON c.command_uuid = r.command_uuid
|
|
WHERE r.id = d.id
|
|
AND c.request_type = 'InstallApplication'
|
|
AND r.status = 'Acknowledged') AS last_app_push_at
|
|
FROM devices d
|
|
WHERE d.id = $1`,
|
|
[udid],
|
|
);
|
|
|
|
const row = result.rows[0];
|
|
const enrolled = !!row;
|
|
|
|
return {
|
|
enrolled,
|
|
company: enrolled ? "ReBreak" : null,
|
|
supervised: enrolled && row?.unlock_token != null,
|
|
tokenUpdateAt: row?.token_update_at ?? null,
|
|
lastAckAt: row?.last_ack ?? null,
|
|
lastAppPushAt: row?.last_app_push_at ?? null,
|
|
};
|
|
}
|