fix(MySQL Node): Fix potential sql injection (#13818)

This commit is contained in:
Jon
2025-03-13 08:19:14 +00:00
committed by GitHub
parent b5632545c5
commit dd4f51cff5
2 changed files with 106 additions and 11 deletions

View File

@@ -0,0 +1,94 @@
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
import mysql2 from 'mysql2/promise';
import type { ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
import { searchTables } from '../../v1/GenericFunctions';
jest.mock('mysql2/promise');
describe('MySQL / v1 / Generic Functions', () => {
let mockLoadOptionsFunctions: ILoadOptionsFunctions;
beforeEach(() => {
jest.resetAllMocks();
mockLoadOptionsFunctions = {
getCredentials: jest.fn().mockResolvedValue({
database: 'test_db',
}),
} as unknown as ILoadOptionsFunctions;
});
describe('searchTables', () => {
it('should return matching tables', async () => {
const mockRows = [{ table_name: 'users' }, { table_name: 'products' }];
const mockQuery = jest.fn().mockResolvedValue([mockRows]);
const mockEnd = jest.fn().mockResolvedValue(undefined);
(mysql2.createConnection as jest.Mock).mockResolvedValue({
query: mockQuery,
end: mockEnd,
});
const result: INodeListSearchResult = await searchTables.call(
mockLoadOptionsFunctions,
'user',
);
expect(result).toEqual({
results: [
{ name: 'users', value: 'users' },
{ name: 'products', value: 'products' },
],
});
expect(mockQuery).toHaveBeenCalledWith(
`SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
AND table_name LIKE ?
ORDER BY table_name`,
['test_db', '%user%'],
);
expect(mockEnd).toHaveBeenCalled();
});
it('should handle empty search query', async () => {
const mockRows: any[] = [];
const mockQuery = jest.fn().mockResolvedValue([mockRows]);
const mockEnd = jest.fn().mockResolvedValue(undefined);
(mysql2.createConnection as jest.Mock).mockResolvedValue({
query: mockQuery,
end: mockEnd,
});
const result = await searchTables.call(mockLoadOptionsFunctions);
expect(result).toEqual({ results: [] });
expect(mockQuery).toHaveBeenCalledWith(
`SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
AND table_name LIKE ?
ORDER BY table_name`,
['test_db', '%%'],
);
expect(mockEnd).toHaveBeenCalled();
});
it('should handle database errors', async () => {
const mockError = new Error('Database connection failed');
(mysql2.createConnection as jest.Mock).mockRejectedValue(mockError);
await expect(searchTables.call(mockLoadOptionsFunctions)).rejects.toThrow(
'Database connection failed',
);
});
});
});

View File

@@ -30,20 +30,21 @@ export async function createConnection(
export async function searchTables(
this: ILoadOptionsFunctions,
query?: string,
tableName?: string,
): Promise<INodeListSearchResult> {
const credentials = await this.getCredentials('mySql');
const connection = await createConnection(credentials);
const sql = `
SELECT table_name FROM information_schema.tables
WHERE table_schema = '${credentials.database}'
and table_name like '%${query || ''}%'
ORDER BY table_name
`;
const [rows] = await connection.query(sql);
const results = (rows as IDataObject[]).map((r) => ({
name: r.TABLE_NAME as string,
value: r.TABLE_NAME as string,
const sql = `SELECT table_name
FROM information_schema.tables
WHERE table_schema = ?
AND table_name LIKE ?
ORDER BY table_name`;
const values = [credentials.database, `%${tableName ?? ''}%`];
const [rows] = await connection.query(sql, values);
const results = (rows as IDataObject[]).map((table) => ({
name: (table.table_name as string) || (table.TABLE_NAME as string),
value: (table.table_name as string) || (table.TABLE_NAME as string),
}));
await connection.end();
return { results };