import { createHash, timingSafeEqual } from "node:crypto"; import { createServer } from "node:http"; import { hashRestrictedIdentifier, maskRestrictedIdentifier, normalizeRestrictedIdentifier, } from "../../../packages/device-protocol-contract/src/index.mjs"; import { createDeviceGatewayIngest } from "./gateway-ingest.mjs"; import { normalizeManagementActor, } from "./project-management.mjs"; import { normalizeDeviceManagementCommand } from "./management-command.mjs"; const managementRoutes = new Map([ ["/internal/v1/management/owner-scopes:ensure", "owner_scope.ensure"], ["/internal/v1/management/projects:ensure", "project.ensure"], ["/internal/v1/management/collections:ensure", "collection.ensure"], ["/internal/v1/management/project-grants:upsert", "project_grant.upsert"], ["/internal/v1/management/adapter-packages:ensure", "adapter_package.ensure"], ["/internal/v1/management/adapter-versions:register", "adapter_version.register"], ["/internal/v1/management/model-profiles:register", "model_profile.register"], ["/internal/v1/management/edges:ensure", "edge.ensure"], ["/internal/v1/management/routes:ensure", "route.ensure"], ["/internal/v1/management/enrollment-intents:ensure", "enrollment_intent.ensure"], ["/internal/v1/management/devices:claim", "device.claim"], ["/internal/v1/management/devices:transfer", "device.transfer"], ["/internal/v1/management/discoveries:reject", "discovery.reject"], ["/internal/v1/management/discoveries:expire", "discovery.expire"], [ "/internal/v1/management/device-credential-bindings:upsert", "device_credential_binding.upsert", ], [ "/internal/v1/management/device-credential-bindings:revoke", "device_credential_binding.revoke", ], ["/internal/v1/management/device-bindings:ensure", "device_binding.ensure"], ["/internal/v1/management/device-bindings:revoke", "device_binding.revoke"], [ "/internal/v1/management/device-configuration-revisions:create", "device_configuration_revision.create", ], [ "/internal/v1/management/device-configurations:set-desired", "device_configuration_desired.set", ], ]); export function createControlCoreApp({ repository, gatewayToken = "", identifierPepper = "", discoveryIngestEnabled = false, managementApiEnabled = false, managementToken = "", gatewayIngest = null, edgeChannelStatusProvider = null, typedCommandRuntime = null, } = {}) { if (!repository || typeof repository.health !== "function") { throw new TypeError("device_repository_required"); } if (discoveryIngestEnabled) { if (typeof repository.upsertQuarantineDiscovery !== "function") { throw new TypeError("device_discovery_repository_required"); } if (typeof repository.acceptAdapterMessage !== "function") { throw new TypeError("device_gateway_message_repository_required"); } if (typeof gatewayToken !== "string" || gatewayToken.length < 32) { throw new TypeError("device_gateway_token_invalid"); } if (typeof identifierPepper !== "string" || identifierPepper.length < 32) { throw new TypeError("device_identifier_pepper_invalid"); } } if (managementApiEnabled) { if (typeof repository.executeManagementCommand !== "function") { throw new TypeError("device_management_repository_required"); } if (typeof managementToken !== "string" || managementToken.length < 32) { throw new TypeError("device_management_token_invalid"); } if (typeof identifierPepper !== "string" || identifierPepper.length < 32) { throw new TypeError("device_identifier_pepper_invalid"); } } const ingest = discoveryIngestEnabled ? gatewayIngest ?? createDeviceGatewayIngest({ repository, identifierPepper }) : gatewayIngest; if ( ingest && ( typeof ingest.observeDiscovery !== "function" || typeof ingest.acceptMessage !== "function" ) ) { throw new TypeError("device_gateway_ingest_invalid"); } if ( typedCommandRuntime != null && ( typeof typedCommandRuntime.planServicePing !== "function" || typeof typedCommandRuntime.status !== "function" ) ) { throw new TypeError("device_typed_command_runtime_invalid"); } if ( edgeChannelStatusProvider != null && typeof edgeChannelStatusProvider !== "function" ) { throw new TypeError("device_edge_channel_status_provider_invalid"); } const server = createServer(async (request, response) => { response.setHeader("Content-Type", "application/json; charset=utf-8"); response.setHeader("Cache-Control", "no-store"); response.setHeader("X-Content-Type-Options", "nosniff"); try { const requestUrl = new URL( request.url || "/", `http://${request.headers.host || "127.0.0.1"}`, ); if (request.method === "GET" && requestUrl.pathname === "/healthz") { const database = await repository.health(); return writeJson(response, 200, { ok: true, service: "nodedc-device-control-core", database, discoveryIngest: discoveryIngestEnabled ? "enabled" : "disabled", managementApi: managementApiEnabled ? "enabled" : "disabled", edgeChannels: edgeChannelStatusProvider ? edgeChannelStatusProvider() : { enabled: false, configured: 0, accepted: 0, degraded: 0 }, commandTransport: typedCommandRuntime ? "typed-service-ping-v1" : "disabled", }); } if ( request.method === "POST" && requestUrl.pathname === "/internal/v1/commands:service-ping" ) { if (!managementApiEnabled || !typedCommandRuntime) { return writeJson(response, 404, { ok: false, error: "device_command_transport_disabled", }); } if (!matchesBearer(request.headers.authorization, managementToken)) { return writeJson(response, 401, { ok: false, error: "device_management_auth_required", }); } const idempotencyKey = normalizeIdempotencyKey( request.headers["idempotency-key"], ); const actor = managementActorFromHeaders(request.headers); const input = await readJsonBody(request, 8 * 1024); const execution = await typedCommandRuntime.planServicePing({ idempotencyKey, actor, input, }); response.setHeader("Idempotency-Key", idempotencyKey); response.setHeader( "Idempotency-Replayed", execution.replayed ? "true" : "false", ); return writeJson(response, 200, { ok: true, replayed: execution.replayed, result: execution.command, }); } const managementCommandKind = managementRoutes.get(requestUrl.pathname); if (request.method === "POST" && managementCommandKind) { if (!managementApiEnabled) { return writeJson(response, 404, { ok: false, error: "device_management_api_disabled", }); } if (!matchesBearer(request.headers.authorization, managementToken)) { return writeJson(response, 401, { ok: false, error: "device_management_auth_required", }); } const idempotencyKey = normalizeIdempotencyKey( request.headers["idempotency-key"], ); const actor = managementActorFromHeaders(request.headers); const input = await readJsonBody(request, 64 * 1024); const protectedInput = managementCommandKind === "enrollment_intent.ensure" ? protectEnrollmentIdentifier(input, identifierPepper) : input; const command = normalizeDeviceManagementCommand( managementCommandKind, protectedInput, ); const requestDigest = managementRequestDigest({ actor, commandKind: managementCommandKind, command, }); const execution = await repository.executeManagementCommand({ idempotencyKey, commandKind: managementCommandKind, requestDigest, actor, command, }); response.setHeader("Idempotency-Key", idempotencyKey); response.setHeader( "Idempotency-Replayed", execution.replayed ? "true" : "false", ); return writeJson(response, 200, { ok: true, replayed: execution.replayed, result: execution.result, }); } if ( request.method === "GET" && requestUrl.pathname === "/internal/v1/query/projects" ) { if (!managementApiEnabled) { return writeJson(response, 404, { ok: false, error: "device_management_api_disabled", }); } if (!matchesBearer(request.headers.authorization, managementToken)) { return writeJson(response, 401, { ok: false, error: "device_management_auth_required", }); } if (typeof repository.listAccessibleProjects !== "function") { return writeJson(response, 503, { ok: false, error: "device_query_repository_unavailable", }); } const actor = managementActorFromHeaders(request.headers); const projects = await repository.listAccessibleProjects(actor); return writeJson(response, 200, { ok: true, projects }); } const workspaceProjectId = projectWorkspaceId(requestUrl.pathname); if (request.method === "GET" && workspaceProjectId) { if (!managementApiEnabled) { return writeJson(response, 404, { ok: false, error: "device_management_api_disabled", }); } if (!matchesBearer(request.headers.authorization, managementToken)) { return writeJson(response, 401, { ok: false, error: "device_management_auth_required", }); } if (typeof repository.getProjectWorkspace !== "function") { return writeJson(response, 503, { ok: false, error: "device_query_repository_unavailable", }); } const actor = managementActorFromHeaders(request.headers); const workspace = await repository.getProjectWorkspace( actor, workspaceProjectId, { commandTransport: typedCommandRuntime ? "typed-service-ping-v1" : "disabled", }, ); return writeJson(response, 200, { ok: true, workspace }); } if ( request.method === "POST" && requestUrl.pathname === "/internal/v1/device-discoveries:observe" ) { if (!discoveryIngestEnabled) { return writeJson(response, 404, { ok: false, error: "device_discovery_ingest_disabled", }); } if (!matchesBearer(request.headers.authorization, gatewayToken)) { return writeJson(response, 401, { ok: false, error: "device_gateway_auth_required", }); } const input = await readJsonBody(request, 32 * 1024); const discovery = await ingest.observeDiscovery(input); return writeJson(response, discovery.created ? 201 : 200, { ok: true, created: discovery.created, discovery: discovery.value, }); } if ( request.method === "POST" && requestUrl.pathname === "/internal/v1/gateway/messages:accept" ) { if (!discoveryIngestEnabled) { return writeJson(response, 404, { ok: false, error: "device_gateway_message_ingest_disabled", }); } if (!matchesBearer(request.headers.authorization, gatewayToken)) { return writeJson(response, 401, { ok: false, error: "device_gateway_auth_required", }); } const input = await readJsonBody(request, 1024 * 1024); const receipt = await ingest.acceptMessage(input); const acceptance = receipt.value; return writeJson(response, acceptance.replayed ? 200 : 201, { ok: true, acceptance, }); } return writeJson(response, 404, { ok: false, error: "device_control_core_route_not_found", }); } catch (error) { const status = Number(error?.statusCode || 400); return writeJson( response, Number.isInteger(status) && status >= 400 && status < 600 ? status : 500, { ok: false, error: safeErrorCode(error), }, ); } }); return server; } function protectEnrollmentIdentifier(input, identifierPepper) { if (!input || typeof input !== "object" || Array.isArray(input)) { throw new TypeError("device_enrollment_input_invalid"); } const allowedKeys = new Set([ "projectRef", "enrollmentKey", "routeRef", "modelProfileRef", "displayName", "identifier", "expiresAt", ]); for (const key of Object.keys(input)) { if (!allowedKeys.has(key)) { throw new TypeError("device_enrollment_input_field_unexpected"); } } const identifier = normalizeRestrictedIdentifier(input.identifier); return Object.freeze({ projectRef: input.projectRef, enrollmentKey: input.enrollmentKey, routeRef: input.routeRef, modelProfileRef: input.modelProfileRef, displayName: input.displayName, identifierKind: identifier.kind, identifierDigest: hashRestrictedIdentifier(identifier, identifierPepper), identifierMasked: maskRestrictedIdentifier(identifier), expiresAt: input.expiresAt, }); } function projectWorkspaceId(pathname) { const match = pathname.match( /^\/internal\/v1\/query\/projects\/([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\/workspace$/i, ); return match?.[1]?.toLowerCase() ?? null; } function managementActorFromHeaders(headers) { return normalizeManagementActor({ userRef: singleHeader(headers["x-nodedc-user-ref"]), hubRole: singleHeader(headers["x-nodedc-hub-role"]), groupRefs: commaSeparatedHeader(headers["x-nodedc-group-refs"]), ownerScopes: ownerScopeHeader(headers["x-nodedc-owner-scopes"]), }); } function ownerScopeHeader(value) { return commaSeparatedHeader(value).map((claim) => { const separatorIndex = claim.indexOf("="); if (separatorIndex < 1 || separatorIndex === claim.length - 1) { throw new TypeError("device_actor_owner_scopes_invalid"); } return { scopeKind: claim.slice(0, separatorIndex), ownerRef: claim.slice(separatorIndex + 1), }; }); } function commaSeparatedHeader(value) { const header = singleHeader(value, true); if (!header) return []; return header.split(",").map((item) => item.trim()).filter(Boolean); } function singleHeader(value, optional = false) { if (Array.isArray(value)) throw new TypeError("device_management_header_invalid"); if (value == null || value === "") { if (optional) return ""; throw new TypeError("device_management_header_required"); } if (typeof value !== "string" || value.length > 4096) { throw new TypeError("device_management_header_invalid"); } return value.trim(); } function normalizeIdempotencyKey(value) { const key = singleHeader(value); if (!/^[\x21-\x7e]{8,256}$/.test(key)) { const error = new Error("device_idempotency_key_invalid"); error.statusCode = 400; throw error; } return key; } function managementRequestDigest(value) { return `sha256:${createHash("sha256") .update(JSON.stringify(value), "utf8") .digest("hex")}`; } function matchesBearer(header, expected) { if (typeof header !== "string" || !header.startsWith("Bearer ")) return false; const actual = Buffer.from(header.slice("Bearer ".length), "utf8"); const required = Buffer.from(expected, "utf8"); return ( actual.length === required.length && required.length > 0 && timingSafeEqual(actual, required) ); } async function readJsonBody(request, maxBytes) { const chunks = []; let size = 0; for await (const chunk of request) { size += chunk.length; if (size > maxBytes) { const error = new Error("device_request_body_too_large"); error.statusCode = 413; throw error; } chunks.push(chunk); } if (size === 0) throw new TypeError("device_request_body_required"); try { return JSON.parse(Buffer.concat(chunks).toString("utf8")); } catch { throw new TypeError("device_request_json_invalid"); } } function writeJson(response, status, body) { response.statusCode = status; return response.end(`${JSON.stringify(body)}\n`); } function safeErrorCode(error) { const value = error instanceof Error ? error.message : "device_control_error"; return /^[a-z0-9_:-]{1,128}$/.test(value) ? value : "device_control_error"; }