mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-02 05:55:33 +02:00
fix(core): Reject reserved system column names when creating data tables (#27064)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Charlie Kolb <charlie@n8n.io>
This commit is contained in:
parent
689d873384
commit
dbb7fa47c9
|
|
@ -100,6 +100,24 @@ describe('POST /projects/:projectId/data-tables', () => {
|
|||
await authOwnerAgent.post(`/projects/${project.id}/data-tables`).send(payload).expect(400);
|
||||
});
|
||||
|
||||
test('should return 409 when column name conflicts with system columns', async () => {
|
||||
const project = await createTeamProject(undefined, owner);
|
||||
|
||||
for (const systemColumnName of ['id', 'ID', 'createdAt', 'updatedAt']) {
|
||||
const payload = {
|
||||
name: `Table with ${systemColumnName}`,
|
||||
columns: [{ name: systemColumnName, type: 'string' }],
|
||||
};
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.post(`/projects/${project.id}/data-tables`)
|
||||
.send(payload)
|
||||
.expect(409);
|
||||
|
||||
expect(response.body.message).toContain('reserved');
|
||||
}
|
||||
});
|
||||
|
||||
test('should not create data table if user has project:viewer role in team project', async () => {
|
||||
const project = await createTeamProject(undefined, owner);
|
||||
await linkUserToProject(member, project, 'project:viewer');
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { DataTableRepository } from '../data-table.repository';
|
|||
import { DataTableService } from '../data-table.service';
|
||||
import { mockDataTableSizeValidator } from './test-helpers';
|
||||
import { DataTableColumnNameConflictError } from '../errors/data-table-column-name-conflict.error';
|
||||
import { DataTableSystemColumnNameConflictError } from '../errors/data-table-system-column-name-conflict.error';
|
||||
import { DataTableColumnNotFoundError } from '../errors/data-table-column-not-found.error';
|
||||
import { DataTableNameConflictError } from '../errors/data-table-name-conflict.error';
|
||||
import { DataTableNotFoundError } from '../errors/data-table-not-found.error';
|
||||
|
|
@ -140,6 +141,25 @@ describe('dataTable', () => {
|
|||
expect(project.name).toBe(project1.name);
|
||||
});
|
||||
|
||||
it('should reject columns named after system columns (id, createdAt, updatedAt)', async () => {
|
||||
for (const systemColumnName of [
|
||||
'id',
|
||||
'ID',
|
||||
'Id',
|
||||
'createdAt',
|
||||
'CreatedAt',
|
||||
'updatedAt',
|
||||
'UpdatedAt',
|
||||
]) {
|
||||
await expect(
|
||||
dataTableService.createDataTable(project1.id, {
|
||||
name: `table_with_${systemColumnName}`,
|
||||
columns: [{ name: systemColumnName, type: 'string' }],
|
||||
}),
|
||||
).rejects.toThrow(DataTableSystemColumnNameConflictError);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return an error if name/project combination already exists', async () => {
|
||||
const name = 'dataTable';
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,11 @@ export class DataTableColumnRepository extends Repository<DataTableColumn> {
|
|||
* Validates that a column name is not reserved as a system column
|
||||
*/
|
||||
private validateNotSystemColumn(columnName: string): void {
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.includes(columnName)) {
|
||||
const lowerName = columnName.toLowerCase();
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.some((sc) => sc.toLowerCase() === lowerName)) {
|
||||
throw new DataTableSystemColumnNameConflictError(columnName);
|
||||
}
|
||||
if (columnName === DATA_TABLE_SYSTEM_TESTING_COLUMN) {
|
||||
if (lowerName === DATA_TABLE_SYSTEM_TESTING_COLUMN.toLowerCase()) {
|
||||
throw new DataTableSystemColumnNameConflictError(columnName, 'testing');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,10 @@ export class DataTableController {
|
|||
} catch (e: unknown) {
|
||||
if (!(e instanceof Error)) {
|
||||
throw e;
|
||||
} else if (e instanceof DataTableNameConflictError) {
|
||||
} else if (
|
||||
e instanceof DataTableNameConflictError ||
|
||||
e instanceof DataTableSystemColumnNameConflictError
|
||||
) {
|
||||
throw new ConflictError(e.message);
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import { GlobalConfig } from '@n8n/config';
|
|||
import { Project, withTransaction } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
import { DataSource, EntityManager, Repository, SelectQueryBuilder } from '@n8n/typeorm';
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
import {
|
||||
DATA_TABLE_SYSTEM_COLUMNS,
|
||||
DATA_TABLE_SYSTEM_TESTING_COLUMN,
|
||||
UnexpectedError,
|
||||
} from 'n8n-workflow';
|
||||
import type { DataTableInfo, DataTablesSizeData } from 'n8n-workflow';
|
||||
|
||||
import { DataTableColumn } from './data-table-column.entity';
|
||||
|
|
@ -16,6 +20,7 @@ import { DataTableDDLService } from './data-table-ddl.service';
|
|||
import { DataTable } from './data-table.entity';
|
||||
import { DataTableUserTableName } from './data-table.types';
|
||||
import { DataTableNameConflictError } from './errors/data-table-name-conflict.error';
|
||||
import { DataTableSystemColumnNameConflictError } from './errors/data-table-system-column-name-conflict.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
import { isValidColumnName, toTableId, toTableName } from './utils/sql-utils';
|
||||
|
||||
|
|
@ -56,6 +61,16 @@ export class DataTableRepository extends Repository<DataTable> {
|
|||
throw new DataTableValidationError(DATA_TABLE_COLUMN_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
for (const col of columns) {
|
||||
const lowerName = col.name.toLowerCase();
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.some((sc) => sc.toLowerCase() === lowerName)) {
|
||||
throw new DataTableSystemColumnNameConflictError(col.name);
|
||||
}
|
||||
if (lowerName === DATA_TABLE_SYSTEM_TESTING_COLUMN.toLowerCase()) {
|
||||
throw new DataTableSystemColumnNameConflictError(col.name, 'testing');
|
||||
}
|
||||
}
|
||||
|
||||
const dataTable = em.create(DataTable, { name, columns, projectId });
|
||||
|
||||
await em.insert(DataTable, dataTable);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ const getColumnTypeOptions = (compatibleTypes: ColumnType[]) => {
|
|||
};
|
||||
|
||||
const validateColumnName = (columnName: string): string | undefined => {
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.includes(columnName)) {
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.some((sc) => sc.toLowerCase() === columnName.toLowerCase())) {
|
||||
return i18n.baseText('dataTable.import.systemColumnName', {
|
||||
interpolate: { columnName },
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user