import assert from "node:assert/strict"; import { once } from "node:events"; import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; import { createServer as createHttpServer } from "node:http"; import { createServer } from "node:net"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { spawn } from "node:child_process"; const foundryRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const gatewayRoot = resolve(foundryRoot, "../platform/services/map-gateway"); const root = await mkdtemp(join(tmpdir(), "nodedc-foundry-platform-settings-smoke-")); const gatewayPort = await freePort(); const foundryPort = await freePort(); const providerPort = await freePort(); const internalSecret = "foundry-gateway-admin-smoke-secret"; const providerToken = "cesium-private-token-must-not-reach-browser"; const ionReferer = "https://foundry.example.test/page-library/"; const adminSecretFile = join(root, "map-gateway-admin-secret"); let gateway; let foundry; let provider; try { await writeFile(adminSecretFile, `${"a".repeat(64)}\n`, { mode: 0o640 }); await chmod(adminSecretFile, 0o640); provider = createCesiumIonMock(providerToken); provider.listen(providerPort, "127.0.0.1"); await once(provider, "listening"); gateway = spawn(process.execPath, ["src/server.mjs"], { cwd: gatewayRoot, env: { ...process.env, PORT: String(gatewayPort), MAP_CACHE_DIR: join(root, "gateway-cache"), MAP_GATEWAY_ALLOW_ANONYMOUS: "true", MAP_CACHE_MODE: "readwrite", NODE_ENV: "test", CESIUM_ION_TOKEN: "", CESIUM_ION_API_BASE_URL: `http://127.0.0.1:${providerPort}/`, NODEDC_MAP_GATEWAY_ADMIN_SECRET_FILE: adminSecretFile, }, stdio: ["ignore", "pipe", "pipe"], }); await waitForService(gatewayPort, gateway, "gateway"); foundry = spawn(process.execPath, ["server/catalog-server.mjs"], { cwd: foundryRoot, env: { ...process.env, NODE_ENV: "development", HOST: "127.0.0.1", PORT: String(foundryPort), FOUNDRY_RUNTIME_DIR: join(root, "foundry-runtime"), NODEDC_FOUNDRY_AUTH_REQUIRED: "false", NODEDC_MAP_GATEWAY_INTERNAL_URL: `http://127.0.0.1:${gatewayPort}`, NODEDC_INTERNAL_ACCESS_TOKEN: internalSecret, NODEDC_MAP_GATEWAY_ADMIN_SECRET_FILE: adminSecretFile, FOUNDRY_PUBLIC_URL: "https://foundry.example.test", }, stdio: ["ignore", "pipe", "pipe"], }); await waitForService(foundryPort, foundry, "foundry"); const profile = await fetch(`http://127.0.0.1:${foundryPort}/api/session/profile`).then((response) => response.json()); assert.equal(profile.access.role, "admin"); const initial = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, { headers: { referer: ionReferer } }); assert.equal(initial.status, 200); assert.equal((await initial.json()).configured, false); const saved = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, { method: "PUT", headers: { "content-type": "application/json", referer: ionReferer }, body: JSON.stringify({ token: providerToken }), }); const savedRaw = await saved.text(); assert.equal(saved.status, 200); assert.equal(savedRaw.includes(providerToken), false); assert.equal(JSON.parse(savedRaw).configured, true); assert.equal(JSON.parse(savedRaw).verification, "verified"); const status = await fetch(`http://127.0.0.1:${foundryPort}/api/platform-settings/cesium-ion`, { headers: { referer: ionReferer } }); const statusRaw = await status.text(); assert.equal(status.status, 200); assert.equal(statusRaw.includes(providerToken), false); assert.equal(JSON.parse(statusRaw).configured, true); assert.equal(JSON.parse(statusRaw).verification, "verified"); const runtime = await fetch(`http://127.0.0.1:${foundryPort}/api/map/runtime-config`); assert.equal(runtime.status, 200); const runtimeBody = await runtime.json(); assert.equal(runtimeBody.gatewayReady, true); assert.equal(runtimeBody.assetEndpointBase, "/api/map-gateway/api/map/ion/assets"); const terrain = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/api/map/ion/assets/1/endpoint`, { headers: { referer: ionReferer } }); const terrainRaw = await terrain.text(); assert.equal(terrain.status, 200); assert.equal(terrainRaw.includes(providerToken), false); assert.equal(JSON.parse(terrainRaw).credentialMode, "gateway"); const buildings = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/api/map/ion/assets/96188/endpoint`, { headers: { referer: ionReferer } }); const buildingsRaw = await buildings.text(); assert.equal(buildings.status, 200); assert.equal(buildingsRaw.includes(providerToken), false); assert.equal(JSON.parse(buildingsRaw).credentialMode, "gateway"); const health = await fetch(`http://127.0.0.1:${foundryPort}/api/map-gateway/healthz`); assert.equal(health.status, 200); assert.equal((await health.json()).cache.persistent, true); console.log("ok: Foundry admin settings use the private signed Gateway path without returning the token"); } finally { await stop(foundry); await stop(gateway); if (provider?.listening) { provider.close(); await once(provider, "close"); } await rm(root, { recursive: true, force: true }); } function createCesiumIonMock(expectedToken) { return createHttpServer((request, response) => { if (request.headers.authorization !== `Bearer ${expectedToken}`) { response.writeHead(401, { "content-type": "application/json" }); response.end(JSON.stringify({ error: "unauthorized" })); return; } if (request.headers.referer !== ionReferer) { response.writeHead(401, { "content-type": "application/json" }); response.end(JSON.stringify({ error: "referer_required" })); return; } const assetId = new URL(request.url || "/", "http://127.0.0.1").pathname.match(/^\/v1\/assets\/(\d+)\/endpoint$/)?.[1]; const body = assetId === "1" ? { type: "TERRAIN", url: "https://assets.ion.cesium.com/1/", accessToken: "terrain-private-endpoint-key" } : assetId === "2" ? { type: "IMAGERY", externalType: "BING", options: { url: "https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial", key: "bing-private-endpoint-key", mapStyle: "Aerial" } } : assetId === "96188" ? { type: "3DTILES", url: "https://assets.ion.cesium.com/96188/", accessToken: "buildings-private-endpoint-key" } : null; if (!body) { response.writeHead(404, { "content-type": "application/json" }); response.end(JSON.stringify({ error: "not_found" })); return; } response.writeHead(200, { "content-type": "application/json" }); response.end(JSON.stringify(body)); }); } async function freePort() { const server = createServer(); server.listen(0, "127.0.0.1"); await once(server, "listening"); const address = server.address(); assert(address && typeof address === "object"); const port = address.port; server.close(); await once(server, "close"); return port; } async function waitForService(port, child, label) { let output = ""; child.stderr.on("data", (chunk) => { output += String(chunk); }); for (let attempt = 0; attempt < 80; attempt += 1) { if (child.exitCode !== null) throw new Error(`${label}_exited:${child.exitCode}:${output}`); try { const response = await fetch(`http://127.0.0.1:${port}/healthz`); if (response.ok) return; } catch { /* service is still starting */ } await new Promise((resolve) => setTimeout(resolve, 50)); } throw new Error(`${label}_start_timeout:${output}`); } async function stop(child) { if (child && !child.killed) { child.kill("SIGTERM"); await once(child, "exit").catch(() => undefined); } }