import { constants as fsConstants } from "node:fs"; import { chmod, lstat, mkdir, open } from "node:fs/promises"; import { createHash, createPrivateKey, randomBytes, randomUUID, sign } from "node:crypto"; import { join, resolve } from "node:path"; const SIGNATURE_SCHEMA = "nodedc.external-data-plane.managed-provisioner-request/v1"; const TOKEN_PATTERN = /^ndc_edprb_[A-Za-z0-9_-]{32,512}$/; const IDENTITY_PATTERN = /^[a-z][a-z0-9._:-]{2,127}$/i; const AUDIENCE_PATTERN = /^[a-z][a-z0-9._:/-]{2,255}$/i; function provisionerError(code, statusCode = 500) { return Object.assign(new Error(code), { statusCode }); } function targetAddress(target) { const applicationId = String(target?.application?.id || target?.applicationId || ""); const pageId = String(target?.page?.id || target?.pageId || ""); const bindingId = String(target?.binding?.id || target?.bindingId || ""); if (!/^[0-9a-f-]{36}$/i.test(applicationId) || !IDENTITY_PATTERN.test(pageId) || !IDENTITY_PATTERN.test(bindingId)) { throw provisionerError("foundry_reader_grant_target_invalid", 400); } return { applicationId, pageId, bindingId }; } function targetIdentity(target) { const address = targetAddress(target); const dataProductId = String(target?.binding?.dataProductId || target?.dataProductId || ""); if (!IDENTITY_PATTERN.test(dataProductId)) throw provisionerError("foundry_reader_grant_target_invalid", 400); const { applicationId, pageId, bindingId } = address; return { applicationId, pageId, bindingId, dataProductId }; } function targetDigest(target) { const identity = targetAddress(target); return createHash("sha256") .update(`${identity.applicationId}/${identity.pageId}/${identity.bindingId}`, "utf8") .digest("hex"); } function signingPayload({ audience, serviceId, keyId, method, path, timestamp, nonce, bodySha256 }) { return JSON.stringify({ schemaVersion: SIGNATURE_SCHEMA, audience, serviceId, keyId, method, path, timestamp, nonce, bodySha256, }); } export function createFoundryReaderGrantProvisioner({ dataPlaneUrl, privateKeyFile, grantsDir, serviceId = "nodedc-module-foundry", keyId = "foundry-edp-managed-provisioner-v1", audience = "nodedc-external-data-plane.managed-provisioning.v1", fetchImpl = fetch, now = () => new Date(), randomBytesImpl = randomBytes, randomUUIDImpl = randomUUID, production = process.env.NODE_ENV === "production", }) { const baseUrl = String(dataPlaneUrl || "").trim().replace(/\/$/, ""); const keyPath = String(privateKeyFile || "").trim(); const tokenRoot = String(grantsDir || "").trim(); const configured = Boolean(baseUrl && keyPath && tokenRoot && IDENTITY_PATTERN.test(serviceId) && IDENTITY_PATTERN.test(keyId) && AUDIENCE_PATTERN.test(audience)); let privateKeyPromise = null; async function loadPrivateKey() { if (!configured) throw provisionerError("foundry_reader_grant_provisioner_not_configured", 503); if (!privateKeyPromise) privateKeyPromise = readPrivateKeySecurely(keyPath, { production }); return privateKeyPromise; } async function signedRequest(method, path, value) { const privateKey = await loadPrivateKey(); const body = JSON.stringify(value); const bodySha256 = createHash("sha256").update(body, "utf8").digest("hex"); const timestamp = now().toISOString(); const nonce = randomUUIDImpl(); const signature = sign(null, Buffer.from(signingPayload({ audience, serviceId, keyId, method, path, timestamp, nonce, bodySha256, }), "utf8"), privateKey).toString("base64url"); let response; try { response = await fetchImpl(new URL(path, `${baseUrl}/`), { method, headers: { "content-type": "application/json", accept: "application/json", "x-nodedc-engine-service-id": serviceId, "x-nodedc-engine-key-id": keyId, "x-nodedc-request-audience": audience, "x-nodedc-request-timestamp": timestamp, "x-nodedc-request-nonce": nonce, "x-nodedc-content-sha256": bodySha256, "x-nodedc-request-signature": signature, }, body, signal: AbortSignal.timeout(10_000), }); } catch { throw provisionerError("foundry_reader_grant_provisioner_unavailable", 503); } const payload = await response.json().catch(() => null); if (!response.ok) { const code = String(payload?.error || payload?.code || "foundry_reader_grant_provisioner_rejected"); throw provisionerError(/^[a-z][a-z0-9_]{2,120}$/.test(code) ? code : "foundry_reader_grant_provisioner_rejected", response.status); } return payload; } async function plan(target) { const identity = targetIdentity(target); const payload = await signedRequest("POST", "/internal/data-plane/v1/consumer-reader-bindings/plan", { allowedDataProductIds: [identity.dataProductId], }); const product = Array.isArray(payload?.dataProducts) ? payload.dataProducts.find((candidate) => candidate?.id === identity.dataProductId) : null; if (!product || payload?.sourceScope !== "resolved-server-side") { throw provisionerError("foundry_reader_grant_plan_response_invalid", 502); } return { product, sourceScope: "resolved-server-side" }; } async function ensure(target) { const identity = targetIdentity(target); const digest = targetDigest(identity); const token = await ensureReaderToken(join(resolve(tokenRoot), digest), { production, randomBytesImpl, }); const bindingKey = `fndrc-${digest}`; const payload = await signedRequest( "PUT", `/internal/data-plane/v1/consumer-reader-bindings/by-key/${encodeURIComponent(bindingKey)}`, { allowedDataProductIds: [identity.dataProductId], expiresAt: null, generation: 1, capabilityDigest: createHash("sha256").update(token, "utf8").digest("hex"), }, ); const binding = payload?.readerBinding; if (binding?.bindingKey !== bindingKey || binding?.generation !== 1 || binding?.active !== true || binding?.expiresAt !== null || binding?.sourceScope !== "resolved-server-side" || !Array.isArray(binding?.allowedDataProductIds) || !binding.allowedDataProductIds.includes(identity.dataProductId)) { throw provisionerError("foundry_reader_grant_ensure_response_invalid", 502); } return { ensured: true, idempotent: payload?.idempotent === true, generation: 1, sourceScope: "resolved-server-side" }; } async function readToken(target) { if (!tokenRoot) return null; return readReaderToken(join(resolve(tokenRoot), targetDigest(target)), { production, missing: null }); } return { configured, plan, ensure, readToken }; } async function readPrivateKeySecurely(path, { production }) { let handle; try { handle = await open(path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0)); } catch { throw provisionerError("foundry_reader_grant_private_key_unreadable", 503); } try { const metadata = await handle.stat(); if (!metadata.isFile() || metadata.size < 80 || metadata.size > 8192 || (metadata.mode & 0o022) !== 0 || (production && metadata.uid !== 0)) { throw provisionerError("foundry_reader_grant_private_key_invalid", 503); } const pem = await handle.readFile("utf8"); const privateKey = createPrivateKey(pem); if (privateKey.type !== "private" || privateKey.asymmetricKeyType !== "ed25519") { throw provisionerError("foundry_reader_grant_private_key_invalid", 503); } return privateKey; } catch (error) { if (String(error?.message || "").startsWith("foundry_reader_grant_")) throw error; throw provisionerError("foundry_reader_grant_private_key_invalid", 503); } finally { await handle.close(); } } async function ensureReaderToken(path, { production, randomBytesImpl }) { const existing = await readReaderToken(path, { production, missing: null }); if (existing) return existing; const directory = resolve(path, ".."); await mkdir(directory, { recursive: true, mode: 0o700 }); await chmod(directory, 0o700); const directoryMetadata = await lstat(directory); if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink() || (directoryMetadata.mode & 0o077) !== 0 || (production && directoryMetadata.uid !== 0)) { throw provisionerError("foundry_reader_grant_store_invalid", 503); } const token = `ndc_edprb_${randomBytesImpl(32).toString("base64url")}`; let handle; try { handle = await open( path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | (fsConstants.O_NOFOLLOW || 0), 0o400, ); await handle.writeFile(`${token}\n`, "utf8"); await handle.sync(); await handle.chmod(0o400); return token; } catch (error) { if (error?.code === "EEXIST") return readConcurrentlyCreatedReaderToken(path, { production }); throw provisionerError("foundry_reader_grant_store_unavailable", 503); } finally { await handle?.close(); } } async function readConcurrentlyCreatedReaderToken(path, { production }) { for (let attempt = 0; attempt < 10; attempt += 1) { try { const token = await readReaderToken(path, { production, missing: null }); if (token) return token; } catch (error) { if (error?.message !== "foundry_reader_grant_invalid") throw error; } await new Promise((resolveDelay) => setTimeout(resolveDelay, 10)); } throw provisionerError("foundry_reader_grant_store_unavailable", 503); } async function readReaderToken(path, { production, missing }) { let handle; try { handle = await open(path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0)); } catch (error) { if (error?.code === "ENOENT") return missing; throw provisionerError("foundry_reader_grant_unavailable", 503); } try { const metadata = await handle.stat(); if (!metadata.isFile() || metadata.size < 2 || metadata.size > 1024 || (metadata.mode & 0o222) !== 0 || (production && metadata.uid !== 0)) { throw provisionerError("foundry_reader_grant_invalid", 503); } const token = (await handle.readFile("utf8")).trim(); if (!TOKEN_PATTERN.test(token)) throw provisionerError("foundry_reader_grant_invalid", 503); return token; } finally { await handle.close(); } }