fix(edp): resolve managed reader source scope
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
readerBindingRequestHash,
|
||||
safeReaderBinding,
|
||||
} from "./reader-binding.mjs";
|
||||
import { resolveManagedReaderSourceConnection } from "./reader-source-scope.mjs";
|
||||
import { migrate } from "./schema.mjs";
|
||||
import {
|
||||
createWriterToken,
|
||||
@@ -97,6 +98,7 @@ app.get("/healthz", asyncRoute(async (_req, res) => {
|
||||
managedWriterBindings: "digest+idempotent-generation+explicit-revoke",
|
||||
readerBindings: "supported",
|
||||
managedReaderBindings: "digest+idempotent-generation+explicit-revoke",
|
||||
readerSourceScope: "writer-resolved+fail-closed-ambiguity",
|
||||
dataProductDelivery: "snapshot+history+durable-patch",
|
||||
legacyIntake: config.legacyIntakeEnabled ? "migration-only" : "disabled",
|
||||
legacyCapabilityProvisioning: config.provisionerApiEnabled ? "enabled" : "disabled",
|
||||
@@ -388,7 +390,8 @@ app.put("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey", requireMan
|
||||
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
|
||||
const existing = await client.query(
|
||||
`select id, binding_key as "bindingKey", request_hash as "requestHash", generation,
|
||||
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId", provider_id as "providerId",
|
||||
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
|
||||
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
|
||||
from external_data_plane_reader_bindings
|
||||
@@ -397,22 +400,40 @@ app.put("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey", requireMan
|
||||
[bindingKey, policy.generation],
|
||||
);
|
||||
if (existing.rowCount) {
|
||||
const binding = existing.rows[0];
|
||||
let binding = existing.rows[0];
|
||||
if (binding.requestHash !== requestHash) {
|
||||
throw httpError(409, "managed_reader_binding_request_conflict");
|
||||
}
|
||||
if (binding.active !== true || binding.expiresAt !== null) {
|
||||
throw httpError(409, "managed_reader_binding_generation_inactive");
|
||||
}
|
||||
if (!binding.sourceConnectionId) {
|
||||
const sourceConnectionId = await resolveManagedReaderSourceConnection(client, policy);
|
||||
const resolved = await client.query(
|
||||
`update external_data_plane_reader_bindings
|
||||
set source_connection_id = $3
|
||||
where binding_key = $1 and generation = $2 and source_connection_id is null
|
||||
returning id, binding_key as "bindingKey", request_hash as "requestHash", generation,
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId", provider_id as "providerId",
|
||||
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
|
||||
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
[bindingKey, policy.generation, sourceConnectionId],
|
||||
);
|
||||
binding = resolved.rows[0] || binding;
|
||||
}
|
||||
response = { status: 200, idempotent: true, binding };
|
||||
} else {
|
||||
const sourceConnectionId = await resolveManagedReaderSourceConnection(client, policy);
|
||||
const inserted = await client.query(
|
||||
`insert into external_data_plane_reader_bindings (
|
||||
id, token_hash, binding_key, request_hash, generation,
|
||||
tenant_id, connection_id, provider_id, allowed_data_product_ids, expires_at
|
||||
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10)
|
||||
tenant_id, connection_id, source_connection_id, provider_id,
|
||||
allowed_data_product_ids, expires_at
|
||||
) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::jsonb, $11)
|
||||
returning id, binding_key as "bindingKey", generation,
|
||||
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId", provider_id as "providerId",
|
||||
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
|
||||
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
[
|
||||
@@ -423,6 +444,7 @@ app.put("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey", requireMan
|
||||
policy.generation,
|
||||
policy.tenantId,
|
||||
policy.connectionId,
|
||||
sourceConnectionId,
|
||||
policy.providerId,
|
||||
JSON.stringify(policy.allowedDataProductIds),
|
||||
policy.expiresAt,
|
||||
@@ -459,7 +481,8 @@ app.post("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey/generations
|
||||
await client.query("select pg_advisory_xact_lock(hashtextextended($1, 0))", [bindingKey]);
|
||||
const existing = await client.query(
|
||||
`select id, binding_key as "bindingKey", generation,
|
||||
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId", provider_id as "providerId",
|
||||
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
|
||||
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"
|
||||
from external_data_plane_reader_bindings
|
||||
@@ -474,7 +497,8 @@ app.post("/internal/data-plane/v1/reader-bindings/by-key/:bindingKey/generations
|
||||
set active = false, revoked_at = now()
|
||||
where binding_key = $1 and generation = $2 and active = true
|
||||
returning id, binding_key as "bindingKey", generation,
|
||||
tenant_id as "tenantId", connection_id as "connectionId", provider_id as "providerId",
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId", provider_id as "providerId",
|
||||
allowed_data_product_ids as "allowedDataProductIds", expires_at as "expiresAt",
|
||||
active, created_at as "createdAt", rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
[bindingKey, generation],
|
||||
@@ -505,10 +529,11 @@ app.post("/internal/data-plane/v1/reader-bindings", requireProvisionerApi, async
|
||||
const bindingId = randomUUID();
|
||||
const result = await pool.query(
|
||||
`insert into external_data_plane_reader_bindings (
|
||||
id, token_hash, tenant_id, connection_id, provider_id,
|
||||
id, token_hash, tenant_id, connection_id, source_connection_id, provider_id,
|
||||
allowed_data_product_ids, expires_at
|
||||
) values ($1, $2, $3, $4, $5, $6::jsonb, $7)
|
||||
) values ($1, $2, $3, $4, $4, $5, $6::jsonb, $7)
|
||||
returning id, tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId",
|
||||
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
|
||||
expires_at as "expiresAt", active, created_at as "createdAt",
|
||||
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
@@ -533,6 +558,7 @@ app.post("/internal/data-plane/v1/reader-bindings/:bindingId/rotate", requirePro
|
||||
set token_hash = $2, rotated_at = now()
|
||||
where id = $1 and binding_key is null and active = true and expires_at > now()
|
||||
returning id, tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId",
|
||||
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
|
||||
expires_at as "expiresAt", active, created_at as "createdAt",
|
||||
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
@@ -549,6 +575,7 @@ app.post("/internal/data-plane/v1/reader-bindings/:bindingId/revoke", requirePro
|
||||
set active = false, revoked_at = now()
|
||||
where id = $1 and binding_key is null and active = true
|
||||
returning id, tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId",
|
||||
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
|
||||
expires_at as "expiresAt", active, created_at as "createdAt",
|
||||
rotated_at as "rotatedAt", revoked_at as "revokedAt"`,
|
||||
@@ -937,7 +964,9 @@ async function resolveReaderBinding(req) {
|
||||
const token = bearerToken(req);
|
||||
if (!token) throw httpError(401, "reader_binding_unauthorized");
|
||||
const result = await pool.query(
|
||||
`select id, token_hash as "tokenHash", tenant_id as "tenantId", connection_id as "connectionId",
|
||||
`select id, binding_key as "bindingKey", token_hash as "tokenHash",
|
||||
tenant_id as "tenantId", connection_id as "connectionId",
|
||||
source_connection_id as "sourceConnectionId",
|
||||
provider_id as "providerId", allowed_data_product_ids as "allowedDataProductIds",
|
||||
expires_at as "expiresAt", active, created_at as "createdAt",
|
||||
rotated_at as "rotatedAt", revoked_at as "revokedAt"
|
||||
@@ -947,6 +976,9 @@ async function resolveReaderBinding(req) {
|
||||
[hashReaderToken(token)],
|
||||
);
|
||||
if (!result.rowCount) throw httpError(401, "reader_binding_unauthorized");
|
||||
if (result.rows[0].bindingKey && !result.rows[0].sourceConnectionId) {
|
||||
throw httpError(409, "reader_source_scope_unresolved");
|
||||
}
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user