"use strict"; const crypto = require("node:crypto"); const fs = require("node:fs"); const CONNECT_SCHEMA = "guanghu.router-connect/v1"; const APPROVAL_SCHEMA = "guanghu.router-approval/v1"; const MAX_CLOCK_SKEW_SECONDS = 120; class GuanghuRouter { constructor({ devices = [], challengeTtl = 60, routeTokenTtl = 30 } = {}) { this.devices = new Map( devices .filter(validDevice) .map(device => [device.device_id, Object.freeze({ ...device })]), ); this.challengeTtl = Math.max(15, Number(challengeTtl) || 60); this.routeTokenTtl = Math.max(10, Number(routeTokenTtl) || 30); this.challenges = new Map(); this.routeTokens = new Map(); this.connections = new Map(); } challenge(deviceId, now = Date.now() / 1000) { const device = this.devices.get(String(deviceId || "")); if (!device || !device.enabled) return { ok: false, reason: "device_not_registered" }; const challengeId = crypto.randomUUID(); const nonce = randomToken(); this.challenges.set(challengeId, { deviceId: device.device_id, nonce, expiresAt: now + this.challengeTtl, }); return { ok: true, schema: CONNECT_SCHEMA, challengeId, nonce, expiresAt: now + this.challengeTtl, serverTime: now, }; } authorizeConnection(input, now = Date.now() / 1000) { const deviceId = String(input && input.deviceId || ""); const challengeId = String(input && input.challengeId || ""); const challenge = this.challenges.get(challengeId); const device = this.devices.get(deviceId); if (!device || !device.enabled) return { ok: false, reason: "device_not_registered" }; if (!challenge || challenge.deviceId !== deviceId) return { ok: false, reason: "challenge_not_found" }; if (now > challenge.expiresAt) { this.challenges.delete(challengeId); return { ok: false, reason: "challenge_expired" }; } const clientTimestamp = Number(input && input.clientTimestamp); if (!Number.isFinite(clientTimestamp) || Math.abs(now - clientTimestamp) > MAX_CLOCK_SKEW_SECONDS) { return { ok: false, reason: "device_clock_out_of_range" }; } const message = canonicalConnect({ deviceId, challengeId, nonce: challenge.nonce, clientTimestamp, }); if (!verifyDeviceSignature(device, message, input && input.signature)) { return { ok: false, reason: "device_signature_invalid" }; } this.challenges.delete(challengeId); const routeToken = randomToken(); this.routeTokens.set(tokenHash(routeToken), { deviceId, expiresAt: now + this.routeTokenTtl, }); return { ok: true, deviceId, deviceLabel: device.label, ownerId: device.owner_id, routeToken, expiresAt: now + this.routeTokenTtl, }; } open(routeToken, send, now = Date.now() / 1000) { const key = tokenHash(routeToken || ""); const pending = this.routeTokens.get(key); if (!pending) return { ok: false, reason: "route_token_not_found" }; this.routeTokens.delete(key); if (now > pending.expiresAt) return { ok: false, reason: "route_token_expired" }; const device = this.devices.get(pending.deviceId); if (!device || !device.enabled) return { ok: false, reason: "device_not_registered" }; const connectionId = crypto.randomUUID(); const existing = this.connections.get(device.device_id); if (existing) existing.close(now, "replaced"); const connection = { connectionId, device, send, openedAt: now, closed: false, close: (closedAt = Date.now() / 1000, reason = "client_closed") => { if (connection.closed) return; connection.closed = true; if (this.connections.get(device.device_id) === connection) { this.connections.delete(device.device_id); } send({ type: "router.closed", reason, receipt: routeReceipt("offline", device, connectionId, closedAt), }); }, }; this.connections.set(device.device_id, connection); const receipt = routeReceipt("online", device, connectionId, now); send({ type: "router.connected", connection_id: connectionId, receipt }); return { ok: true, state: "online", connectionId, deviceId: device.device_id, ownerId: device.owner_id, receipt, close: connection.close, }; } isApproverOnline(approverId) { return [...this.connections.values()].some(connection => ( !connection.closed && connection.device.owner_id === String(approverId || "") )); } deliver(approverId, order) { const event = { type: "authorization.requested", digest: workorderDigest(order), workorder: order, }; let delivered = 0; for (const connection of this.connections.values()) { if (!connection.closed && connection.device.owner_id === String(approverId || "")) { connection.send(event); delivered += 1; } } return delivered; } verifyApproval(deviceId, order, signature) { const connection = this.connections.get(String(deviceId || "")); if (!connection || connection.closed) return { ok: false, reason: "device_route_offline" }; const digest = workorderDigest(order); const message = canonicalApproval({ deviceId: connection.device.device_id, workorderId: order.id, digest, }); if (!verifyDeviceSignature(connection.device, message, signature)) { return { ok: false, reason: "device_signature_invalid" }; } return { ok: true, authorizerId: connection.device.owner_id, deviceId: connection.device.device_id, digest, }; } } function loadDevices(file) { if (!file || !fs.existsSync(file)) return []; const parsed = JSON.parse(fs.readFileSync(file, "utf8")); if (!Array.isArray(parsed)) throw new Error("HoloLake device registry must be a JSON array"); return parsed.filter(validDevice); } function validDevice(device) { return Boolean( device && typeof device.device_id === "string" && /^[A-Za-z0-9._-]{3,128}$/.test(device.device_id) && typeof device.owner_id === "string" && device.owner_id.length > 0 && typeof device.label === "string" && device.label.length > 0 && typeof device.public_key === "string" && /^[A-Za-z0-9_-]{40,64}$/.test(device.public_key) && typeof device.enabled === "boolean", ); } function verifyDeviceSignature(device, message, signature) { try { const publicKey = crypto.createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: device.public_key }, format: "jwk", }); return crypto.verify( null, Buffer.from(message), publicKey, Buffer.from(String(signature || ""), "base64url"), ); } catch { return false; } } function canonicalConnect({ deviceId, challengeId, nonce, clientTimestamp }) { return [ CONNECT_SCHEMA, String(deviceId), String(challengeId), String(nonce), String(clientTimestamp), ].join("\n"); } function canonicalApproval({ deviceId, workorderId, digest }) { return [ APPROVAL_SCHEMA, String(deviceId), String(workorderId), String(digest), ].join("\n"); } function workorderDigest(order) { const value = { id: String(order.id || ""), persona_id: String(order.persona && order.persona.pid || ""), persona_name: String(order.persona && order.persona.name || ""), target: String(order.target || ""), scope: String(order.scope || ""), action: String(order.action || ""), allowed_actions: [...(order.allowed_actions || order.allowedActions || [order.action])].map(String), description: String(order.description || ""), resource: String(order.resource || ""), created_at: Number(order.createdAt || 0), expires_at: Number(order.expiresAt || 0), }; return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex"); } function routeReceipt(state, device, connectionId, now) { return { schema: "guanghu.route-receipt/v1", receipt_id: crypto.randomUUID(), state, device_id: device.device_id, owner_id: device.owner_id, node_id: "JD-FD-PRIMARY", connection_id: connectionId, occurred_at: now, }; } function randomToken() { return crypto.randomBytes(32).toString("base64url"); } function tokenHash(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); } module.exports = { APPROVAL_SCHEMA, CONNECT_SCHEMA, GuanghuRouter, canonicalApproval, canonicalConnect, loadDevices, workorderDigest, };