Skip to content

Commit

Permalink
fix(Redis Node): Add support for username auth (#12274)
Browse files Browse the repository at this point in the history
  • Loading branch information
netroy authored Dec 19, 2024
1 parent 38c5ed2 commit 64c0414
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 26 deletions.
7 changes: 7 additions & 0 deletions packages/nodes-base/credentials/Redis.credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export class Redis implements ICredentialType {
},
default: '',
},
{
displayName: 'User',
name: 'user',
type: 'string',
default: '',
hint: 'Leave blank for password-only auth',
},
{
displayName: 'Host',
name: 'host',
Expand Down
3 changes: 2 additions & 1 deletion packages/nodes-base/nodes/Redis/Redis.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getValue,
setValue,
} from './utils';
import type { RedisCredential } from './types';

export class Redis implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -512,7 +513,7 @@ export class Redis implements INodeType {
// have a parameter field for a path. Because it is not possible to set
// array, object via parameter directly (should maybe be possible?!?!)
// Should maybe have a parameter which is JSON.
const credentials = await this.getCredentials('redis');
const credentials = await this.getCredentials<RedisCredential>('redis');

const client = setupRedisClient(credentials);
await client.connect();
Expand Down
3 changes: 2 additions & 1 deletion packages/nodes-base/nodes/Redis/RedisTrigger.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';

import { redisConnectionTest, setupRedisClient } from './utils';
import type { RedisCredential } from './types';

interface Options {
jsonParseBody: boolean;
Expand Down Expand Up @@ -74,7 +75,7 @@ export class RedisTrigger implements INodeType {
};

async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
const credentials = await this.getCredentials('redis');
const credentials = await this.getCredentials<RedisCredential>('redis');

const channels = (this.getNodeParameter('channels') as string).split(',');
const options = this.getNodeParameter('options') as Options;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import type { RedisClientType } from '@redis/client';
import { mock } from 'jest-mock-extended';
import { NodeOperationError, type IExecuteFunctions } from 'n8n-workflow';

const mockClient = mock<RedisClientType>();
import type {
ICredentialsDecrypted,
ICredentialTestFunctions,
IExecuteFunctions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';

const mockClient = mock<RedisClient>();
const createClient = jest.fn().mockReturnValue(mockClient);
jest.mock('redis', () => ({ createClient }));

import { Redis } from '../Redis.node';
import { setupRedisClient } from '../utils';
import { redisConnectionTest, setupRedisClient } from '../utils';
import type { RedisClient } from '../types';

describe('Redis Node', () => {
const node = new Redis();

beforeEach(() => {
jest.clearAllMocks();
createClient.mockReturnValue(mockClient);
});

Expand All @@ -27,7 +33,6 @@ describe('Redis Node', () => {
});
expect(createClient).toHaveBeenCalledWith({
database: 0,
password: undefined,
socket: {
host: 'redis.domain',
port: 1234,
Expand All @@ -45,14 +50,82 @@ describe('Redis Node', () => {
});
expect(createClient).toHaveBeenCalledWith({
database: 0,
password: undefined,
socket: {
host: 'redis.domain',
port: 1234,
tls: true,
},
});
});

it('should set user on auth', () => {
setupRedisClient({
host: 'redis.domain',
port: 1234,
database: 0,
user: 'test_user',
password: 'test_password',
});
expect(createClient).toHaveBeenCalledWith({
database: 0,
username: 'test_user',
password: 'test_password',
socket: {
host: 'redis.domain',
port: 1234,
tls: false,
},
});
});
});

describe('redisConnectionTest', () => {
const thisArg = mock<ICredentialTestFunctions>({});
const credentials = mock<ICredentialsDecrypted>({
data: {
host: 'localhost',
port: 6379,
user: 'username',
password: 'password',
database: 0,
},
});
const redisOptions = {
socket: {
host: 'localhost',
port: 6379,
tls: false,
},
database: 0,
username: 'username',
password: 'password',
};

it('should return success when connection is established', async () => {
const result = await redisConnectionTest.call(thisArg, credentials);

expect(result).toEqual({
status: 'OK',
message: 'Connection successful!',
});
expect(createClient).toHaveBeenCalledWith(redisOptions);
expect(mockClient.connect).toHaveBeenCalled();
expect(mockClient.ping).toHaveBeenCalled();
});

it('should return error when connection fails', async () => {
mockClient.connect.mockRejectedValue(new Error('Connection failed'));

const result = await redisConnectionTest.call(thisArg, credentials);

expect(result).toEqual({
status: 'Error',
message: 'Connection failed',
});
expect(createClient).toHaveBeenCalledWith(redisOptions);
expect(mockClient.connect).toHaveBeenCalled();
expect(mockClient.ping).not.toHaveBeenCalled();
});
});

describe('operations', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { captor, mock } from 'jest-mock-extended';
import type { ICredentialDataDecryptedObject, ITriggerFunctions } from 'n8n-workflow';

import { RedisTrigger } from '../RedisTrigger.node';
import { type RedisClientType, setupRedisClient } from '../utils';
import { setupRedisClient } from '../utils';
import type { RedisClient } from '../types';

jest.mock('../utils', () => {
const mockRedisClient = mock<RedisClientType>();
const mockRedisClient = mock<RedisClient>();
return {
setupRedisClient: jest.fn().mockReturnValue(mockRedisClient),
};
Expand Down
12 changes: 12 additions & 0 deletions packages/nodes-base/nodes/Redis/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { createClient } from 'redis';

export type RedisClient = ReturnType<typeof createClient>;

export type RedisCredential = {
host: string;
port: number;
ssl?: boolean;
database: number;
user?: string;
password?: string;
};
28 changes: 13 additions & 15 deletions packages/nodes-base/nodes/Redis/utils.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import type {
ICredentialDataDecryptedObject,
ICredentialTestFunctions,
ICredentialsDecrypted,
IDataObject,
IExecuteFunctions,
INodeCredentialTestResult,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { createClient } from 'redis';

import { type RedisClientOptions, createClient } from 'redis';
export type RedisClientType = ReturnType<typeof createClient>;
import type { RedisCredential, RedisClient } from './types';

export function setupRedisClient(credentials: ICredentialDataDecryptedObject): RedisClientType {
const redisOptions: RedisClientOptions = {
export function setupRedisClient(credentials: RedisCredential): RedisClient {
return createClient({
socket: {
host: credentials.host as string,
port: credentials.port as number,
host: credentials.host,
port: credentials.port,
tls: credentials.ssl === true,
},
database: credentials.database as number,
password: (credentials.password as string) || undefined,
};

return createClient(redisOptions);
database: credentials.database,
username: credentials.user || undefined,
password: credentials.password || undefined,
});
}

export async function redisConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
const credentials = credential.data as RedisCredential;

try {
const client = setupRedisClient(credentials);
Expand Down Expand Up @@ -88,7 +86,7 @@ export function convertInfoToObject(stringData: string): IDataObject {
return returnData;
}

export async function getValue(client: RedisClientType, keyName: string, type?: string) {
export async function getValue(client: RedisClient, keyName: string, type?: string) {
if (type === undefined || type === 'automatic') {
// Request the type first
type = await client.type(keyName);
Expand All @@ -107,7 +105,7 @@ export async function getValue(client: RedisClientType, keyName: string, type?:

export async function setValue(
this: IExecuteFunctions,
client: RedisClientType,
client: RedisClient,
keyName: string,
value: string | number | object | string[] | number[],
expire: boolean,
Expand Down

0 comments on commit 64c0414

Please sign in to comment.