@carbonorm/carbonnode 3.5.4 → 3.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,35 @@ export abstract class ConditionBuilder<
8
8
  G extends OrmGenerics
9
9
  > extends AggregateBuilder<G> {
10
10
 
11
- protected aliasMappings: Record<string, string> = {};
11
+ protected aliasMap: Record<string, string> = {};
12
+
13
+ protected initAlias(baseTable: string, joins?: any): void {
14
+ this.aliasMap = { [baseTable]: baseTable };
15
+
16
+ if (!joins) return;
17
+
18
+ for (const joinType in joins) {
19
+ for (const raw in joins[joinType]) {
20
+ const [table, alias] = raw.split(' ');
21
+ this.aliasMap[alias || table] = table;
22
+ }
23
+ }
24
+ }
25
+
26
+ protected isColumnRef(ref: string): boolean {
27
+ if (typeof ref !== 'string' || !ref.includes('.')) return false;
28
+
29
+ const [prefix, column] = ref.split('.', 2);
30
+ const tableName = this.aliasMap[prefix] || prefix;
31
+ const table = this.config.C6?.TABLES?.[tableName];
32
+ if (!table) return false;
33
+
34
+ const fullKey = `${tableName}.${column}`;
35
+ if (table.COLUMNS && (fullKey in table.COLUMNS)) return true;
36
+ if (table.COLUMNS && Object.values(table.COLUMNS).includes(column)) return true;
37
+
38
+ return false;
39
+ }
12
40
 
13
41
  abstract build(table: string): SqlBuilderResult;
14
42
 
@@ -27,16 +55,6 @@ export abstract class ConditionBuilder<
27
55
  C6C.ST_DISTANCE_SPHERE
28
56
  ]);
29
57
 
30
- private isTableReference(val: any): boolean {
31
- if (typeof val !== 'string' || !val.includes('.')) return false;
32
- const [prefix, column] = val.split('.');
33
- const tableName = this.aliasMappings[prefix] ?? prefix;
34
- return (
35
- typeof this.config.C6?.TABLES[tableName] === 'object' &&
36
- column in this.config.C6.TABLES[tableName].COLUMNS
37
- );
38
- }
39
-
40
58
  private validateOperator(op: string) {
41
59
  if (!this.OPERATORS.has(op)) {
42
60
  throw new Error(`Invalid or unsupported SQL operator detected: '${op}'`);
@@ -82,8 +100,14 @@ export abstract class ConditionBuilder<
82
100
  }
83
101
  }
84
102
 
103
+ const leftIsCol = this.isColumnRef(column);
104
+ const rightIsCol = typeof value === 'string' && this.isColumnRef(value);
105
+
106
+ if (!leftIsCol && !rightIsCol) {
107
+ throw new Error(`Potential SQL injection detected: '${column} ${op} ${value}'`);
108
+ }
109
+
85
110
  this.validateOperator(op);
86
- const leftIsRef: boolean = this.isTableReference(column);
87
111
 
88
112
  if (op === C6C.MATCH_AGAINST && Array.isArray(value)) {
89
113
  const [search, mode] = value;
@@ -108,7 +132,7 @@ export abstract class ConditionBuilder<
108
132
  break;
109
133
  }
110
134
 
111
- if (!leftIsRef) {
135
+ if (!leftIsCol) {
112
136
  throw new Error(`MATCH_AGAINST requires a table reference as the left operand. Column '${column}' is not a valid table reference.`);
113
137
  }
114
138
  const matchClause = `(MATCH(${column}) ${againstClause})`;
@@ -117,9 +141,11 @@ export abstract class ConditionBuilder<
117
141
  }
118
142
 
