From ee9738caa7daee0173150df96d022b071f6ea417 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Wed, 30 Oct 2024 11:24:51 +0100 Subject: [PATCH 1/2] Fix authentication node not created --- src/PulsarDefinition.ts | 2 +- src/authentication/pulsar-authentication.ts | 74 ++++++++++++--------- src/client/pulsar-client.html.ts | 2 +- src/client/pulsar-client.ts | 38 +++++------ test/node-red/Nodes.spec.ts | 38 ++++++++++- 5 files changed, 96 insertions(+), 58 deletions(-) diff --git a/src/PulsarDefinition.ts b/src/PulsarDefinition.ts index dbf4ae6..fdd2fe6 100644 --- a/src/PulsarDefinition.ts +++ b/src/PulsarDefinition.ts @@ -28,7 +28,7 @@ export interface NoAuthentication { blank: true } export type AuthenticationImpl = AuthenticationToken | AuthenticationOauth2 | AuthenticationTls | NoAuthentication -export interface PulsarAuthentication extends PulsarAuthenticationProperties, NodeRED.NodeDef { } +export interface PulsarAuthenticationConfig extends PulsarAuthenticationProperties, NodeRED.NodeDef { } //Client export interface PulsarClientProperties { diff --git a/src/authentication/pulsar-authentication.ts b/src/authentication/pulsar-authentication.ts index a8708af..8a58f66 100644 --- a/src/authentication/pulsar-authentication.ts +++ b/src/authentication/pulsar-authentication.ts @@ -1,43 +1,14 @@ import * as NodeRED from "node-red"; -import {AuthenticationImpl, PulsarAuthentication, PulsarAuthenticationId} from "../PulsarDefinition"; +import {AuthenticationImpl, PulsarAuthenticationConfig, PulsarAuthenticationId} from "../PulsarDefinition"; import {AuthenticationOauth2, AuthenticationTls, AuthenticationToken} from "pulsar-client"; import {loadToken, parseToken} from "../Token"; +type RuntimeNode = NodeRED.Node export = (RED: NodeRED.NodeAPI): void => { RED.nodes.registerType(PulsarAuthenticationId, - function (this: NodeRED.Node, config: PulsarAuthentication): void { - async function resolveAuthentication(config: PulsarAuthentication): Promise { - switch (config.authType) { - case 'Token': - if(!config.jwtToken) { - return {} - } - const token = parseToken(config.jwtToken) - return new AuthenticationToken({token: await loadToken(token)}) - case 'Oauth2': - if(!config.oauthType || !config.oauthIssuerUrl) { - return {} - } - return new AuthenticationOauth2({ - type: config.oauthType, - issuer_url: config.oauthIssuerUrl, - client_id: config.oauthClientId, - client_secret: config.oauthClientSecret, - private_key: config.oauthPrivateKey, - audience: config.oauthAudience, - scope: config.oauthScope - }) - case 'TLS': - if(!config.tlsCertificatePath || !config.tlsPrivateKeyPath) { - return {} - } - return new AuthenticationTls({ - certificatePath: config.tlsCertificatePath, - privateKeyPath: config.tlsPrivateKeyPath - }) - } - } + function (this: RuntimeNode, config: PulsarAuthenticationConfig): void { + RED.nodes.createNode(this, config) resolveAuthentication(config).then( (auth) => { this.credentials = auth @@ -49,3 +20,40 @@ export = (RED: NodeRED.NodeAPI): void => { } ) } + +async function resolveAuthentication(config: PulsarAuthenticationConfig): Promise { + switch (config.authType) { + case 'Token': + return createAuthenticationToken(config) + case 'Oauth2': + if(!config.oauthType || !config.oauthIssuerUrl) { + return {} + } + return new AuthenticationOauth2({ + type: config.oauthType, + issuer_url: config.oauthIssuerUrl, + client_id: config.oauthClientId, + client_secret: config.oauthClientSecret, + private_key: config.oauthPrivateKey, + audience: config.oauthAudience, + scope: config.oauthScope + }) + case 'TLS': + if(!config.tlsCertificatePath || !config.tlsPrivateKeyPath) { + return {} + } + return new AuthenticationTls({ + certificatePath: config.tlsCertificatePath, + privateKeyPath: config.tlsPrivateKeyPath + }) + } +} + +async function createAuthenticationToken(config: PulsarAuthenticationConfig): Promise { + if(!config.jwtToken) { + return {} + } + const token = parseToken(config.jwtToken) + const tokenContent = await loadToken(token) + return new AuthenticationToken({token: tokenContent}) +} diff --git a/src/client/pulsar-client.html.ts b/src/client/pulsar-client.html.ts index dd37a97..cc9517d 100644 --- a/src/client/pulsar-client.html.ts +++ b/src/client/pulsar-client.html.ts @@ -22,7 +22,7 @@ function displayTlsFields(show: boolean): void { * Registration of the 'pulsar-client' type with its configuration. */ RED.nodes.registerType(CLIENT_ID, { - category: 'config', + category: PULSAR_CONFIG, icon: 'font-awesome/fa-server', color: PULSAR_COLOR, defaults: { diff --git a/src/client/pulsar-client.ts b/src/client/pulsar-client.ts index 10a18a0..bb246e4 100644 --- a/src/client/pulsar-client.ts +++ b/src/client/pulsar-client.ts @@ -1,8 +1,5 @@ import * as NodeRED from 'node-red' import { - AuthenticationOauth2, - AuthenticationTls, - AuthenticationToken, Client, ClientConfig, LogLevel @@ -15,16 +12,15 @@ import { } from "../PulsarDefinition"; import {parseBoolean, parseNumber, parseNonEmptyString} from "../PulsarConfig"; -type ClientAuthentication = AuthenticationToken | AuthenticationOauth2 | AuthenticationTls | undefined + type RuntimeNode = NodeRED.Node -type AuthenticationNode = NodeRED.Node -function createPulsarConfigNode(auth: AuthenticationNode, config: PulsarClientConfig): ClientConfig { +function createPulsarConfigNode(auth: AuthenticationImpl | undefined, config: PulsarClientConfig): ClientConfig { return { serviceUrl: config.serviceUrl, - authentication: resolveAuthentication(auth), + authentication: auth, operationTimeoutSeconds: parseNumber(config.operationTimeoutSeconds), ioThreads: parseNumber(config.ioThreads), messageListenerThreads: parseNumber(config.messageListenerThreads), @@ -34,16 +30,17 @@ function createPulsarConfigNode(auth: AuthenticationNode, config: PulsarClientCo tlsValidateHostname: parseBoolean(config.tlsValidateHostname), tlsAllowInsecureConnection: parseBoolean(config.tlsAllowInsecureConnection), statsIntervalInSeconds: parseNumber(config.statsIntervalInSeconds), - listenerName: parseNonEmptyString(config.listenerName) + listenerName: parseNonEmptyString(config.listenerName), } } export = (RED: NodeRED.NodeAPI): void => { RED.nodes.registerType(PulsarClientId, function (this: RuntimeNode, config: PulsarClientConfig): void { - const authNode = RED.nodes.getNode(config.authenticationNodeId) as NodeRED.Node RED.nodes.createNode(this, config) - const client = createClient(this, createPulsarConfigNode(authNode, config)) + this.debug('Creating pulsar client') + const authentication = getAuthentication(RED, config) + const client = createClient(this, createPulsarConfigNode(authentication, config)) if(client) { this.credentials = client mapClientLoginToNode(this) @@ -57,19 +54,16 @@ export = (RED: NodeRED.NodeAPI): void => { ) } +function getAuthentication(RED: NodeRED.NodeAPI, config: PulsarClientConfig): AuthenticationImpl | undefined { + if(!config.authenticationNodeId) { + return undefined + } -/** - * Resolves the authentication based on the provided Node. - * - * @param {NodeRED.Node} [node] - The Node containing the authentication information. - * @return {ClientAuthentication} - The resolved ClientAuthentication. Returns undefined if no authentication is provided or if the authentication is NoAuthentication. - */ -function resolveAuthentication(node?: NodeRED.Node): ClientAuthentication { - if(!node) { + const authNode = RED.nodes.getNode(config.authenticationNodeId) as NodeRED.Node + if(!authNode) { return undefined } - const auth = node.credentials - //If the authentication is NoAuthentication, return undefined + const auth = authNode.credentials if((auth as NoAuthentication).blank) { return undefined } @@ -107,6 +101,10 @@ function createClient(node: NodeRED.Node, clientConfig: ClientConfig): C * @return {void} */ function closeClient(node: NodeRED.Node, done: () => void): void { + if(!node) { + done() + return + } const client = node.credentials if (client) { client.close().then(() => { diff --git a/test/node-red/Nodes.spec.ts b/test/node-red/Nodes.spec.ts index 7bb7394..db8a579 100644 --- a/test/node-red/Nodes.spec.ts +++ b/test/node-red/Nodes.spec.ts @@ -1,13 +1,18 @@ +// @ts-ignore import helper, {TestFlowsItem} from "node-red-node-test-helper"; import pulsarClientNode from "../../src/client/pulsar-client"; +import pulsarAuthenticationNode from "../../src/authentication/pulsar-authentication"; import pulsarSchemaNode from "../../src/schema/pulsar-schema"; import pulsarProducerNode from "../../src/producer/pulsar-producer"; import pulsarConsumerNode from "../../src/consumer/pulsar-consumer"; import pulsarReaderNode from "../../src/reader/pulsar-reader"; -import Pulsar, {Client, SchemaInfo} from 'pulsar-client'; +import Pulsar, {AuthenticationToken, Client, SchemaInfo} from 'pulsar-client'; import {GenericContainer, StartedTestContainer, Wait} from "testcontainers"; // @ts-ignore import { + AuthenticationImpl, + PulsarAuthenticationConfig, + PulsarAuthenticationId, PulsarClientConfig, PulsarClientId, PulsarConsumerConfig, PulsarConsumerId, @@ -24,6 +29,10 @@ import {expect} from "chai"; const logger = new Logger(); + +const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ" + + describe('Pulsar Integration Test', function () { const topic = "it-" + v4(); @@ -56,6 +65,17 @@ describe('Pulsar Integration Test', function () { }) }); + it('Authentication should be loaded', async function () { + await helper.load([pulsarAuthenticationNode], [authenticationConf()]); + const node = helper.getNode("authentication") as Node; + node.should.not.be.null; + const auth: AuthenticationImpl = node.credentials; + auth.should.not.be.null; + expect(auth).to.be.an.instanceOf(AuthenticationToken); + const expectedAuth: AuthenticationToken = new AuthenticationToken({token: jwtToken}); + expect(expectedAuth).to.be.eql(auth); + }) + it('Schema should be loaded', async function () { await helper.load([pulsarSchemaNode], [schemaConf()]); const node = helper.getNode("schema") as Node; @@ -183,6 +203,7 @@ describe('Pulsar Integration Test', function () { }); +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function createPulsarContainer() { logger.info("Starting pulsar container") return new GenericContainer("apachepulsar/pulsar") @@ -192,11 +213,13 @@ async function createPulsarContainer() { .start() } +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function stopPulsarContainer(container: StartedTestContainer) { console.log("Stopping pulsar container") return container.stop() } +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function createTopic(broker: StartedTestContainer, topic: string) { const config = { headers: { @@ -217,12 +240,21 @@ const jsonSchema = { helper.init(require.resolve('node-red')); -// @ts-ignore function clientConf(broker: StartedTestContainer): TestFlowsItem { return { id: "client", type: PulsarClientId, - serviceUrl: brokerUrl(broker) + serviceUrl: brokerUrl(broker), + authenticationNodeId: "authentication", + } +} + +function authenticationConf(): TestFlowsItem { + return { + id: "authentication", + type: PulsarAuthenticationId, + authType: "Token", + jwtToken: jwtToken } } From 9ffcc8c2f8d64f1250e240f3802605cbcb1e5704 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Wed, 30 Oct 2024 11:27:24 +0100 Subject: [PATCH 2/2] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d6c808..cde734e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ng-galien/node-red-pulsar", - "version": "1.1.4", + "version": "1.1.5", "description": "Node-RED nodes for Apache Pulsar", "repository": { "type": "git",