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,
};
}