119
143
  if ((op === C6C.IN || op === C6C.NOT_IN) && Array.isArray(value)) {
120
- const placeholders = value.map(v => this.addParam(params, column, v)).join(', ');
144
+ const placeholders = value.map(v =>
145
+ this.isColumnRef(v) ? v : this.addParam(params, column, v)
146
+ ).join(', ');
121
147
  const normalized = op.replace('_', ' ');
122
- if (!leftIsRef) {
148
+ if (!leftIsCol) {
123
149
  throw new Error(`IN operator requires a table reference as the left operand. Column '${column}' is not a valid table reference.`);
124
150
  }
125
151
  return `( ${column} ${normalized} (${placeholders}) )`;
@@ -130,23 +156,22 @@ export abstract class ConditionBuilder<
130
156
  throw new Error(`BETWEEN operator requires an array of two values`);
131
157
  }
132
158
  const [start, end] = value;
133
- if (!leftIsRef) {
159
+ if (!leftIsCol) {
134
160
  throw new Error(`BETWEEN operator requires a table reference as the left operand. Column '${column}' is not a valid table reference.`);
135
161
  }
136
162
  return `(${column}) ${op.replace('_', ' ')} ${this.addParam(params, column, start)} AND ${this.addParam(params, column, end)}`;
137
163
  }
138
164
 
139
- const rightIsRef: boolean = this.isTableReference(value);
140
165
 
141
- if (leftIsRef && rightIsRef) {
166
+ if (leftIsCol && rightIsCol) {
142
167
  return `(${column}) ${op} ${value}`;
143
168
  }
144
169
 
145
- if (leftIsRef && !rightIsRef) {
170
+ if (leftIsCol && !rightIsCol) {
146
171
  return `(${column}) ${op} ${this.addParam(params, column, value)}`;
147
172
  }
148
173
 
149
- if (rightIsRef) {
174
+ if (rightIsCol) {
150
175
  return `(${this.addParam(params, column, column)}) ${op} ${value}`;
151
176
  }
152
177
 
@@ -11,9 +11,7 @@ export abstract class JoinBuilder<G extends OrmGenerics> extends ConditionBuilde
11
11
 
12
12
  for (const raw in joinArgs[joinType]) {
13
13
  const [table, alias] = raw.split(' ');
14
- if (alias) {
15
- this.aliasMappings[alias] = table;
16
- }
14
+ this.aliasMap[alias || table] = table;
17
15
  const onClause = this.buildBooleanJoinedConditions(joinArgs[joinType][raw], true, params);
18
16
  const joinSql = alias ? `\`${table}\` AS \`${alias}\`` : `\`${table}\``;
19
17
  sql += ` ${joinKind} JOIN ${joinSql} ON ${onClause}`;
@@ -6,8 +6,8 @@ export class DeleteQueryBuilder<G extends OrmGenerics> extends JoinBuilder<G> {
6
6
  build(
7
7
  table: string
8
8
  ): SqlBuilderResult {
9
- this.aliasMappings = {};
10
9
  const params = this.useNamedParams ? {} : [];
10
+ this.initAlias(table, this.request.JOIN);
11
11
 
12
12
  let sql = `DELETE \`${table}\` FROM \`${table}\``;
13
13
 
@@ -14,7 +14,6 @@ export class PostQueryBuilder<G extends OrmGenerics> extends ConditionBuilder<G>
14
14
  }
15
15
 
16
16
  build(table: string) {
17
- this.aliasMappings = {};
18
17
  const verb = C6C.REPLACE in this.request ? C6C.REPLACE : C6C.INSERT;
19
18
  const body = verb in this.request ? this.request[verb] : this.request;
20
19
  const keys = Object.keys(body);
@@ -8,8 +8,8 @@ export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder
8
8
  table: string,
9
9
  isSubSelect: boolean = false
10
10
  ): SqlBuilderResult {
11
- this.aliasMappings = {};
12
11
  const args = this.request;
12
+ this.initAlias(table, args.JOIN);
13
13
  const params = this.useNamedParams ? {} : [];
14
14
  const selectList = args.SELECT ?? ['*'];
15
15
  const selectFields = selectList
@@ -8,9 +8,9 @@ export class UpdateQueryBuilder<G extends OrmGenerics> extends PaginationBuilder
8
8
  build(
9
9
  table: string,
10
10
  ): SqlBuilderResult {
11
- this.aliasMappings = {};
12
11
  const args = this.request;
13
12
  const params = this.useNamedParams ? {} : [];
13
+ this.initAlias(table, args.JOIN);
14
14
  let sql = `UPDATE \`${table}\``;
15
15
 
16
16
  if (args.JOIN) {