mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
fix(MySQL Node): Fix potential sql injection (#13818)
This commit is contained in:
@@ -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',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -30,20 +30,21 @@ export async function createConnection(
|
|||||||
|
|
||||||
export async function searchTables(
|
export async function searchTables(
|
||||||
this: ILoadOptionsFunctions,
|
this: ILoadOptionsFunctions,
|
||||||
query?: string,
|
tableName?: string,
|
||||||
): Promise<INodeListSearchResult> {
|
): Promise<INodeListSearchResult> {
|
||||||
const credentials = await this.getCredentials('mySql');
|
const credentials = await this.getCredentials('mySql');
|
||||||
const connection = await createConnection(credentials);
|
const connection = await createConnection(credentials);
|
||||||
const sql = `
|
const sql = `SELECT table_name
|
||||||
SELECT table_name FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = '${credentials.database}'
|
WHERE table_schema = ?
|
||||||
and table_name like '%${query || ''}%'
|
AND table_name LIKE ?
|
||||||
ORDER BY table_name
|
ORDER BY table_name`;
|
||||||
`;
|
|
||||||
const [rows] = await connection.query(sql);
|
const values = [credentials.database, `%${tableName ?? ''}%`];
|
||||||
const results = (rows as IDataObject[]).map((r) => ({
|
const [rows] = await connection.query(sql, values);
|
||||||
name: r.TABLE_NAME as string,
|
const results = (rows as IDataObject[]).map((table) => ({
|
||||||
value: r.TABLE_NAME as string,
|
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();
|
await connection.end();
|
||||||
return { results };
|
return { results };
|
||||||
|
|||||||
Reference in New Issue
Block a user