@platformatic/kafka 1.2.0 → 1.3.0
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.
- package/README.md +4 -0
- package/dist/{clients → apis}/callbacks.d.ts +1 -1
- package/dist/apis/enumerations.d.ts +2 -0
- package/dist/apis/enumerations.js +2 -0
- package/dist/apis/index.d.ts +1 -0
- package/dist/apis/index.js +1 -0
- package/dist/apis/security/sasl-handshake-v1.js +1 -1
- package/dist/clients/admin/admin.d.ts +1 -1
- package/dist/clients/admin/admin.js +8 -8
- package/dist/clients/base/base.d.ts +6 -2
- package/dist/clients/base/base.js +19 -6
- package/dist/clients/base/options.d.ts +21 -0
- package/dist/clients/base/options.js +12 -0
- package/dist/clients/consumer/consumer.d.ts +1 -1
- package/dist/clients/consumer/consumer.js +5 -5
- package/dist/clients/consumer/messages-stream.d.ts +1 -1
- package/dist/clients/consumer/messages-stream.js +1 -1
- package/dist/clients/index.d.ts +0 -1
- package/dist/clients/index.js +0 -1
- package/dist/clients/producer/producer.d.ts +1 -1
- package/dist/clients/producer/producer.js +4 -4
- package/dist/diagnostic.d.ts +2 -2
- package/dist/errors.js +1 -1
- package/dist/network/connection-pool.d.ts +5 -3
- package/dist/network/connection-pool.js +7 -1
- package/dist/network/connection.d.ts +13 -3
- package/dist/network/connection.js +67 -17
- package/dist/protocol/sasl/plain.d.ts +2 -0
- package/dist/protocol/sasl/plain.js +7 -2
- package/dist/protocol/sasl/scram-sha.d.ts +2 -0
- package/dist/protocol/sasl/scram-sha.js +61 -43
- package/package.json +1 -1
- /package/dist/{clients → apis}/callbacks.js +0 -0
package/README.md
CHANGED
|
@@ -137,6 +137,10 @@ await admin.deleteTopics({ topics: ['my-topic'] })
|
|
|
137
137
|
await admin.close()
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
## TLS and SASL
|
|
141
|
+
|
|
142
|
+
See the relevant sections in the the [Base Client](./docs/base.md) page.
|
|
143
|
+
|
|
140
144
|
## Serialisation/Deserialisation
|
|
141
145
|
|
|
142
146
|
`@platformatic/kafka` supports customisation of serialisation out of the box.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Callback } from '
|
|
1
|
+
import { type Callback } from './definitions.ts';
|
|
2
2
|
export declare const kCallbackPromise: unique symbol;
|
|
3
3
|
export declare const kNoopCallbackReturnValue: unique symbol;
|
|
4
4
|
export declare const noopCallback: CallbackWithPromise<any>;
|
package/dist/apis/index.d.ts
CHANGED
package/dist/apis/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ResponseError } from "../../errors.js";
|
|
|
2
2
|
import { Writer } from "../../protocol/writer.js";
|
|
3
3
|
import { createAPI } from "../definitions.js";
|
|
4
4
|
/*
|
|
5
|
-
SaslHandshake Request (Version:
|
|
5
|
+
SaslHandshake Request (Version: 1) => mechanism
|
|
6
6
|
mechanism => STRING
|
|
7
7
|
*/
|
|
8
8
|
export function createRequest(mechanism) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
1
2
|
import { type Callback } from '../../apis/definitions.ts';
|
|
2
3
|
import { Base } from '../base/base.ts';
|
|
3
|
-
import { type CallbackWithPromise } from '../callbacks.ts';
|
|
4
4
|
import { type AdminOptions, type CreatedTopic, type CreateTopicsOptions, type DeleteGroupsOptions, type DeleteTopicsOptions, type DescribeGroupsOptions, type Group, type GroupBase, type ListGroupsOptions } from './types.ts';
|
|
5
5
|
export declare class Admin extends Base<AdminOptions> {
|
|
6
6
|
#private;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
1
2
|
import { FindCoordinatorKeyTypes } from "../../apis/enumerations.js";
|
|
2
3
|
import { adminGroupsChannel, adminTopicsChannel, createDiagnosticContext } from "../../diagnostic.js";
|
|
3
4
|
import { Reader } from "../../protocol/reader.js";
|
|
4
|
-
import { Base, kAfterCreate,
|
|
5
|
-
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../callbacks.js";
|
|
5
|
+
import { Base, kAfterCreate, kCheckNotClosed, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kValidateOptions } from "../base/base.js";
|
|
6
6
|
import { createTopicsOptionsValidator, deleteGroupsOptionsValidator, deleteTopicsOptionsValidator, describeGroupsOptionsValidator, listGroupsOptionsValidator } from "./options.js";
|
|
7
7
|
export class Admin extends Base {
|
|
8
8
|
constructor(options) {
|
|
@@ -107,7 +107,7 @@ export class Admin extends Base {
|
|
|
107
107
|
}
|
|
108
108
|
this[kPerformDeduplicated]('createTopics', deduplicateCallback => {
|
|
109
109
|
this[kPerformWithRetry]('createTopics', retryCallback => {
|
|
110
|
-
this[
|
|
110
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
111
111
|
if (error) {
|
|
112
112
|
retryCallback(error, undefined);
|
|
113
113
|
return;
|
|
@@ -140,7 +140,7 @@ export class Admin extends Base {
|
|
|
140
140
|
#deleteTopics(options, callback) {
|
|
141
141
|
this[kPerformDeduplicated]('deleteTopics', deduplicateCallback => {
|
|
142
142
|
this[kPerformWithRetry]('deleteTopics', retryCallback => {
|
|
143
|
-
this[
|
|
143
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
144
144
|
if (error) {
|
|
145
145
|
retryCallback(error, undefined);
|
|
146
146
|
return;
|
|
@@ -168,7 +168,7 @@ export class Admin extends Base {
|
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
runConcurrentCallbacks('Listing groups failed.', metadata.brokers, ([, broker], concurrentCallback) => {
|
|
171
|
-
this[
|
|
171
|
+
this[kGetConnection](broker, (error, connection) => {
|
|
172
172
|
if (error) {
|
|
173
173
|
concurrentCallback(error, undefined);
|
|
174
174
|
return;
|
|
@@ -231,7 +231,7 @@ export class Admin extends Base {
|
|
|
231
231
|
coordinator.push(group);
|
|
232
232
|
}
|
|
233
233
|
runConcurrentCallbacks('Describing groups failed.', coordinators, ([node, groups], concurrentCallback) => {
|
|
234
|
-
this[
|
|
234
|
+
this[kGetConnection](metadata.brokers.get(node), (error, connection) => {
|
|
235
235
|
if (error) {
|
|
236
236
|
concurrentCallback(error, undefined);
|
|
237
237
|
return;
|
|
@@ -313,7 +313,7 @@ export class Admin extends Base {
|
|
|
313
313
|
coordinator.push(group);
|
|
314
314
|
}
|
|
315
315
|
runConcurrentCallbacks('Deleting groups failed.', coordinators, ([node, groups], concurrentCallback) => {
|
|
316
|
-
this[
|
|
316
|
+
this[kGetConnection](metadata.brokers.get(node), (error, connection) => {
|
|
317
317
|
if (error) {
|
|
318
318
|
concurrentCallback(error, undefined);
|
|
319
319
|
return;
|
|
@@ -334,7 +334,7 @@ export class Admin extends Base {
|
|
|
334
334
|
}
|
|
335
335
|
#findGroupCoordinator(groups, callback) {
|
|
336
336
|
this[kPerformWithRetry]('findGroupCoordinator', retryCallback => {
|
|
337
|
-
this[
|
|
337
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
338
338
|
if (error) {
|
|
339
339
|
retryCallback(error, undefined);
|
|
340
340
|
return;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { type ValidateFunction } from 'ajv';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
3
4
|
import { type API, type Callback } from '../../apis/definitions.ts';
|
|
4
5
|
import { type ApiVersionsResponseApi } from '../../apis/metadata/api-versions-v3.ts';
|
|
5
6
|
import { type ClientType } from '../../diagnostic.ts';
|
|
6
7
|
import { ConnectionPool } from '../../network/connection-pool.ts';
|
|
7
|
-
import { type Broker } from '../../network/connection.ts';
|
|
8
|
+
import { type Broker, type Connection } from '../../network/connection.ts';
|
|
8
9
|
import { kInstance } from '../../symbols.ts';
|
|
9
|
-
import { type CallbackWithPromise } from '../callbacks.ts';
|
|
10
10
|
import { type Metrics } from '../metrics.ts';
|
|
11
11
|
import { type BaseOptions, type ClusterMetadata, type MetadataOptions } from './types.ts';
|
|
12
12
|
export declare const kClientId: unique symbol;
|
|
13
13
|
export declare const kBootstrapBrokers: unique symbol;
|
|
14
14
|
export declare const kApis: unique symbol;
|
|
15
15
|
export declare const kGetApi: unique symbol;
|
|
16
|
+
export declare const kGetConnection: unique symbol;
|
|
17
|
+
export declare const kGetBootstrapConnection: unique symbol;
|
|
16
18
|
export declare const kOptions: unique symbol;
|
|
17
19
|
export declare const kConnections: unique symbol;
|
|
18
20
|
export declare const kFetchConnections: unique symbol;
|
|
@@ -63,6 +65,8 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions> extends
|
|
|
63
65
|
[kPerformWithRetry]<ReturnType>(operationId: string, operation: (callback: Callback<ReturnType>) => void, callback: CallbackWithPromise<ReturnType>, attempt?: number, errors?: Error[], shouldSkipRetry?: (e: Error) => boolean): void | Promise<ReturnType>;
|
|
64
66
|
[kPerformDeduplicated]<ReturnType>(operationId: string, operation: (callback: CallbackWithPromise<ReturnType>) => void, callback: CallbackWithPromise<ReturnType>): void | Promise<ReturnType>;
|
|
65
67
|
[kGetApi]<RequestArguments extends Array<unknown>, ResponseType>(name: string, callback: Callback<API<RequestArguments, ResponseType>>): void;
|
|
68
|
+
[kGetConnection](broker: Broker, callback: Callback<Connection>): void;
|
|
69
|
+
[kGetBootstrapConnection](callback: Callback<Connection>): void;
|
|
66
70
|
[kValidateOptions](target: unknown, validator: ValidateFunction<unknown>, targetName: string, throwOnErrors?: boolean): Error | null;
|
|
67
71
|
[kInspect](...args: unknown[]): void;
|
|
68
72
|
[kFormatValidationErrors](validator: ValidateFunction<unknown>, targetName: string): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
2
3
|
import * as apis from "../../apis/index.js";
|
|
3
4
|
import { api as apiVersionsV3 } from "../../apis/metadata/api-versions-v3.js";
|
|
4
5
|
import { baseApisChannel, baseMetadataChannel, createDiagnosticContext, notifyCreation } from "../../diagnostic.js";
|
|
@@ -6,12 +7,13 @@ import { MultipleErrors, NetworkError, UnsupportedApiError, UserError } from "..
|
|
|
6
7
|
import { ConnectionPool } from "../../network/connection-pool.js";
|
|
7
8
|
import { kInstance } from "../../symbols.js";
|
|
8
9
|
import { ajv, debugDump, loggers } from "../../utils.js";
|
|
9
|
-
import { createPromisifiedCallback, kCallbackPromise } from "../callbacks.js";
|
|
10
10
|
import { baseOptionsValidator, clientSoftwareName, clientSoftwareVersion, defaultBaseOptions, defaultPort, metadataOptionsValidator } from "./options.js";
|
|
11
11
|
export const kClientId = Symbol('plt.kafka.base.clientId');
|
|
12
12
|
export const kBootstrapBrokers = Symbol('plt.kafka.base.bootstrapBrokers');
|
|
13
13
|
export const kApis = Symbol('plt.kafka.base.apis');
|
|
14
14
|
export const kGetApi = Symbol('plt.kafka.base.getApi');
|
|
15
|
+
export const kGetConnection = Symbol('plt.kafka.base.getConnection');
|
|
16
|
+
export const kGetBootstrapConnection = Symbol('plt.kafka.base.getBootstrapConnection');
|
|
15
17
|
export const kOptions = Symbol('plt.kafka.base.options');
|
|
16
18
|
export const kConnections = Symbol('plt.kafka.base.connections');
|
|
17
19
|
export const kFetchConnections = Symbol('plt.kafka.base.fetchCnnections');
|
|
@@ -119,15 +121,13 @@ export class Base extends EventEmitter {
|
|
|
119
121
|
ownerId: this[kInstance],
|
|
120
122
|
...this[kOptions]
|
|
121
123
|
});
|
|
122
|
-
|
|
123
|
-
pool.on(event, payload => this.emitWithDebug('client', `broker:${event}`, payload));
|
|
124
|
-
}
|
|
124
|
+
this.#forwardEvents(pool, ['connect', 'disconnect', 'failed', 'drain', 'sasl:handshake', 'sasl:authentication']);
|
|
125
125
|
return pool;
|
|
126
126
|
}
|
|
127
127
|
[kListApis](callback) {
|
|
128
128
|
this[kPerformDeduplicated]('listApis', deduplicateCallback => {
|
|
129
129
|
this[kPerformWithRetry]('listApis', retryCallback => {
|
|
130
|
-
this[
|
|
130
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
131
131
|
if (error) {
|
|
132
132
|
retryCallback(error, undefined);
|
|
133
133
|
return;
|
|
@@ -157,7 +157,7 @@ export class Base extends EventEmitter {
|
|
|
157
157
|
const autocreateTopics = options.autocreateTopics ?? this[kOptions].autocreateTopics;
|
|
158
158
|
this[kPerformDeduplicated]('metadata', deduplicateCallback => {
|
|
159
159
|
this[kPerformWithRetry]('metadata', retryCallback => {
|
|
160
|
-
this[
|
|
160
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
161
161
|
if (error) {
|
|
162
162
|
retryCallback(error, undefined);
|
|
163
163
|
return;
|
|
@@ -305,6 +305,12 @@ export class Base extends EventEmitter {
|
|
|
305
305
|
}
|
|
306
306
|
callback(new UnsupportedApiError(`No usable implementation found for API ${name}.`, { minVersion, maxVersion }), undefined);
|
|
307
307
|
}
|
|
308
|
+
[kGetConnection](broker, callback) {
|
|
309
|
+
this[kConnections].get(broker, callback);
|
|
310
|
+
}
|
|
311
|
+
[kGetBootstrapConnection](callback) {
|
|
312
|
+
this[kConnections].getFirstAvailable(this[kBootstrapBrokers], callback);
|
|
313
|
+
}
|
|
308
314
|
[kValidateOptions](target, validator, targetName, throwOnErrors = true) {
|
|
309
315
|
if (!this[kOptions].strict) {
|
|
310
316
|
return null;
|
|
@@ -330,4 +336,11 @@ export class Base extends EventEmitter {
|
|
|
330
336
|
this[kClientType] = type;
|
|
331
337
|
notifyCreation(type, this);
|
|
332
338
|
}
|
|
339
|
+
#forwardEvents(source, events) {
|
|
340
|
+
for (const event of events) {
|
|
341
|
+
source.on(event, (...args) => {
|
|
342
|
+
this.emitWithDebug('client', `broker:${event}`, ...args);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
333
346
|
}
|
|
@@ -69,6 +69,27 @@ export declare const baseOptionsSchema: {
|
|
|
69
69
|
type: string;
|
|
70
70
|
minimum: number;
|
|
71
71
|
};
|
|
72
|
+
tls: {
|
|
73
|
+
type: string;
|
|
74
|
+
additionalProperties: boolean;
|
|
75
|
+
};
|
|
76
|
+
sasl: {
|
|
77
|
+
type: string;
|
|
78
|
+
properties: {
|
|
79
|
+
mechanism: {
|
|
80
|
+
type: string;
|
|
81
|
+
enum: readonly ["PLAIN", "SCRAM-SHA-256", "SCRAM-SHA-512"];
|
|
82
|
+
};
|
|
83
|
+
username: {
|
|
84
|
+
type: string;
|
|
85
|
+
};
|
|
86
|
+
password: {
|
|
87
|
+
type: string;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
required: string[];
|
|
91
|
+
additionalProperties: boolean;
|
|
92
|
+
};
|
|
72
93
|
metadataMaxAge: {
|
|
73
94
|
type: string;
|
|
74
95
|
minimum: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { SASLMechanisms } from "../../apis/enumerations.js";
|
|
2
3
|
import { ajv } from "../../utils.js";
|
|
3
4
|
const packageJson = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf-8'));
|
|
4
5
|
// Note: clientSoftwareName can only contain alphanumeric characters, hyphens and dots
|
|
@@ -31,6 +32,17 @@ export const baseOptionsSchema = {
|
|
|
31
32
|
retries: { type: 'number', minimum: 0 },
|
|
32
33
|
retryDelay: { type: 'number', minimum: 0 },
|
|
33
34
|
maxInflights: { type: 'number', minimum: 0 },
|
|
35
|
+
tls: { type: 'object', additionalProperties: true }, // No validation as they come from Node.js
|
|
36
|
+
sasl: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
mechanism: { type: 'string', enum: SASLMechanisms },
|
|
40
|
+
username: { type: 'string' },
|
|
41
|
+
password: { type: 'string' }
|
|
42
|
+
},
|
|
43
|
+
required: ['mechanism', 'username', 'password'],
|
|
44
|
+
additionalProperties: false
|
|
45
|
+
},
|
|
34
46
|
metadataMaxAge: { type: 'number', minimum: 0 },
|
|
35
47
|
autocreateTopics: { type: 'boolean' },
|
|
36
48
|
strict: { type: 'boolean' },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
1
2
|
import { type FetchResponse } from '../../apis/consumer/fetch-v17.ts';
|
|
2
3
|
import { type ConnectionPool } from '../../network/connection-pool.ts';
|
|
3
4
|
import { Base, kFetchConnections } from '../base/base.ts';
|
|
4
|
-
import { type CallbackWithPromise } from '../callbacks.ts';
|
|
5
5
|
import { MessagesStream } from './messages-stream.ts';
|
|
6
6
|
import { TopicsMap } from './topics-map.ts';
|
|
7
7
|
import { type CommitOptions, type ConsumeOptions, type ConsumerOptions, type FetchOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets } from './types.ts';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
1
2
|
import { FetchIsolationLevels, FindCoordinatorKeyTypes } from "../../apis/enumerations.js";
|
|
2
3
|
import { consumerCommitsChannel, consumerConsumesChannel, consumerFetchesChannel, consumerGroupChannel, consumerHeartbeatChannel, consumerOffsetsChannel, createDiagnosticContext } from "../../diagnostic.js";
|
|
3
4
|
import { UserError } from "../../errors.js";
|
|
4
5
|
import { Reader } from "../../protocol/reader.js";
|
|
5
6
|
import { Writer } from "../../protocol/writer.js";
|
|
6
|
-
import { Base, kAfterCreate,
|
|
7
|
+
import { Base, kAfterCreate, kCheckNotClosed, kClosed, kCreateConnectionPool, kFetchConnections, kFormatValidationErrors, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
|
|
7
8
|
import { defaultBaseOptions } from "../base/options.js";
|
|
8
|
-
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../callbacks.js";
|
|
9
9
|
import { ensureMetric } from "../metrics.js";
|
|
10
10
|
import { MessagesStream } from "./messages-stream.js";
|
|
11
11
|
import { commitOptionsValidator, consumeOptionsValidator, consumerOptionsValidator, defaultConsumerOptions, fetchOptionsValidator, groupIdAndOptionsValidator, groupOptionsValidator, listCommitsOptionsValidator, listOffsetsOptionsValidator } from "./options.js";
|
|
@@ -340,7 +340,7 @@ export class Consumer extends Base {
|
|
|
340
340
|
}
|
|
341
341
|
runConcurrentCallbacks('Listing offsets failed.', requests, ([leader, requests], concurrentCallback) => {
|
|
342
342
|
this[kPerformWithRetry]('listOffsets', retryCallback => {
|
|
343
|
-
this[
|
|
343
|
+
this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
|
|
344
344
|
if (error) {
|
|
345
345
|
retryCallback(error, undefined);
|
|
346
346
|
return;
|
|
@@ -508,7 +508,7 @@ export class Consumer extends Base {
|
|
|
508
508
|
#performFindGroupCoordinator(callback) {
|
|
509
509
|
this[kPerformDeduplicated]('findGroupCoordinator', deduplicateCallback => {
|
|
510
510
|
this[kPerformWithRetry]('findGroupCoordinator', retryCallback => {
|
|
511
|
-
this[
|
|
511
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
512
512
|
if (error) {
|
|
513
513
|
retryCallback(error, undefined);
|
|
514
514
|
return;
|
|
@@ -738,7 +738,7 @@ export class Consumer extends Base {
|
|
|
738
738
|
return;
|
|
739
739
|
}
|
|
740
740
|
this[kPerformWithRetry](operationId, retryCallback => {
|
|
741
|
-
this[
|
|
741
|
+
this[kGetConnection](metadata.brokers.get(coordinatorId), (error, connection) => {
|
|
742
742
|
if (error) {
|
|
743
743
|
retryCallback(error, undefined);
|
|
744
744
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
3
|
import { type Message } from '../../protocol/records.ts';
|
|
3
4
|
import { kInspect } from '../base/base.ts';
|
|
4
|
-
import { type CallbackWithPromise } from '../callbacks.ts';
|
|
5
5
|
import { type Consumer } from './consumer.ts';
|
|
6
6
|
import { type CommitOptionsPartition, type ConsumeOptions } from './types.ts';
|
|
7
7
|
export declare function noopDeserializer(data?: Buffer): Buffer | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
+
import { createPromisifiedCallback, kCallbackPromise, noopCallback } from "../../apis/callbacks.js";
|
|
2
3
|
import { ListOffsetTimestamps } from "../../apis/enumerations.js";
|
|
3
4
|
import { consumerReceivesChannel, createDiagnosticContext, notifyCreation } from "../../diagnostic.js";
|
|
4
5
|
import { UserError } from "../../errors.js";
|
|
5
6
|
import { kInspect, kPrometheus } from "../base/base.js";
|
|
6
|
-
import { createPromisifiedCallback, kCallbackPromise, noopCallback } from "../callbacks.js";
|
|
7
7
|
import { ensureMetric } from "../metrics.js";
|
|
8
8
|
import { defaultConsumerOptions } from "./options.js";
|
|
9
9
|
import { MessagesStreamFallbackModes, MessagesStreamModes } from "./types.js";
|
package/dist/clients/index.d.ts
CHANGED
package/dist/clients/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
1
2
|
import { Base } from '../base/base.ts';
|
|
2
|
-
import { type CallbackWithPromise } from '../callbacks.ts';
|
|
3
3
|
import { type ProduceOptions, type ProduceResult, type ProducerInfo, type ProducerOptions, type SendOptions } from './types.ts';
|
|
4
4
|
export declare class Producer<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Base<ProducerOptions<Key, Value, HeaderKey, HeaderValue>> {
|
|
5
5
|
#private;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
|
|
1
2
|
import { ProduceAcks } from "../../apis/enumerations.js";
|
|
2
3
|
import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel } from "../../diagnostic.js";
|
|
3
4
|
import { UserError } from "../../errors.js";
|
|
4
5
|
import { murmur2 } from "../../protocol/murmur2.js";
|
|
5
6
|
import { NumericMap } from "../../utils.js";
|
|
6
|
-
import { Base, kAfterCreate,
|
|
7
|
-
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../callbacks.js";
|
|
7
|
+
import { Base, kAfterCreate, kCheckNotClosed, kClearMetadata, kClosed, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
|
|
8
8
|
import { ensureMetric } from "../metrics.js";
|
|
9
9
|
import { produceOptionsValidator, producerOptionsValidator, sendOptionsValidator } from "./options.js";
|
|
10
10
|
// Don't move this function as being in the same file will enable V8 to remove.
|
|
@@ -108,7 +108,7 @@ export class Producer extends Base {
|
|
|
108
108
|
#initIdempotentProducer(options, callback) {
|
|
109
109
|
this[kPerformDeduplicated]('initProducerId', deduplicateCallback => {
|
|
110
110
|
this[kPerformWithRetry]('initProducerId', retryCallback => {
|
|
111
|
-
this[
|
|
111
|
+
this[kGetBootstrapConnection]((error, connection) => {
|
|
112
112
|
if (error) {
|
|
113
113
|
retryCallback(error, undefined);
|
|
114
114
|
return;
|
|
@@ -292,7 +292,7 @@ export class Producer extends Base {
|
|
|
292
292
|
const { topic, partition } = messages[0];
|
|
293
293
|
const leader = metadata.topics.get(topic).partitions[partition].leader;
|
|
294
294
|
this[kPerformWithRetry]('produce', retryCallback => {
|
|
295
|
-
this[
|
|
295
|
+
this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
|
|
296
296
|
if (error) {
|
|
297
297
|
retryCallback(error, undefined);
|
|
298
298
|
return;
|
package/dist/diagnostic.d.ts
CHANGED
|
@@ -19,13 +19,13 @@ export type ClientDiagnosticEvent<InstanceType extends Base = Base, Attributes =
|
|
|
19
19
|
export type TracingChannelWithName<EventType extends object> = TracingChannel<string, EventType> & {
|
|
20
20
|
name: string;
|
|
21
21
|
};
|
|
22
|
-
export type DiagnosticContext<BaseContext> = BaseContext & {
|
|
22
|
+
export type DiagnosticContext<BaseContext = {}> = BaseContext & {
|
|
23
23
|
operationId: bigint;
|
|
24
24
|
result?: unknown;
|
|
25
25
|
error?: unknown;
|
|
26
26
|
};
|
|
27
27
|
export declare const channelsNamespace: "plt:kafka";
|
|
28
|
-
export declare function createDiagnosticContext<BaseContext>(context: BaseContext): DiagnosticContext<BaseContext>;
|
|
28
|
+
export declare function createDiagnosticContext<BaseContext = {}>(context: BaseContext): DiagnosticContext<BaseContext>;
|
|
29
29
|
export declare function notifyCreation<InstanceType>(type: ClientType | 'connection' | 'connection-pool' | 'messages-stream', instance: InstanceType): void;
|
|
30
30
|
export declare function createTracingChannel<DiagnosticEvent extends object>(name: string): TracingChannelWithName<DiagnosticEvent>;
|
|
31
31
|
export declare const instancesChannel: import("diagnostics_channel").Channel<unknown, unknown>;
|
package/dist/errors.js
CHANGED
|
@@ -87,7 +87,7 @@ export * from "./protocol/errors.js";
|
|
|
87
87
|
export class AuthenticationError extends GenericError {
|
|
88
88
|
static code = 'PLT_KFK_AUTHENTICATION';
|
|
89
89
|
constructor(message, properties = {}) {
|
|
90
|
-
super(AuthenticationError.code, message, properties);
|
|
90
|
+
super(AuthenticationError.code, message, { canRetry: false, ...properties });
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
export class NetworkError extends GenericError {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
-
import { type CallbackWithPromise } from '../
|
|
2
|
+
import { type CallbackWithPromise } from '../apis/callbacks.ts';
|
|
3
3
|
import { Connection, type Broker, type ConnectionOptions } from './connection.ts';
|
|
4
4
|
export declare class ConnectionPool extends EventEmitter {
|
|
5
5
|
#private;
|
|
@@ -7,6 +7,8 @@ export declare class ConnectionPool extends EventEmitter {
|
|
|
7
7
|
get instanceId(): number;
|
|
8
8
|
get(broker: Broker, callback: CallbackWithPromise<Connection>): void;
|
|
9
9
|
get(broker: Broker): Promise<Connection>;
|
|
10
|
-
getFirstAvailable(brokers: Broker[], callback
|
|
11
|
-
|
|
10
|
+
getFirstAvailable(brokers: Broker[], callback: CallbackWithPromise<Connection>): void;
|
|
11
|
+
getFirstAvailable(brokers: Broker[]): Promise<Connection>;
|
|
12
|
+
close(callback: CallbackWithPromise<void>): void;
|
|
13
|
+
close(): Promise<void>;
|
|
12
14
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
-
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../
|
|
2
|
+
import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../apis/callbacks.js";
|
|
3
3
|
import { connectionsPoolGetsChannel, createDiagnosticContext, notifyCreation } from "../diagnostic.js";
|
|
4
4
|
import { MultipleErrors } from "../errors.js";
|
|
5
5
|
import { Connection, ConnectionStatuses } from "./connection.js";
|
|
@@ -83,6 +83,12 @@ export class ConnectionPool extends EventEmitter {
|
|
|
83
83
|
this.emit('connect', eventPayload);
|
|
84
84
|
callback(null, connection);
|
|
85
85
|
});
|
|
86
|
+
connection.on('sasl:handshake', mechanisms => {
|
|
87
|
+
this.emit('sasl:handshake', { ...eventPayload, mechanisms });
|
|
88
|
+
});
|
|
89
|
+
connection.on('sasl:authentication', authentication => {
|
|
90
|
+
this.emit('sasl:authentication', { ...eventPayload, authentication });
|
|
91
|
+
});
|
|
86
92
|
// Remove stale connections from the pool
|
|
87
93
|
connection.once('close', () => {
|
|
88
94
|
this.emit('disconnect', eventPayload);
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
2
|
import { type Socket } from 'node:net';
|
|
3
3
|
import { type ConnectionOptions as TLSConnectionOptions } from 'node:tls';
|
|
4
|
+
import { type CallbackWithPromise } from '../apis/callbacks.ts';
|
|
4
5
|
import { type Callback, type ResponseParser } from '../apis/definitions.ts';
|
|
5
|
-
import { type
|
|
6
|
+
import { type SASLMechanism } from '../apis/enumerations.ts';
|
|
6
7
|
import { Writer } from '../protocol/writer.ts';
|
|
7
8
|
export interface Broker {
|
|
8
9
|
host: string;
|
|
9
10
|
port: number;
|
|
10
11
|
}
|
|
12
|
+
export interface SASLOptions {
|
|
13
|
+
mechanism: SASLMechanism;
|
|
14
|
+
username: string;
|
|
15
|
+
password: string;
|
|
16
|
+
}
|
|
11
17
|
export interface ConnectionOptions {
|
|
12
18
|
connectTimeout?: number;
|
|
13
19
|
maxInflights?: number;
|
|
14
20
|
tls?: TLSConnectionOptions;
|
|
21
|
+
sasl?: SASLOptions;
|
|
15
22
|
ownerId?: number;
|
|
16
23
|
}
|
|
17
24
|
export interface Request {
|
|
@@ -28,6 +35,7 @@ export interface Request {
|
|
|
28
35
|
export declare const ConnectionStatuses: {
|
|
29
36
|
readonly NONE: "none";
|
|
30
37
|
readonly CONNECTING: "connecting";
|
|
38
|
+
readonly AUTHENTICATING: "authenticating";
|
|
31
39
|
readonly CONNECTED: "connected";
|
|
32
40
|
readonly CLOSED: "closed";
|
|
33
41
|
readonly CLOSING: "closing";
|
|
@@ -43,7 +51,9 @@ export declare class Connection extends EventEmitter {
|
|
|
43
51
|
get status(): ConnectionStatusValue;
|
|
44
52
|
get socket(): Socket;
|
|
45
53
|
connect(host: string, port: number, callback?: CallbackWithPromise<void>): void | Promise<void>;
|
|
46
|
-
ready(callback
|
|
47
|
-
|
|
54
|
+
ready(callback: CallbackWithPromise<void>): void;
|
|
55
|
+
ready(): Promise<void>;
|
|
56
|
+
close(callback: CallbackWithPromise<void>): void;
|
|
57
|
+
close(): Promise<void>;
|
|
48
58
|
send<ReturnType>(apiKey: number, apiVersion: number, payload: () => Writer, responseParser: ResponseParser<ReturnType>, hasRequestHeaderTaggedFields: boolean, hasResponseHeaderTaggedFields: boolean, callback: Callback<ReturnType>): void;
|
|
49
59
|
}
|
|
@@ -2,18 +2,23 @@ import fastq from 'fastq';
|
|
|
2
2
|
import EventEmitter from 'node:events';
|
|
3
3
|
import { createConnection } from 'node:net';
|
|
4
4
|
import { connect as createTLSConnection } from 'node:tls';
|
|
5
|
-
import { createPromisifiedCallback, kCallbackPromise } from "../
|
|
5
|
+
import { createPromisifiedCallback, kCallbackPromise } from "../apis/callbacks.js";
|
|
6
|
+
import { SASLMechanisms } from "../apis/enumerations.js";
|
|
7
|
+
import { saslAuthenticateV2, saslHandshakeV1 } from "../apis/index.js";
|
|
6
8
|
import { connectionsApiChannel, connectionsConnectsChannel, createDiagnosticContext, notifyCreation } from "../diagnostic.js";
|
|
7
|
-
import { NetworkError, TimeoutError, UnexpectedCorrelationIdError } from "../errors.js";
|
|
9
|
+
import { AuthenticationError, NetworkError, TimeoutError, UnexpectedCorrelationIdError, UserError } from "../errors.js";
|
|
8
10
|
import { protocolAPIsById } from "../protocol/apis.js";
|
|
9
11
|
import { EMPTY_OR_SINGLE_COMPACT_LENGTH_SIZE, INT32_SIZE } from "../protocol/definitions.js";
|
|
10
12
|
import { DynamicBuffer } from "../protocol/dynamic-buffer.js";
|
|
13
|
+
import { saslPlain, saslScramSha } from "../protocol/index.js";
|
|
11
14
|
import { Reader } from "../protocol/reader.js";
|
|
15
|
+
import { defaultCrypto } from "../protocol/sasl/scram-sha.js";
|
|
12
16
|
import { Writer } from "../protocol/writer.js";
|
|
13
17
|
import { loggers } from "../utils.js";
|
|
14
18
|
export const ConnectionStatuses = {
|
|
15
19
|
NONE: 'none',
|
|
16
20
|
CONNECTING: 'connecting',
|
|
21
|
+
AUTHENTICATING: 'authenticating',
|
|
17
22
|
CONNECTED: 'connected',
|
|
18
23
|
CLOSED: 'closed',
|
|
19
24
|
CLOSING: 'closing',
|
|
@@ -97,14 +102,8 @@ export class Connection extends EventEmitter {
|
|
|
97
102
|
this.emit('error', error);
|
|
98
103
|
connectionsConnectsChannel.asyncEnd.publish(diagnosticContext);
|
|
99
104
|
};
|
|
100
|
-
const connectionErrorHandler = (
|
|
101
|
-
|
|
102
|
-
diagnosticContext.error = error;
|
|
103
|
-
this.#status = ConnectionStatuses.ERROR;
|
|
104
|
-
connectionsConnectsChannel.error.publish(diagnosticContext);
|
|
105
|
-
connectionsConnectsChannel.asyncStart.publish(diagnosticContext);
|
|
106
|
-
this.emit('error', error);
|
|
107
|
-
connectionsConnectsChannel.asyncEnd.publish(diagnosticContext);
|
|
105
|
+
const connectionErrorHandler = (error) => {
|
|
106
|
+
this.#onConnectionError(host, port, diagnosticContext, error);
|
|
108
107
|
};
|
|
109
108
|
this.emit('connecting');
|
|
110
109
|
/* c8 ignore next 3 - TLS connection is not tested but we rely on Node.js tests */
|
|
@@ -120,10 +119,12 @@ export class Connection extends EventEmitter {
|
|
|
120
119
|
this.#socket.on('drain', this.#onDrain.bind(this));
|
|
121
120
|
this.#socket.on('close', this.#onClose.bind(this));
|
|
122
121
|
this.#socket.setTimeout(0);
|
|
123
|
-
this.#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
if (this.#options.sasl) {
|
|
123
|
+
this.#authenticate(host, port, diagnosticContext);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.#onConnectionSucceed(diagnosticContext);
|
|
127
|
+
}
|
|
127
128
|
});
|
|
128
129
|
this.#socket.once('timeout', connectionTimeoutHandler);
|
|
129
130
|
this.#socket.once('error', connectionErrorHandler);
|
|
@@ -212,6 +213,27 @@ export class Connection extends EventEmitter {
|
|
|
212
213
|
return this.#sendRequest(request);
|
|
213
214
|
}, callback);
|
|
214
215
|
}
|
|
216
|
+
#authenticate(host, port, diagnosticContext) {
|
|
217
|
+
this.#status = ConnectionStatuses.AUTHENTICATING;
|
|
218
|
+
const { mechanism, username, password } = this.#options.sasl;
|
|
219
|
+
if (!SASLMechanisms.includes(mechanism)) {
|
|
220
|
+
this.#onConnectionError(host, port, diagnosticContext, new UserError(`SASL mechanism ${mechanism} not supported.`));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
saslHandshakeV1.api(this, mechanism, (error, response) => {
|
|
224
|
+
if (error) {
|
|
225
|
+
this.#onConnectionError(host, port, diagnosticContext, new AuthenticationError('Cannot find a suitable SASL mechanism.', { cause: error }));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.emit('sasl:handshake', response.mechanisms);
|
|
229
|
+
if (mechanism === 'PLAIN') {
|
|
230
|
+
saslPlain.authenticate(saslAuthenticateV2.api, this, username, password, this.#onSaslAuthenticate.bind(this, host, port, diagnosticContext));
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
saslScramSha.authenticate(saslAuthenticateV2.api, this, mechanism.substring(6), username, password, defaultCrypto, this.#onSaslAuthenticate.bind(this, host, port, diagnosticContext));
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
215
237
|
/*
|
|
216
238
|
Request => Size [Request Header v2] [payload]
|
|
217
239
|
Request Header v2 => request_api_key request_api_version correlation_id client_id TAG_BUFFER
|
|
@@ -223,7 +245,7 @@ export class Connection extends EventEmitter {
|
|
|
223
245
|
#sendRequest(request) {
|
|
224
246
|
connectionsApiChannel.start.publish(request.diagnostic);
|
|
225
247
|
try {
|
|
226
|
-
if (this.#status !== ConnectionStatuses.CONNECTED) {
|
|
248
|
+
if (this.#status !== ConnectionStatuses.CONNECTED && this.#status !== ConnectionStatuses.AUTHENTICATING) {
|
|
227
249
|
request.callback(new NetworkError('Connection closed'), undefined);
|
|
228
250
|
return false;
|
|
229
251
|
}
|
|
@@ -245,7 +267,7 @@ export class Connection extends EventEmitter {
|
|
|
245
267
|
if (!payload.context.noResponse) {
|
|
246
268
|
this.#inflightRequests.set(correlationId, request);
|
|
247
269
|
}
|
|
248
|
-
loggers.protocol({ apiKey: protocolAPIsById[apiKey], correlationId, request }
|
|
270
|
+
loggers.protocol('Sending request.', { apiKey: protocolAPIsById[apiKey], correlationId, request });
|
|
249
271
|
for (const buf of writer.buffers) {
|
|
250
272
|
if (!this.#socket.write(buf)) {
|
|
251
273
|
canWrite = false;
|
|
@@ -272,6 +294,34 @@ export class Connection extends EventEmitter {
|
|
|
272
294
|
connectionsApiChannel.end.publish(request.diagnostic);
|
|
273
295
|
}
|
|
274
296
|
}
|
|
297
|
+
#onConnectionSucceed(diagnosticContext) {
|
|
298
|
+
this.#status = ConnectionStatuses.CONNECTED;
|
|
299
|
+
connectionsConnectsChannel.asyncStart.publish(diagnosticContext);
|
|
300
|
+
this.emit('connect');
|
|
301
|
+
connectionsConnectsChannel.asyncEnd.publish(diagnosticContext);
|
|
302
|
+
}
|
|
303
|
+
#onConnectionError(host, port, diagnosticContext, cause) {
|
|
304
|
+
const error = new NetworkError(`Connection to ${host}:${port} failed.`, { cause });
|
|
305
|
+
this.#status = ConnectionStatuses.ERROR;
|
|
306
|
+
diagnosticContext.error = error;
|
|
307
|
+
connectionsConnectsChannel.error.publish(diagnosticContext);
|
|
308
|
+
connectionsConnectsChannel.asyncStart.publish(diagnosticContext);
|
|
309
|
+
this.emit('error', error);
|
|
310
|
+
connectionsConnectsChannel.asyncEnd.publish(diagnosticContext);
|
|
311
|
+
this.#socket.end();
|
|
312
|
+
}
|
|
313
|
+
#onSaslAuthenticate(host, port, diagnosticContext, error, response) {
|
|
314
|
+
if (error) {
|
|
315
|
+
const protocolError = error.errors[0];
|
|
316
|
+
if (protocolError.apiId === 'SASL_AUTHENTICATION_FAILED') {
|
|
317
|
+
error = new AuthenticationError('SASL authentication failed.', { cause: error });
|
|
318
|
+
}
|
|
319
|
+
this.#onConnectionError(host, port, diagnosticContext, error);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
this.emit('sasl:authentication', response.authBytes);
|
|
323
|
+
this.#onConnectionSucceed(diagnosticContext);
|
|
324
|
+
}
|
|
275
325
|
/*
|
|
276
326
|
Response Header v1 => correlation_id TAG_BUFFER
|
|
277
327
|
correlation_id => INT32
|
|
@@ -327,7 +377,7 @@ export class Connection extends EventEmitter {
|
|
|
327
377
|
// apiKey: protocolAPIsById[apiKey],
|
|
328
378
|
// correlationId
|
|
329
379
|
// })
|
|
330
|
-
loggers.protocol({ apiKey: protocolAPIsById[apiKey], correlationId, request
|
|
380
|
+
loggers.protocol('Received response.', { apiKey: protocolAPIsById[apiKey], correlationId, request, deserialized });
|
|
331
381
|
if (responseError) {
|
|
332
382
|
request.diagnostic.error = responseError;
|
|
333
383
|
connectionsApiChannel.error.publish(request.diagnostic);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
1
2
|
import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
|
|
2
3
|
import { type Connection } from '../../network/connection.ts';
|
|
4
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, username: string, password: string, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
|
|
3
5
|
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, username: string, password: string): Promise<SaslAuthenticateResponse>;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
2
|
+
export function authenticate(authenticateAPI, connection, username, password, callback) {
|
|
3
|
+
if (!callback) {
|
|
4
|
+
callback = createPromisifiedCallback();
|
|
5
|
+
}
|
|
6
|
+
authenticateAPI(connection, Buffer.from(['', username, password].join('\0')), callback);
|
|
7
|
+
return callback[kCallbackPromise];
|
|
3
8
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
1
2
|
import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
|
|
2
3
|
import { type Connection } from '../../network/connection.ts';
|
|
3
4
|
export interface ScramAlgorithmDefinition {
|
|
@@ -32,4 +33,5 @@ export declare function hi(definition: ScramAlgorithmDefinition, password: strin
|
|
|
32
33
|
export declare function hmac(definition: ScramAlgorithmDefinition, key: Buffer, data: string | Buffer): Buffer;
|
|
33
34
|
export declare function xor(a: Buffer, b: Buffer): Buffer;
|
|
34
35
|
export declare const defaultCrypto: ScramCryptoModule;
|
|
36
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, username: string, password: string, crypto: ScramCryptoModule, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
|
|
35
37
|
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, username: string, password: string, crypto?: ScramCryptoModule): Promise<SaslAuthenticateResponse>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, createHmac, pbkdf2Sync, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
2
3
|
import { AuthenticationError } from "../../errors.js";
|
|
3
4
|
const GS2_HEADER = 'n,,';
|
|
4
5
|
const GS2_HEADER_BASE64 = Buffer.from(GS2_HEADER).toString('base64');
|
|
@@ -56,8 +57,10 @@ export const defaultCrypto = {
|
|
|
56
57
|
hmac,
|
|
57
58
|
xor
|
|
58
59
|
};
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
export function authenticate(authenticateAPI, connection, algorithm, username, password, crypto = defaultCrypto, callback) {
|
|
61
|
+
if (!callback) {
|
|
62
|
+
callback = createPromisifiedCallback();
|
|
63
|
+
}
|
|
61
64
|
const { h, hi, hmac, xor } = crypto;
|
|
62
65
|
const definition = ScramAlgorithms[algorithm];
|
|
63
66
|
if (!definition) {
|
|
@@ -66,45 +69,60 @@ export async function authenticate(authenticateAPI, connection, algorithm, usern
|
|
|
66
69
|
const clientNonce = createNonce();
|
|
67
70
|
const clientFirstMessageBare = `n=${sanitizeString(username)},r=${clientNonce}`;
|
|
68
71
|
// First of all, send the first message
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
72
|
+
authenticateAPI(connection, Buffer.from(`${GS2_HEADER}${clientFirstMessageBare}`), (error, firstResponse) => {
|
|
73
|
+
if (error) {
|
|
74
|
+
callback(new AuthenticationError('Authentication failed.', { cause: error }), undefined);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const firstData = parseParameters(firstResponse.authBytes);
|
|
78
|
+
// Extract some parameters
|
|
79
|
+
const salt = Buffer.from(firstData.s, 'base64');
|
|
80
|
+
const iterations = parseInt(firstData.i, 10);
|
|
81
|
+
const serverNonce = firstData.r;
|
|
82
|
+
const serverFirstMessage = firstData.__original;
|
|
83
|
+
// Validate response
|
|
84
|
+
if (!serverNonce.startsWith(clientNonce)) {
|
|
85
|
+
callback(new AuthenticationError('Server nonce does not start with client nonce.'), undefined);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
else if (definition.minIterations > iterations) {
|
|
89
|
+
callback(new AuthenticationError(`Algorithm ${algorithm} requires at least ${definition.minIterations} iterations, while ${iterations} were requested.`), undefined);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// SaltedPassword := Hi(Normalize(password), salt, i)
|
|
93
|
+
// ClientKey := HMAC(SaltedPassword, "Client Key")
|
|
94
|
+
// StoredKey := H(ClientKey)
|
|
95
|
+
// AuthMessage := ClientFirstMessageBare + "," ServerFirstMessage + "," + ClientFinalMessageWithoutProof
|
|
96
|
+
// ClientSignature := HMAC(StoredKey, AuthMessage)
|
|
97
|
+
// ClientProof := ClientKey XOR ClientSignature
|
|
98
|
+
// ServerKey := HMAC(SaltedPassword, "Server Key")
|
|
99
|
+
// ServerSignature := HMAC(ServerKey, AuthMessage)
|
|
100
|
+
const saltedPassword = hi(definition, password, salt, iterations);
|
|
101
|
+
const clientKey = hmac(definition, saltedPassword, HMAC_CLIENT_KEY);
|
|
102
|
+
const storedKey = h(definition, clientKey);
|
|
103
|
+
const clientFinalMessageWithoutProof = `c=${GS2_HEADER_BASE64},r=${serverNonce}`;
|
|
104
|
+
const authMessage = `${clientFirstMessageBare},${serverFirstMessage},${clientFinalMessageWithoutProof}`;
|
|
105
|
+
const clientSignature = hmac(definition, storedKey, authMessage);
|
|
106
|
+
const clientProof = xor(clientKey, clientSignature);
|
|
107
|
+
const serverKey = hmac(definition, saltedPassword, HMAC_SERVER_KEY);
|
|
108
|
+
const serverSignature = hmac(definition, serverKey, authMessage);
|
|
109
|
+
authenticateAPI(connection, Buffer.from(`${clientFinalMessageWithoutProof},p=${clientProof.toString('base64')}`), (error, lastResponse) => {
|
|
110
|
+
if (error) {
|
|
111
|
+
callback(new AuthenticationError('Authentication failed.', { cause: error }), undefined);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Send the last message to the server
|
|
115
|
+
const lastData = parseParameters(lastResponse.authBytes);
|
|
116
|
+
if (lastData.e) {
|
|
117
|
+
callback(new AuthenticationError(lastData.e), undefined);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
else if (lastData.v !== serverSignature.toString('base64')) {
|
|
121
|
+
callback(new AuthenticationError('Invalid server signature.'), undefined);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
callback(null, lastResponse);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
return callback[kCallbackPromise];
|
|
110
128
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Modern and performant client for Apache Kafka",
|
|
5
5
|
"homepage": "https://github.com/platformatic/kafka",
|
|
6
6
|
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
File without changes
|