@platformatic/kafka 1.11.0 → 1.12.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 +2 -2
- package/dist/clients/base/base.d.ts +0 -2
- package/dist/clients/base/base.js +12 -16
- package/dist/clients/base/index.d.ts +1 -1
- package/dist/clients/base/index.js +1 -1
- package/dist/clients/base/options.d.ts +21 -3
- package/dist/clients/base/options.js +3 -3
- package/dist/network/connection-pool.js +3 -0
- package/dist/network/connection.d.ts +4 -3
- package/dist/network/connection.js +32 -8
- package/dist/network/index.d.ts +1 -0
- package/dist/network/index.js +1 -0
- package/dist/network/utils.d.ts +2 -0
- package/dist/network/utils.js +12 -0
- package/dist/protocol/errors.js +1 -1
- package/dist/protocol/records.js +3 -1
- package/dist/protocol/sasl/credential-provider.d.ts +3 -0
- package/dist/protocol/sasl/credential-provider.js +36 -0
- package/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
- package/dist/protocol/sasl/oauth-bearer.js +8 -2
- package/dist/protocol/sasl/plain.d.ts +3 -3
- package/dist/protocol/sasl/plain.js +13 -2
- package/dist/protocol/sasl/scram-sha.d.ts +3 -3
- package/dist/protocol/sasl/scram-sha.js +24 -10
- package/dist/utils.js +5 -1
- package/dist/version.js +1 -1
- package/package.json +16 -19
package/README.md
CHANGED
|
@@ -281,7 +281,7 @@ Many of the methods accept the same options as the client's constructors. The co
|
|
|
281
281
|
|
|
282
282
|
## Requirements
|
|
283
283
|
|
|
284
|
-
Node.js LTS versions:
|
|
284
|
+
Node.js LTS versions:
|
|
285
285
|
|
|
286
286
|
- `20.19.4` or above
|
|
287
287
|
- `22.18.0` or above
|
|
@@ -301,7 +301,7 @@ Edit .env file as needed, enabling or not `JAVA_OPTIONS`
|
|
|
301
301
|
Start Kafka cluster locally
|
|
302
302
|
|
|
303
303
|
```bash
|
|
304
|
-
docker compose -f compose
|
|
304
|
+
docker compose -f compose.yml up -d
|
|
305
305
|
```
|
|
306
306
|
|
|
307
307
|
## License
|
|
@@ -24,7 +24,6 @@ export declare const kListApis: unique symbol;
|
|
|
24
24
|
export declare const kMetadata: unique symbol;
|
|
25
25
|
export declare const kCheckNotClosed: unique symbol;
|
|
26
26
|
export declare const kClearMetadata: unique symbol;
|
|
27
|
-
export declare const kParseBroker: unique symbol;
|
|
28
27
|
export declare const kPerformWithRetry: unique symbol;
|
|
29
28
|
export declare const kPerformDeduplicated: unique symbol;
|
|
30
29
|
export declare const kValidateOptions: unique symbol;
|
|
@@ -65,7 +64,6 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions> extends
|
|
|
65
64
|
[kMetadata](options: MetadataOptions, callback: CallbackWithPromise<ClusterMetadata>): void;
|
|
66
65
|
[kCheckNotClosed](callback: CallbackWithPromise<any>): boolean;
|
|
67
66
|
[kClearMetadata](): void;
|
|
68
|
-
[kParseBroker](broker: Broker | string): Broker;
|
|
69
67
|
[kPerformWithRetry]<ReturnType>(operationId: string, operation: (callback: Callback<ReturnType>) => void, callback: CallbackWithPromise<ReturnType>, attempt?: number, errors?: Error[], shouldSkipRetry?: (e: Error) => boolean): void | Promise<ReturnType>;
|
|
70
68
|
[kPerformDeduplicated]<ReturnType>(operationId: string, operation: (callback: CallbackWithPromise<ReturnType>) => void, callback: CallbackWithPromise<ReturnType>): void | Promise<ReturnType>;
|
|
71
69
|
[kGetApi]<RequestArguments extends Array<unknown>, ResponseType>(name: string, callback: Callback<API<RequestArguments, ResponseType>>): void;
|
|
@@ -5,6 +5,7 @@ import { api as apiVersionsV3 } from "../../apis/metadata/api-versions-v3.js";
|
|
|
5
5
|
import { baseApisChannel, baseMetadataChannel, createDiagnosticContext, notifyCreation } from "../../diagnostic.js";
|
|
6
6
|
import { MultipleErrors, NetworkError, UnsupportedApiError, UserError } from "../../errors.js";
|
|
7
7
|
import { ConnectionPool } from "../../network/connection-pool.js";
|
|
8
|
+
import { parseBroker } from "../../network/utils.js";
|
|
8
9
|
import { kInstance } from "../../symbols.js";
|
|
9
10
|
import { ajv, debugDump, loggers } from "../../utils.js";
|
|
10
11
|
import { baseOptionsValidator, clientSoftwareName, clientSoftwareVersion, defaultBaseOptions, defaultPort, metadataOptionsValidator } from "./options.js";
|
|
@@ -23,7 +24,6 @@ export const kListApis = Symbol('plt.kafka.base.listApis');
|
|
|
23
24
|
export const kMetadata = Symbol('plt.kafka.base.metadata');
|
|
24
25
|
export const kCheckNotClosed = Symbol('plt.kafka.base.checkNotClosed');
|
|
25
26
|
export const kClearMetadata = Symbol('plt.kafka.base.clearMetadata');
|
|
26
|
-
export const kParseBroker = Symbol('plt.kafka.base.parseBroker');
|
|
27
27
|
export const kPerformWithRetry = Symbol('plt.kafka.base.performWithRetry');
|
|
28
28
|
export const kPerformDeduplicated = Symbol('plt.kafka.base.performDeduplicated');
|
|
29
29
|
export const kValidateOptions = Symbol('plt.kafka.base.validateOptions');
|
|
@@ -63,7 +63,7 @@ export class Base extends EventEmitter {
|
|
|
63
63
|
// Initialize bootstrap brokers
|
|
64
64
|
this[kBootstrapBrokers] = [];
|
|
65
65
|
for (const broker of options.bootstrapBrokers) {
|
|
66
|
-
this[kBootstrapBrokers].push(
|
|
66
|
+
this[kBootstrapBrokers].push(parseBroker(broker, defaultPort));
|
|
67
67
|
}
|
|
68
68
|
// Initialize main connection pool
|
|
69
69
|
this[kConnections] = this[kCreateConnectionPool]();
|
|
@@ -177,7 +177,15 @@ export class Base extends EventEmitter {
|
|
|
177
177
|
ownerId: this[kInstance],
|
|
178
178
|
...this[kOptions]
|
|
179
179
|
});
|
|
180
|
-
this.#forwardEvents(pool, [
|
|
180
|
+
this.#forwardEvents(pool, [
|
|
181
|
+
'connect',
|
|
182
|
+
'disconnect',
|
|
183
|
+
'failed',
|
|
184
|
+
'drain',
|
|
185
|
+
'sasl:handshake',
|
|
186
|
+
'sasl:authentication',
|
|
187
|
+
'sasl:authentication:extended'
|
|
188
|
+
]);
|
|
181
189
|
return pool;
|
|
182
190
|
}
|
|
183
191
|
[kListApis](callback) {
|
|
@@ -216,7 +224,7 @@ export class Base extends EventEmitter {
|
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
// All topics are already up-to-date, simply return them
|
|
219
|
-
if (this.#metadata && !topicsToFetch.length) {
|
|
227
|
+
if (this.#metadata && !topicsToFetch.length && !options.forceUpdate) {
|
|
220
228
|
callback(null, {
|
|
221
229
|
...this.#metadata,
|
|
222
230
|
topics: new Map(options.topics.map(topic => [topic, this.#metadata.topics.get(topic)]))
|
|
@@ -306,18 +314,6 @@ export class Base extends EventEmitter {
|
|
|
306
314
|
[kClearMetadata]() {
|
|
307
315
|
this.#metadata = undefined;
|
|
308
316
|
}
|
|
309
|
-
[kParseBroker](broker) {
|
|
310
|
-
if (typeof broker === 'string') {
|
|
311
|
-
if (broker.includes(':')) {
|
|
312
|
-
const [host, port] = broker.split(':');
|
|
313
|
-
return { host, port: Number(port) };
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
return { host: broker, port: defaultPort };
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return broker;
|
|
320
|
-
}
|
|
321
317
|
[kPerformWithRetry](operationId, operation, callback, attempt = 0, errors = [], shouldSkipRetry) {
|
|
322
318
|
const retries = this[kOptions].retries;
|
|
323
319
|
this.emitWithDebug('client', 'performWithRetry', operationId, attempt, retries);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Base, kCheckNotClosed, kClearMetadata, kGetApi, kGetBootstrapConnection, kGetConnection, kListApis, kMetadata, kOptions,
|
|
1
|
+
export { Base, kCheckNotClosed, kClearMetadata, kGetApi, kGetBootstrapConnection, kGetConnection, kListApis, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kValidateOptions } from './base.ts';
|
|
2
2
|
export * from './options.ts';
|
|
3
3
|
export * from './types.ts';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Base, kCheckNotClosed, kClearMetadata, kGetApi, kGetBootstrapConnection, kGetConnection, kListApis, kMetadata, kOptions,
|
|
1
|
+
export { Base, kCheckNotClosed, kClearMetadata, kGetApi, kGetBootstrapConnection, kGetConnection, kListApis, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kValidateOptions } from "./base.js";
|
|
2
2
|
export * from "./options.js";
|
|
3
3
|
export * from "./types.js";
|
|
@@ -91,13 +91,31 @@ export declare const baseOptionsSchema: {
|
|
|
91
91
|
enum: readonly ["PLAIN", "SCRAM-SHA-256", "SCRAM-SHA-512", "OAUTHBEARER"];
|
|
92
92
|
};
|
|
93
93
|
username: {
|
|
94
|
-
|
|
94
|
+
oneOf: ({
|
|
95
|
+
type: string;
|
|
96
|
+
function?: undefined;
|
|
97
|
+
} | {
|
|
98
|
+
function: boolean;
|
|
99
|
+
type?: undefined;
|
|
100
|
+
})[];
|
|
95
101
|
};
|
|
96
102
|
password: {
|
|
97
|
-
|
|
103
|
+
oneOf: ({
|
|
104
|
+
type: string;
|
|
105
|
+
function?: undefined;
|
|
106
|
+
} | {
|
|
107
|
+
function: boolean;
|
|
108
|
+
type?: undefined;
|
|
109
|
+
})[];
|
|
98
110
|
};
|
|
99
111
|
token: {
|
|
100
|
-
|
|
112
|
+
oneOf: ({
|
|
113
|
+
type: string;
|
|
114
|
+
function?: undefined;
|
|
115
|
+
} | {
|
|
116
|
+
function: boolean;
|
|
117
|
+
type?: undefined;
|
|
118
|
+
})[];
|
|
101
119
|
};
|
|
102
120
|
};
|
|
103
121
|
required: string[];
|
|
@@ -37,9 +37,9 @@ export const baseOptionsSchema = {
|
|
|
37
37
|
type: 'object',
|
|
38
38
|
properties: {
|
|
39
39
|
mechanism: { type: 'string', enum: SASLMechanisms },
|
|
40
|
-
username: { type: 'string' },
|
|
41
|
-
password: { type: 'string' },
|
|
42
|
-
token: { type: 'string' }
|
|
40
|
+
username: { oneOf: [{ type: 'string' }, { function: true }] },
|
|
41
|
+
password: { oneOf: [{ type: 'string' }, { function: true }] },
|
|
42
|
+
token: { oneOf: [{ type: 'string' }, { function: true }] }
|
|
43
43
|
},
|
|
44
44
|
required: ['mechanism'],
|
|
45
45
|
additionalProperties: false
|
|
@@ -114,6 +114,9 @@ export class ConnectionPool extends EventEmitter {
|
|
|
114
114
|
connection.on('sasl:authentication', authentication => {
|
|
115
115
|
this.emit('sasl:authentication', { ...eventPayload, authentication });
|
|
116
116
|
});
|
|
117
|
+
connection.on('sasl:authentication:extended', authentication => {
|
|
118
|
+
this.emit('sasl:authentication:extended', { ...eventPayload, authentication });
|
|
119
|
+
});
|
|
117
120
|
// Remove stale connections from the pool
|
|
118
121
|
connection.once('close', () => {
|
|
119
122
|
this.emit('disconnect', eventPayload);
|
|
@@ -5,15 +5,16 @@ import { type CallbackWithPromise } from '../apis/callbacks.ts';
|
|
|
5
5
|
import { type Callback, type ResponseParser } from '../apis/definitions.ts';
|
|
6
6
|
import { type SASLMechanism } from '../apis/enumerations.ts';
|
|
7
7
|
import { Writer } from '../protocol/writer.ts';
|
|
8
|
+
export type SASLCredentialProvider = () => string | Promise<string>;
|
|
8
9
|
export interface Broker {
|
|
9
10
|
host: string;
|
|
10
11
|
port: number;
|
|
11
12
|
}
|
|
12
13
|
export interface SASLOptions {
|
|
13
14
|
mechanism: SASLMechanism;
|
|
14
|
-
username?: string;
|
|
15
|
-
password?: string;
|
|
16
|
-
token?: string;
|
|
15
|
+
username?: string | SASLCredentialProvider;
|
|
16
|
+
password?: string | SASLCredentialProvider;
|
|
17
|
+
token?: string | SASLCredentialProvider;
|
|
17
18
|
}
|
|
18
19
|
export interface ConnectionOptions {
|
|
19
20
|
connectTimeout?: number;
|
|
@@ -47,6 +47,7 @@ export class Connection extends EventEmitter {
|
|
|
47
47
|
#responseReader;
|
|
48
48
|
#socket;
|
|
49
49
|
#socketMustBeDrained;
|
|
50
|
+
#reauthenticationTimeout;
|
|
50
51
|
constructor(clientId, options = {}) {
|
|
51
52
|
super();
|
|
52
53
|
this.setMaxListeners(0);
|
|
@@ -178,6 +179,7 @@ export class Connection extends EventEmitter {
|
|
|
178
179
|
if (!callback) {
|
|
179
180
|
callback = createPromisifiedCallback();
|
|
180
181
|
}
|
|
182
|
+
clearInterval(this.#reauthenticationTimeout);
|
|
181
183
|
if (this.#status === ConnectionStatuses.CLOSED ||
|
|
182
184
|
this.#status === ConnectionStatuses.ERROR ||
|
|
183
185
|
this.#status === ConnectionStatuses.NONE) {
|
|
@@ -231,7 +233,9 @@ export class Connection extends EventEmitter {
|
|
|
231
233
|
}, callback);
|
|
232
234
|
}
|
|
233
235
|
#authenticate(host, port, diagnosticContext) {
|
|
234
|
-
this.#status
|
|
236
|
+
if (this.#status === ConnectionStatuses.CONNECTING) {
|
|
237
|
+
this.#status = ConnectionStatuses.AUTHENTICATING;
|
|
238
|
+
}
|
|
235
239
|
const { mechanism, username, password, token } = this.#options.sasl;
|
|
236
240
|
if (!SASLMechanisms.includes(mechanism)) {
|
|
237
241
|
this.#onConnectionError(host, port, diagnosticContext, new UserError(`SASL mechanism ${mechanism} not supported.`));
|
|
@@ -243,14 +247,15 @@ export class Connection extends EventEmitter {
|
|
|
243
247
|
return;
|
|
244
248
|
}
|
|
245
249
|
this.emit('sasl:handshake', response.mechanisms);
|
|
250
|
+
const callback = this.#onSaslAuthenticate.bind(this, host, port, diagnosticContext);
|
|
246
251
|
if (mechanism === 'PLAIN') {
|
|
247
|
-
saslPlain.authenticate(saslAuthenticateV2.api, this, username, password,
|
|
252
|
+
saslPlain.authenticate(saslAuthenticateV2.api, this, username, password, callback);
|
|
248
253
|
}
|
|
249
254
|
else if (mechanism === 'OAUTHBEARER') {
|
|
250
|
-
saslOAuthBearer.authenticate(saslAuthenticateV2.api, this, token,
|
|
255
|
+
saslOAuthBearer.authenticate(saslAuthenticateV2.api, this, token, callback);
|
|
251
256
|
}
|
|
252
257
|
else {
|
|
253
|
-
saslScramSha.authenticate(saslAuthenticateV2.api, this, mechanism.substring(6), username, password, defaultCrypto,
|
|
258
|
+
saslScramSha.authenticate(saslAuthenticateV2.api, this, mechanism.substring(6), username, password, defaultCrypto, callback);
|
|
254
259
|
}
|
|
255
260
|
});
|
|
256
261
|
}
|
|
@@ -299,6 +304,7 @@ export class Connection extends EventEmitter {
|
|
|
299
304
|
request.diagnostic.error = err;
|
|
300
305
|
connectionsApiChannel.error.publish(request.diagnostic);
|
|
301
306
|
throw err;
|
|
307
|
+
/* c8 ignore next 3 - Hard to test */
|
|
302
308
|
}
|
|
303
309
|
finally {
|
|
304
310
|
connectionsApiChannel.end.publish(request.diagnostic);
|
|
@@ -313,6 +319,7 @@ export class Connection extends EventEmitter {
|
|
|
313
319
|
#onConnectionError(host, port, diagnosticContext, cause) {
|
|
314
320
|
const error = new NetworkError(`Connection to ${host}:${port} failed.`, { cause });
|
|
315
321
|
this.#status = ConnectionStatuses.ERROR;
|
|
322
|
+
clearTimeout(this.#reauthenticationTimeout);
|
|
316
323
|
diagnosticContext.error = error;
|
|
317
324
|
connectionsConnectsChannel.error.publish(diagnosticContext);
|
|
318
325
|
connectionsConnectsChannel.asyncStart.publish(diagnosticContext);
|
|
@@ -322,15 +329,31 @@ export class Connection extends EventEmitter {
|
|
|
322
329
|
}
|
|
323
330
|
#onSaslAuthenticate(host, port, diagnosticContext, error, response) {
|
|
324
331
|
if (error) {
|
|
325
|
-
const protocolError = error.errors[0];
|
|
326
|
-
if (protocolError
|
|
332
|
+
const protocolError = error.errors?.[0];
|
|
333
|
+
if (protocolError?.apiId === 'SASL_AUTHENTICATION_FAILED') {
|
|
327
334
|
error = new AuthenticationError('SASL authentication failed.', { cause: error });
|
|
328
335
|
}
|
|
329
336
|
this.#onConnectionError(host, port, diagnosticContext, error);
|
|
330
337
|
return;
|
|
331
338
|
}
|
|
332
|
-
|
|
333
|
-
|
|
339
|
+
if (response.sessionLifetimeMs > 0) {
|
|
340
|
+
this.#reauthenticationTimeout = setTimeout(() => {
|
|
341
|
+
const diagnosticContext = createDiagnosticContext({
|
|
342
|
+
connection: this,
|
|
343
|
+
operation: 'reauthenticate',
|
|
344
|
+
host,
|
|
345
|
+
port
|
|
346
|
+
});
|
|
347
|
+
this.#authenticate(host, port, diagnosticContext);
|
|
348
|
+
}, Number(response.sessionLifetimeMs) * 0.8);
|
|
349
|
+
}
|
|
350
|
+
if (this.#status === ConnectionStatuses.CONNECTED) {
|
|
351
|
+
this.emit('sasl:authentication:extended', response.authBytes);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
this.emit('sasl:authentication', response.authBytes);
|
|
355
|
+
this.#onConnectionSucceed(diagnosticContext);
|
|
356
|
+
}
|
|
334
357
|
}
|
|
335
358
|
/*
|
|
336
359
|
Response Header v1 => correlation_id TAG_BUFFER
|
|
@@ -428,6 +451,7 @@ export class Connection extends EventEmitter {
|
|
|
428
451
|
}
|
|
429
452
|
}
|
|
430
453
|
#onError(error) {
|
|
454
|
+
clearTimeout(this.#reauthenticationTimeout);
|
|
431
455
|
this.emit('error', new NetworkError('Connection error', { cause: error }));
|
|
432
456
|
}
|
|
433
457
|
}
|
package/dist/network/index.d.ts
CHANGED
package/dist/network/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function parseBroker(broker, defaultPort = 9092) {
|
|
2
|
+
if (typeof broker === 'string') {
|
|
3
|
+
if (broker.includes(':')) {
|
|
4
|
+
const [host, port] = broker.split(':');
|
|
5
|
+
return { host, port: Number(port) };
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
return { host: broker, port: defaultPort };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return broker;
|
|
12
|
+
}
|
package/dist/protocol/errors.js
CHANGED
|
@@ -489,7 +489,7 @@ export const protocolErrors = {
|
|
|
489
489
|
id: 'SASL_AUTHENTICATION_FAILED',
|
|
490
490
|
code: 58,
|
|
491
491
|
canRetry: false,
|
|
492
|
-
message: 'SASL
|
|
492
|
+
message: 'SASL authentication failed.'
|
|
493
493
|
},
|
|
494
494
|
UNKNOWN_PRODUCER_ID: {
|
|
495
495
|
id: 'UNKNOWN_PRODUCER_ID',
|
package/dist/protocol/records.js
CHANGED
|
@@ -65,8 +65,10 @@ export function createRecordsBatch(messages, options = {}) {
|
|
|
65
65
|
let buffer = new DynamicBuffer();
|
|
66
66
|
for (let i = 0; i < messages.length; i++) {
|
|
67
67
|
let ts = messages[i].timestamp ?? now;
|
|
68
|
-
|
|
68
|
+
/* c8 ignore next 3 - Hard to test */
|
|
69
|
+
if (typeof ts === 'number') {
|
|
69
70
|
ts = BigInt(ts);
|
|
71
|
+
}
|
|
70
72
|
messages[i].timestamp = ts;
|
|
71
73
|
if (ts > maxTimestamp)
|
|
72
74
|
maxTimestamp = ts;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AuthenticationError } from "../../errors.js";
|
|
2
|
+
export function getCredential(label, credentialOrProvider, callback) {
|
|
3
|
+
if (typeof credentialOrProvider === 'string') {
|
|
4
|
+
callback(null, credentialOrProvider);
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
else if (typeof credentialOrProvider !== 'function') {
|
|
8
|
+
callback(new AuthenticationError(`The ${label} should be a string or a function.`), undefined);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const credential = credentialOrProvider();
|
|
13
|
+
if (typeof credential === 'string') {
|
|
14
|
+
callback(null, credential);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
else if (typeof credential?.then !== 'function') {
|
|
18
|
+
callback(new AuthenticationError(`The ${label} provider should return a string or a promise that resolves to a string.`), undefined);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
credential
|
|
22
|
+
.then(token => {
|
|
23
|
+
if (typeof token !== 'string') {
|
|
24
|
+
process.nextTick(callback, new AuthenticationError(`The ${label} provider should resolve to a string.`), undefined);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
process.nextTick(callback, null, token);
|
|
28
|
+
})
|
|
29
|
+
.catch(error => {
|
|
30
|
+
process.nextTick(callback, new AuthenticationError(`The ${label} provider threw an error.`, { cause: error }));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
callback(new AuthenticationError(`The ${label} provider threw an error.`, { cause: error }), undefined);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
2
|
import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
|
|
3
|
-
import { type Connection } from '../../network/connection.ts';
|
|
4
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection,
|
|
5
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection,
|
|
3
|
+
import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
|
|
4
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | SASLCredentialProvider, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
|
|
5
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, tokenOrProvider: string | SASLCredentialProvider): Promise<SaslAuthenticateResponse>;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
2
|
-
|
|
2
|
+
import { getCredential } from "./credential-provider.js";
|
|
3
|
+
export function authenticate(authenticateAPI, connection, tokenOrProvider, callback) {
|
|
3
4
|
if (!callback) {
|
|
4
5
|
callback = createPromisifiedCallback();
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
+
getCredential('SASL/OAUTHBEARER token', tokenOrProvider, (error, token) => {
|
|
8
|
+
if (error) {
|
|
9
|
+
return callback(error, undefined);
|
|
10
|
+
}
|
|
11
|
+
authenticateAPI(connection, Buffer.from(`n,,\x01auth=Bearer ${token}\x01\x01`), callback);
|
|
12
|
+
});
|
|
7
13
|
return callback[kCallbackPromise];
|
|
8
14
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
2
|
import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
|
|
3
|
-
import { type Connection } from '../../network/connection.ts';
|
|
4
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection,
|
|
5
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection,
|
|
3
|
+
import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
|
|
4
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
|
|
5
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider): Promise<SaslAuthenticateResponse>;
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
2
|
-
|
|
2
|
+
import { getCredential } from "./credential-provider.js";
|
|
3
|
+
export function authenticate(authenticateAPI, connection, usernameProvider, passwordProvider, callback) {
|
|
3
4
|
if (!callback) {
|
|
4
5
|
callback = createPromisifiedCallback();
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
+
getCredential('SASL/PLAIN username', usernameProvider, (error, username) => {
|
|
8
|
+
if (error) {
|
|
9
|
+
return callback(error, undefined);
|
|
10
|
+
}
|
|
11
|
+
getCredential('SASL/PLAIN password', passwordProvider, (error, password) => {
|
|
12
|
+
if (error) {
|
|
13
|
+
return callback(error, undefined);
|
|
14
|
+
}
|
|
15
|
+
authenticateAPI(connection, Buffer.from(['', username, password].join('\0')), callback);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
7
18
|
return callback[kCallbackPromise];
|
|
8
19
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CallbackWithPromise } from '../../apis/callbacks.ts';
|
|
2
2
|
import { type SASLAuthenticationAPI, type SaslAuthenticateResponse } from '../../apis/security/sasl-authenticate-v2.ts';
|
|
3
|
-
import { type Connection } from '../../network/connection.ts';
|
|
3
|
+
import { type Connection, type SASLCredentialProvider } from '../../network/connection.ts';
|
|
4
4
|
export interface ScramAlgorithmDefinition {
|
|
5
5
|
keyLength: number;
|
|
6
6
|
algorithm: string;
|
|
@@ -33,5 +33,5 @@ export declare function hi(definition: ScramAlgorithmDefinition, password: strin
|
|
|
33
33
|
export declare function hmac(definition: ScramAlgorithmDefinition, key: Buffer, data: string | Buffer): Buffer;
|
|
34
34
|
export declare function xor(a: Buffer, b: Buffer): Buffer;
|
|
35
35
|
export declare const defaultCrypto: ScramCryptoModule;
|
|
36
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm,
|
|
37
|
-
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm,
|
|
36
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, crypto: ScramCryptoModule, callback: CallbackWithPromise<SaslAuthenticateResponse>): void;
|
|
37
|
+
export declare function authenticate(authenticateAPI: SASLAuthenticationAPI, connection: Connection, algorithm: ScramAlgorithm, usernameProvider: string | SASLCredentialProvider, passwordProvider: string | SASLCredentialProvider, crypto?: ScramCryptoModule): Promise<SaslAuthenticateResponse>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, createHmac, pbkdf2Sync, randomBytes } from 'node:crypto';
|
|
2
2
|
import { createPromisifiedCallback, kCallbackPromise } from "../../apis/callbacks.js";
|
|
3
3
|
import { AuthenticationError } from "../../errors.js";
|
|
4
|
+
import { getCredential } from "./credential-provider.js";
|
|
4
5
|
const GS2_HEADER = 'n,,';
|
|
5
6
|
const GS2_HEADER_BASE64 = Buffer.from(GS2_HEADER).toString('base64');
|
|
6
7
|
const HMAC_CLIENT_KEY = 'Client Key';
|
|
@@ -57,21 +58,14 @@ export const defaultCrypto = {
|
|
|
57
58
|
hmac,
|
|
58
59
|
xor
|
|
59
60
|
};
|
|
60
|
-
|
|
61
|
-
if (!callback) {
|
|
62
|
-
callback = createPromisifiedCallback();
|
|
63
|
-
}
|
|
61
|
+
function performAuthentication(connection, algorithm, definition, authenticateAPI, crypto, username, password, callback) {
|
|
64
62
|
const { h, hi, hmac, xor } = crypto;
|
|
65
|
-
const definition = ScramAlgorithms[algorithm];
|
|
66
|
-
if (!definition) {
|
|
67
|
-
throw new AuthenticationError(`Unsupported SCRAM algorithm ${algorithm}`);
|
|
68
|
-
}
|
|
69
63
|
const clientNonce = createNonce();
|
|
70
64
|
const clientFirstMessageBare = `n=${sanitizeString(username)},r=${clientNonce}`;
|
|
71
65
|
// First of all, send the first message
|
|
72
66
|
authenticateAPI(connection, Buffer.from(`${GS2_HEADER}${clientFirstMessageBare}`), (error, firstResponse) => {
|
|
73
67
|
if (error) {
|
|
74
|
-
callback(new AuthenticationError('
|
|
68
|
+
callback(new AuthenticationError('SASL authentication failed.', { cause: error }), undefined);
|
|
75
69
|
return;
|
|
76
70
|
}
|
|
77
71
|
const firstData = parseParameters(firstResponse.authBytes);
|
|
@@ -108,7 +102,7 @@ export function authenticate(authenticateAPI, connection, algorithm, username, p
|
|
|
108
102
|
const serverSignature = hmac(definition, serverKey, authMessage);
|
|
109
103
|
authenticateAPI(connection, Buffer.from(`${clientFinalMessageWithoutProof},p=${clientProof.toString('base64')}`), (error, lastResponse) => {
|
|
110
104
|
if (error) {
|
|
111
|
-
callback(new AuthenticationError('
|
|
105
|
+
callback(new AuthenticationError('SASL authentication failed.', { cause: error }), undefined);
|
|
112
106
|
return;
|
|
113
107
|
}
|
|
114
108
|
// Send the last message to the server
|
|
@@ -124,5 +118,25 @@ export function authenticate(authenticateAPI, connection, algorithm, username, p
|
|
|
124
118
|
callback(null, lastResponse);
|
|
125
119
|
});
|
|
126
120
|
});
|
|
121
|
+
}
|
|
122
|
+
export function authenticate(authenticateAPI, connection, algorithm, usernameProvider, passwordProvider, crypto = defaultCrypto, callback) {
|
|
123
|
+
if (!callback) {
|
|
124
|
+
callback = createPromisifiedCallback();
|
|
125
|
+
}
|
|
126
|
+
const definition = ScramAlgorithms[algorithm];
|
|
127
|
+
if (!definition) {
|
|
128
|
+
throw new AuthenticationError(`Unsupported SCRAM algorithm ${algorithm}`);
|
|
129
|
+
}
|
|
130
|
+
getCredential(`SASL/SCRAM-${algorithm} username`, usernameProvider, (error, username) => {
|
|
131
|
+
if (error) {
|
|
132
|
+
return callback(error, undefined);
|
|
133
|
+
}
|
|
134
|
+
getCredential(`SASL/SCRAM-${algorithm} password`, passwordProvider, (error, password) => {
|
|
135
|
+
if (error) {
|
|
136
|
+
return callback(error, undefined);
|
|
137
|
+
}
|
|
138
|
+
performAuthentication(connection, algorithm, definition, authenticateAPI, crypto, username, password, callback);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
127
141
|
return callback[kCallbackPromise];
|
|
128
142
|
}
|
package/dist/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
|
2
2
|
import debug from 'debug';
|
|
3
3
|
import { inspect } from 'node:util';
|
|
4
4
|
export { setTimeout as sleep } from 'node:timers/promises';
|
|
5
|
+
/* c8 ignore start - Hard to test */
|
|
5
6
|
function PromiseWithResolversPolyfill() {
|
|
6
7
|
let resolve;
|
|
7
8
|
let reject;
|
|
@@ -12,7 +13,10 @@ function PromiseWithResolversPolyfill() {
|
|
|
12
13
|
// @ts-expect-error - resolve and reject are assigned in the promise constructor
|
|
13
14
|
return { promise, resolve, reject };
|
|
14
15
|
}
|
|
15
|
-
export const PromiseWithResolvers = Promise.withResolvers
|
|
16
|
+
export const PromiseWithResolvers = Promise.withResolvers
|
|
17
|
+
? Promise.withResolvers.bind(Promise)
|
|
18
|
+
: PromiseWithResolversPolyfill;
|
|
19
|
+
/* c8 ignore end */
|
|
16
20
|
export const ajv = new Ajv2020({ allErrors: true, coerceTypes: false, strict: true });
|
|
17
21
|
export const loggers = {
|
|
18
22
|
protocol: debug('plt:kafka:protocol'),
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = "@platformatic/kafka";
|
|
2
|
-
export const version = "1.
|
|
2
|
+
export const version = "1.12.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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)",
|
|
@@ -26,39 +26,39 @@
|
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"ajv": "^8.17.1",
|
|
29
|
-
"debug": "^4.4.
|
|
29
|
+
"debug": "^4.4.3",
|
|
30
30
|
"fastq": "^1.19.1",
|
|
31
31
|
"mnemonist": "^0.40.3",
|
|
32
32
|
"scule": "^1.3.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"lz4-napi": "^2.
|
|
36
|
-
"snappy": "^7.
|
|
35
|
+
"lz4-napi": "^2.9.0",
|
|
36
|
+
"snappy": "^7.3.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@platformatic/rdkafka": "^4.0.0",
|
|
40
40
|
"@types/debug": "^4.1.12",
|
|
41
|
-
"@types/node": "^22.
|
|
42
|
-
"@types/semver": "^7.7.
|
|
41
|
+
"@types/node": "^22.18.5",
|
|
42
|
+
"@types/semver": "^7.7.1",
|
|
43
43
|
"@watchable/unpromise": "^1.0.2",
|
|
44
|
-
"avsc": "^5.7.
|
|
44
|
+
"avsc": "^5.7.9",
|
|
45
45
|
"c8": "^10.1.3",
|
|
46
46
|
"cleaner-spec-reporter": "^0.5.0",
|
|
47
47
|
"cronometro": "^5.3.0",
|
|
48
|
-
"eslint": "^9.
|
|
48
|
+
"eslint": "^9.35.0",
|
|
49
49
|
"fast-jwt": "^6.0.2",
|
|
50
50
|
"hwp": "^0.4.1",
|
|
51
51
|
"json5": "^2.2.3",
|
|
52
52
|
"kafkajs": "^2.2.4",
|
|
53
|
-
"neostandard": "^0.12.
|
|
54
|
-
"node-rdkafka": "^3.
|
|
55
|
-
"parse5": "^7.
|
|
56
|
-
"prettier": "^3.
|
|
53
|
+
"neostandard": "^0.12.2",
|
|
54
|
+
"node-rdkafka": "^3.5.0",
|
|
55
|
+
"parse5": "^7.3.0",
|
|
56
|
+
"prettier": "^3.6.2",
|
|
57
57
|
"prettier-plugin-space-before-function-paren": "^0.0.8",
|
|
58
58
|
"prom-client": "^15.1.3",
|
|
59
|
-
"semver": "^7.7.
|
|
59
|
+
"semver": "^7.7.2",
|
|
60
60
|
"table": "^6.9.0",
|
|
61
|
-
"typescript": "^5.
|
|
61
|
+
"typescript": "^5.9.2"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">= 20.19.4 || >= 22.18.0 || >= 24.6.0"
|
|
@@ -74,12 +74,9 @@
|
|
|
74
74
|
"test:docker:up": "node scripts/docker.ts up -d --wait",
|
|
75
75
|
"test:docker:down": "node scripts/docker.ts down",
|
|
76
76
|
"ci": "npm run build && npm run lint && npm run test:ci",
|
|
77
|
+
"ci:v20": "npm run lint && rm -rf dist && tsc -p tsconfig.json && node dist/scripts/postbuild.js && cd dist && c8 -c ../test/config/c8-ci.json node --env-file=../test/config/env --no-warnings --test --test-timeout=300000",
|
|
77
78
|
"generate:apis": "node scripts/generate-apis.ts",
|
|
78
79
|
"generate:errors": "node scripts/generate-errors.ts",
|
|
79
|
-
"create:api": "node scripts/create-api.ts"
|
|
80
|
-
"build:v20": "rm -rf dist && tsc -p tsconfig.json",
|
|
81
|
-
"postbuild:v20": "cp package.json dist/package.json && node dist/scripts/postbuild.js",
|
|
82
|
-
"test:v20": "npm run build:v20 && cp -R test/fixtures dist/test/fixtures && node --env-file=test/config/env --no-warnings --test --test-timeout=300000 dist/test/**/*.test.js",
|
|
83
|
-
"test:ci:v20": "npm run build:v20 && cp -R test/fixtures dist/test/fixtures && node --env-file=test/config/env --no-warnings --test --test-timeout=300000 dist/test/**/*.test.js"
|
|
80
|
+
"create:api": "node scripts/create-api.ts"
|
|
84
81
|
}
|
|
85
82
|
}
|