@loadstrike/loadstrike-sdk 1.0.30001 → 1.0.30401

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -638,9 +638,13 @@ class WebSocketEndpointDefinitionModel extends TrafficEndpointDefinitionModel {
638
638
  parsed = new URL(url);
639
639
  }
640
640
  catch {
641
+ // Diagnostic text only; no connection is opened here.
642
+ // nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
641
643
  throw new Error("Url must be an absolute ws:// or wss:// URI.");
642
644
  }
643
645
  if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
646
+ // Diagnostic text only; no connection is opened here.
647
+ // nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
644
648
  throw new Error("Url must be an absolute ws:// or wss:// URI.");
645
649
  }
646
650
  if (this.ConnectTimeoutSeconds <= 0) {
@@ -1275,11 +1279,13 @@ function validateHttpAuthOptionsModel(target) {
1275
1279
  }
1276
1280
  function validateKafkaSaslOptionsModel(target) {
1277
1281
  switch (normalizeToken(target.Mechanism)) {
1278
- case "oauthbearer":
1279
- if (!String(target.OAuthBearerTokenEndpointUrl ?? "").trim()) {
1280
- throw new Error("OAuthBearerTokenEndpointUrl must be provided when SASL mechanism is OAuthBearer.");
1281
- }
1282
+ case "oauthbearer": {
1283
+ const additionalSettings = target.AdditionalSettings;
1284
+ validateKafkaOAuthBearerCredentials(String(target.AccessToken ?? "").trim() || String(target.OAuthBearerToken ?? "").trim(), String(target.OAuthBearerTokenEndpointUrl ?? "").trim()
1285
+ || String(target.TokenEndpoint ?? "").trim()
1286
+ || optionString(additionalSettings, "TokenEndpoint", "tokenEndpoint"), optionString(additionalSettings, "ClientId", "clientId"), optionString(additionalSettings, "ClientSecret", "clientSecret"));
1282
1287
  return;
1288
+ }
1283
1289
  default:
1284
1290
  if (!String(target.Username ?? "").trim()) {
1285
1291
  throw new Error("Username must be provided for SASL authentication.");
@@ -1828,21 +1834,12 @@ class KafkaEndpointAdapter extends CallbackAdapter {
1828
1834
  if (!this.consumerStartPromise) {
1829
1835
  this.consumerStartPromise = (async () => {
1830
1836
  const options = this.endpoint.kafka ?? {};
1831
- const useConfluentClient = shouldUseConfluentKafkaClient(this.endpoint);
1832
1837
  const kafka = await createKafkaClient(this.endpoint);
1833
1838
  const consumer = kafka.consumer(buildKafkaConsumerOptions(this.endpoint));
1834
1839
  await consumer.connect();
1835
- if (useConfluentClient) {
1836
- await consumer.subscribe({
1837
- topics: [optionString(options, "Topic", "topic")]
1838
- });
1839
- }
1840
- else {
1841
- await consumer.subscribe({
1842
- topic: optionString(options, "Topic", "topic"),
1843
- fromBeginning: optionBoolean(options, true, "StartFromEarliest", "startFromEarliest")
1844
- });
1845
- }
1840
+ await consumer.subscribe({
1841
+ topics: [optionString(options, "Topic", "topic")]
1842
+ });
1846
1843
  await consumer.run({
1847
1844
  eachMessage: async ({ message }) => {
1848
1845
  this.queue.push(createBrokerPayload(fromKafkaHeaders(message.headers), bufferToUint8Array(message.value), this.endpoint, headerValue(message.headers, "content-type")));
@@ -3178,9 +3175,13 @@ function validateWebSocketEndpoint(endpoint, mode) {
3178
3175
  parsed = new URL(url);
3179
3176
  }
3180
3177
  catch {
3178
+ // Diagnostic text only; no connection is opened here.
3179
+ // nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
3181
3180
  throw new Error("Url must be an absolute ws:// or wss:// URI.");
3182
3181
  }
3183
3182
  if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
3183
+ // Diagnostic text only; no connection is opened here.
3184
+ // nosemgrep: vulnerability-tools.semgrep-rules.javascript.lang.security.detect-insecure-websocket
3184
3185
  throw new Error("Url must be an absolute ws:// or wss:// URI.");
3185
3186
  }
3186
3187
  const connectMs = optionNumber(options, "ConnectTimeoutMs", "connectTimeoutMs");
@@ -3256,7 +3257,8 @@ function validateKafkaEndpoint(endpoint, mode, hasModeDelegate) {
3256
3257
  function validateKafkaSaslOptions(options) {
3257
3258
  const mechanism = optionString(options, "Mechanism", "mechanism") || "Plain";
3258
3259
  if (mechanism.toLowerCase() === "oauthbearer") {
3259
- requireNonEmptyString(optionString(options, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(asRecordOrEmpty(pickProtocolValue(options, "AdditionalSettings", "additionalSettings")), "TokenEndpoint", "tokenEndpoint"), "OAuthBearerTokenEndpointUrl must be provided when SASL mechanism is OAuthBearer.");
3260
+ const additionalSettings = asRecordOrEmpty(pickProtocolValue(options, "AdditionalSettings", "additionalSettings"));
3261
+ validateKafkaOAuthBearerCredentials(optionString(options, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken"), optionString(options, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(additionalSettings, "TokenEndpoint", "tokenEndpoint"), optionString(additionalSettings, "ClientId", "clientId"), optionString(additionalSettings, "ClientSecret", "clientSecret"));
3260
3262
  return;
3261
3263
  }
3262
3264
  requireNonEmptyString(optionString(options, "Username", "username"), "Username must be provided for SASL authentication.");
@@ -4249,74 +4251,31 @@ function pickProtocolValue(options, ...keys) {
4249
4251
  }
4250
4252
  return undefined;
4251
4253
  }
4252
- function buildKafkaClientOptions(endpoint) {
4253
- const options = endpoint.kafka ?? {};
4254
- const securityProtocol = (optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext").toLowerCase();
4255
- const bootstrapServers = optionString(options, "BootstrapServers", "bootstrapServers")
4256
- .split(",")
4257
- .map((value) => value.trim())
4258
- .filter((value) => value.length > 0);
4259
- const clientOptions = {
4260
- clientId: `${endpoint.name}-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`,
4261
- brokers: bootstrapServers,
4262
- ssl: securityProtocol === "ssl" || securityProtocol === "saslssl"
4263
- };
4264
- if (securityProtocol === "saslplaintext" || securityProtocol === "saslssl") {
4265
- const saslOptions = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
4266
- const mechanism = (optionString(saslOptions, "Mechanism", "mechanism") || "Plain").toLowerCase();
4267
- if (mechanism === "oauthbearer") {
4268
- clientOptions.sasl = {
4269
- mechanism: "oauthbearer",
4270
- oauthBearerProvider: async () => ({
4271
- value: await resolveKafkaOAuthBearerToken(saslOptions)
4272
- })
4273
- };
4274
- }
4275
- else {
4276
- clientOptions.sasl = {
4277
- mechanism: mapKafkaSaslMechanism(mechanism),
4278
- username: optionString(saslOptions, "Username", "username"),
4279
- password: optionString(saslOptions, "Password", "password")
4280
- };
4281
- }
4254
+ let kafkaClientFactoryForTests = null;
4255
+ async function createKafkaClient(endpoint) {
4256
+ if (kafkaClientFactoryForTests) {
4257
+ return await kafkaClientFactoryForTests(endpoint);
4282
4258
  }
4283
- return clientOptions;
4259
+ const { KafkaJS } = await Promise.resolve().then(() => __importStar(require("@confluentinc/kafka-javascript")));
4260
+ return new KafkaJS.Kafka(buildConfluentKafkaClientOptions(endpoint));
4284
4261
  }
4285
- async function createKafkaClient(endpoint) {
4286
- if (shouldUseConfluentKafkaClient(endpoint)) {
4287
- const { KafkaJS } = await Promise.resolve().then(() => __importStar(require("@confluentinc/kafka-javascript")));
4288
- return new KafkaJS.Kafka(buildConfluentKafkaClientOptions(endpoint));
4262
+ function validateKafkaOAuthBearerCredentials(directToken, tokenEndpoint, clientId, clientSecret) {
4263
+ if (directToken.trim()) {
4264
+ return;
4265
+ }
4266
+ if (!tokenEndpoint.trim() || !clientId.trim() || !clientSecret.trim()) {
4267
+ throw new Error("Kafka OAuthBearer requires a direct access token or token endpoint with client credentials.");
4289
4268
  }
4290
- const { Kafka } = await Promise.resolve().then(() => __importStar(require("kafkajs")));
4291
- return new Kafka(buildKafkaClientOptions(endpoint));
4292
4269
  }
4293
4270
  function buildKafkaConsumerOptions(endpoint) {
4294
4271
  const options = endpoint.kafka ?? {};
4295
4272
  const groupId = optionString(options, "ConsumerGroupId", "consumerGroupId");
4296
4273
  const fromBeginning = optionBoolean(options, true, "StartFromEarliest", "startFromEarliest");
4297
- if (shouldUseConfluentKafkaClient(endpoint)) {
4298
- return {
4299
- "group.id": groupId,
4300
- "auto.offset.reset": fromBeginning ? "earliest" : "latest",
4301
- "enable.auto.commit": true
4302
- };
4303
- }
4304
- return { groupId };
4305
- }
4306
- function shouldUseConfluentKafkaClient(endpoint) {
4307
- const options = endpoint.kafka ?? {};
4308
- const securityProtocol = (optionString(options, "SecurityProtocol", "securityProtocol") || "Plaintext").toLowerCase();
4309
- if (securityProtocol !== "saslplaintext" && securityProtocol !== "saslssl") {
4310
- return Object.keys(toStringRecord(pickProtocolValue(options, "ConfluentSettings", "confluentSettings"))).length > 0;
4311
- }
4312
- const saslOptions = asRecordOrEmpty(pickProtocolValue(options, "Sasl", "sasl"));
4313
- const mechanism = (optionString(saslOptions, "Mechanism", "mechanism") || "Plain").toLowerCase();
4314
- const confluentSettings = toStringRecord(pickProtocolValue(options, "ConfluentSettings", "confluentSettings"));
4315
- const additionalSettings = toStringRecord(pickProtocolValue(saslOptions, "AdditionalSettings", "additionalSettings"));
4316
- const hasDirectOAuthToken = mechanism === "oauthbearer" && Boolean(optionString(saslOptions, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken"));
4317
- return mechanism === "gssapi"
4318
- || Object.keys(confluentSettings).length > 0
4319
- || (Object.keys(additionalSettings).length > 0 && !hasDirectOAuthToken);
4274
+ return {
4275
+ "group.id": groupId,
4276
+ "auto.offset.reset": fromBeginning ? "earliest" : "latest",
4277
+ "enable.auto.commit": true
4278
+ };
4320
4279
  }
4321
4280
  function buildConfluentKafkaClientOptions(endpoint) {
4322
4281
  const options = endpoint.kafka ?? {};
@@ -4332,30 +4291,41 @@ function buildConfluentKafkaClientOptions(endpoint) {
4332
4291
  const additionalSettings = toStringRecord(pickProtocolValue(saslOptions, "AdditionalSettings", "additionalSettings"));
4333
4292
  base["sasl.mechanism"] = mapConfluentKafkaSaslMechanism(mechanism);
4334
4293
  if (mechanism.toLowerCase() === "oauthbearer") {
4294
+ const directToken = optionString(saslOptions, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken");
4335
4295
  const tokenEndpoint = optionString(saslOptions, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(additionalSettings, "TokenEndpoint", "tokenEndpoint");
4336
- if (tokenEndpoint) {
4337
- base["sasl.oauthbearer.method"] = "oidc";
4338
- base["sasl.oauthbearer.token.endpoint.url"] = tokenEndpoint;
4339
- }
4340
4296
  const clientId = optionString(additionalSettings, "ClientId", "clientId");
4341
- if (clientId) {
4342
- base["sasl.oauthbearer.client.id"] = clientId;
4343
- }
4344
4297
  const clientSecret = optionString(additionalSettings, "ClientSecret", "clientSecret");
4345
- if (clientSecret) {
4346
- base["sasl.oauthbearer.client.secret"] = clientSecret;
4347
- }
4348
4298
  const scope = optionString(additionalSettings, "Scope", "scope");
4349
- if (scope) {
4350
- base["sasl.oauthbearer.scope"] = scope;
4351
- }
4352
4299
  const grantType = optionString(additionalSettings, "GrantType", "grantType");
4353
- if (grantType) {
4354
- base["sasl.oauthbearer.grant.type"] = grantType;
4355
- }
4356
4300
  const extensions = optionString(additionalSettings, "Extensions", "extensions");
4357
- if (extensions) {
4358
- base["sasl.oauthbearer.extensions"] = extensions;
4301
+ if (directToken) {
4302
+ const principal = optionString(additionalSettings, "Principal", "principal")
4303
+ || optionString(saslOptions, "Username", "username")
4304
+ || "loadstrike";
4305
+ base.oauthbearer_token_refresh_cb = async () => ({
4306
+ tokenValue: directToken,
4307
+ lifetime: Date.now() + 60 * 60 * 1000,
4308
+ principal,
4309
+ extensions: parseKafkaOAuthExtensions(extensions)
4310
+ });
4311
+ }
4312
+ else {
4313
+ if (!tokenEndpoint || !clientId || !clientSecret) {
4314
+ throw new Error("Kafka OAuthBearer requires a direct access token or token endpoint with client credentials.");
4315
+ }
4316
+ base["sasl.oauthbearer.method"] = "oidc";
4317
+ base["sasl.oauthbearer.token.endpoint.url"] = tokenEndpoint;
4318
+ base["sasl.oauthbearer.client.id"] = clientId;
4319
+ base["sasl.oauthbearer.client.secret"] = clientSecret;
4320
+ if (scope) {
4321
+ base["sasl.oauthbearer.scope"] = scope;
4322
+ }
4323
+ if (grantType) {
4324
+ base["sasl.oauthbearer.grant.type"] = grantType;
4325
+ }
4326
+ if (extensions) {
4327
+ base["sasl.oauthbearer.extensions"] = extensions;
4328
+ }
4359
4329
  }
4360
4330
  }
4361
4331
  else {
@@ -4374,7 +4344,9 @@ function buildConfluentKafkaClientOptions(endpoint) {
4374
4344
  || key === "Extensions"
4375
4345
  || key === "extensions"
4376
4346
  || key === "TokenEndpoint"
4377
- || key === "tokenEndpoint") {
4347
+ || key === "tokenEndpoint"
4348
+ || key === "Principal"
4349
+ || key === "principal") {
4378
4350
  continue;
4379
4351
  }
4380
4352
  base[key] = value;
@@ -4385,53 +4357,20 @@ function buildConfluentKafkaClientOptions(endpoint) {
4385
4357
  }
4386
4358
  return base;
4387
4359
  }
4388
- async function resolveKafkaOAuthBearerToken(options) {
4389
- const directToken = optionString(options, "AccessToken", "accessToken", "OAuthBearerToken", "oauthBearerToken");
4390
- if (directToken) {
4391
- return directToken;
4392
- }
4393
- const additional = asRecordOrEmpty(pickProtocolValue(options, "AdditionalSettings", "additionalSettings"));
4394
- const tokenEndpoint = optionString(options, "OAuthBearerTokenEndpointUrl", "oauthBearerTokenEndpointUrl", "TokenEndpoint", "tokenEndpoint") || optionString(additional, "TokenEndpoint", "tokenEndpoint");
4395
- const clientId = optionString(additional, "ClientId", "clientId");
4396
- const clientSecret = optionString(additional, "ClientSecret", "clientSecret");
4397
- if (!tokenEndpoint || !clientId || !clientSecret) {
4398
- throw new Error("Kafka OAuthBearer requires a direct access token or token endpoint with client credentials.");
4399
- }
4400
- const form = new URLSearchParams();
4401
- form.set("grant_type", "client_credentials");
4402
- form.set("client_id", clientId);
4403
- form.set("client_secret", clientSecret);
4404
- const scope = optionString(additional, "Scope", "scope");
4405
- if (scope) {
4406
- form.set("scope", scope);
4407
- }
4408
- const response = await fetch(tokenEndpoint, {
4409
- method: "POST",
4410
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
4411
- body: form.toString()
4412
- });
4413
- if (!response.ok) {
4414
- throw new Error(`Kafka OAuthBearer token request failed with status ${response.status}.`);
4415
- }
4416
- const payload = parseMaybeJson(await response.text());
4417
- if (!isRecord(payload) || !payload.access_token) {
4418
- throw new Error("Kafka OAuthBearer token response did not contain access_token.");
4419
- }
4420
- return String(payload.access_token);
4421
- }
4422
- function mapKafkaSaslMechanism(mechanism) {
4423
- switch (mechanism) {
4424
- case "plain":
4425
- return "plain";
4426
- case "scramsha256":
4427
- case "scram-sha-256":
4428
- return "scram-sha-256";
4429
- case "scramsha512":
4430
- case "scram-sha-512":
4431
- return "scram-sha-512";
4432
- default:
4433
- throw new Error(`Unsupported Kafka SASL mechanism: ${mechanism}.`);
4360
+ function parseKafkaOAuthExtensions(value) {
4361
+ const parsed = {};
4362
+ for (const entry of value.split(",")) {
4363
+ const separatorIndex = entry.indexOf("=");
4364
+ if (separatorIndex <= 0) {
4365
+ continue;
4366
+ }
4367
+ const key = entry.slice(0, separatorIndex).trim();
4368
+ if (!key) {
4369
+ continue;
4370
+ }
4371
+ parsed[key] = entry.slice(separatorIndex + 1).trim();
4434
4372
  }
4373
+ return parsed;
4435
4374
  }
4436
4375
  function mapConfluentKafkaSecurityProtocol(protocol) {
4437
4376
  switch (protocol.toLowerCase()) {
@@ -4715,7 +4654,6 @@ exports.__loadstrikeTestExports = {
4715
4654
  applyHttpAuthHeaders,
4716
4655
  buildHttpRequestBody,
4717
4656
  buildConfluentKafkaClientOptions,
4718
- buildKafkaClientOptions,
4719
4657
  canonicalizeHttpResponseSource,
4720
4658
  createDelegateRequestEndpointView,
4721
4659
  createNatsHeaders,
@@ -4731,7 +4669,6 @@ exports.__loadstrikeTestExports = {
4731
4669
  injectTrackingValue,
4732
4670
  mapConfluentKafkaSaslMechanism,
4733
4671
  mapConfluentKafkaSecurityProtocol,
4734
- mapKafkaSaslMechanism,
4735
4672
  parseBodyObject,
4736
4673
  partitionFromKey,
4737
4674
  payloadBodyAsUtf8,
@@ -4739,10 +4676,8 @@ exports.__loadstrikeTestExports = {
4739
4676
  protocolBus: protocolBus,
4740
4677
  readFirstRedisStreamEntry,
4741
4678
  resolveConnectionMetadata,
4742
- resolveKafkaOAuthBearerToken,
4743
4679
  serializePayloadBody,
4744
4680
  setJsonBodyValue,
4745
- shouldUseConfluentKafkaClient,
4746
4681
  toHeaderRecord,
4747
4682
  toKafkaHeadersWithContentType,
4748
4683
  toSqsMessageAttributes,
@@ -4750,6 +4685,9 @@ exports.__loadstrikeTestExports = {
4750
4685
  setSqsClientFactoryForTests: (factory) => {
4751
4686
  sqsClientFactoryForTests = factory;
4752
4687
  },
4688
+ setKafkaClientFactoryForTests: (factory) => {
4689
+ kafkaClientFactoryForTests = factory;
4690
+ },
4753
4691
  validateHttpEndpoint,
4754
4692
  validateTrackingSelectorPath
4755
4693
  };