// Sign a Plynth robot API call with Node's built-in crypto (Node 18 or later), and send it. // GATEWAY_URL=https://... GATEWAY_ROBOT_ID=... GATEWAY_ROBOT_SECRET=... node sign.mjs [METHOD PATH [BODY]] // node sign.mjs --vector # the test vectors' signatures, one per line import { createHmac } from "node:crypto"; // Hex HMAC-SHA256 keyed with the secret's own characters, over METHOD \n PATH \n TIMESTAMP \n and then the body. // A string key is used as its UTF-8 bytes: the secret as given, never hex-decoded. function sign(secret, method, path, timestamp, body = Buffer.alloc(0)) { const message = Buffer.concat([Buffer.from(`${method}\n${path}\n${timestamp}\n`), body]); return createHmac("sha256", secret).update(message).digest("hex"); } // The body is a Buffer, sent as signed: never serialize it again after sign(). async function call(method, path, body = Buffer.alloc(0)) { const { GATEWAY_URL, GATEWAY_ROBOT_ID, GATEWAY_ROBOT_SECRET } = process.env; const timestamp = String(Math.floor(Date.now() / 1000)); // seconds, not milliseconds const headers = { "X-Robot-Id": GATEWAY_ROBOT_ID, "X-Timestamp": timestamp, "X-Signature": sign(GATEWAY_ROBOT_SECRET, method, path, timestamp, body) }; if (body.length) headers["Content-Type"] = "application/json"; const response = await fetch(GATEWAY_URL.replace(/\/+$/, "") + path, { method, headers, body: body.length ? body : undefined, signal: AbortSignal.timeout(10000) }); const text = await response.text(); if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`); return text; } const args = process.argv.slice(2); if (args[0] === "--vector") { const key = "test-secret-for-docs-only-0123456789abcdef"; console.log(sign(key, "GET", "/v1/me", "1767225600")); console.log(sign(key, "POST", "/v1/passages", "1767225600", Buffer.from('{"passage_id":"3f2b9c1e-6a4d-4e8b-9f1a-2c7d5e0b8a14","resource_id":"door"}'))); } else { const [method = "GET", path = "/v1/me", body = ""] = args; try { console.log(await call(method, path, Buffer.from(body))); } catch (e) { console.error(e.message); process.exitCode = 1; } }