diff --git a/CHANGELOG.md b/CHANGELOG.md index b89e0ab1..112e4a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [4.4.3] - 2020-04-18 + +### Bug Fixes + +- **crud** fixed returning `pageCount` in some cases ([#465](https://github.com/nestjsx/crud/pull/465)) +- **typeorm** fixed critical bug with possible SQL injections when using query `?sort=` (big kudos to João Maurício) +- **typeorm** fixed filter conditions for LIKE/iLIKE operators ([#395](https://github.com/nestjsx/crud/pull/395)) + ## [4.4.2] - 2020-03-17 ### Bug Fixes @@ -117,6 +125,7 @@ - several fixes +[4.4.3]: https://github.com/nestjsx/crud/compare/v4.4.2...v4.4.3 [4.4.2]: https://github.com/nestjsx/crud/compare/v4.4.1...v4.4.2 [4.4.1]: https://github.com/nestjsx/crud/compare/v4.4.0...v4.4.1 [4.4.0]: https://github.com/nestjsx/crud/compare/v4.3.0...v4.4.0 diff --git a/integration/crud-typeorm/orm.config.ts b/integration/crud-typeorm/orm.config.ts index 6ce8b1fc..44f0406f 100644 --- a/integration/crud-typeorm/orm.config.ts +++ b/integration/crud-typeorm/orm.config.ts @@ -1,5 +1,6 @@ import { join } from 'path'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { isNil } from '@nestjsx/util'; export const withCache: TypeOrmModuleOptions = { type: 'postgres', @@ -9,7 +10,9 @@ export const withCache: TypeOrmModuleOptions = { password: 'root', database: 'nestjsx_crud', synchronize: false, - logging: true, + logging: !isNil(process.env.TYPEORM_LOGGING) + ? !!parseInt(process.env.TYPEORM_LOGGING, 10) + : true, cache: { type: 'redis', options: { diff --git a/lerna.json b/lerna.json index 1c253592..500424bb 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "4.4.2", + "version": "4.4.3", "command": { "publish": { "message": "chore: release" diff --git a/package.json b/package.json index 8e4959e3..0990c656 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test": "yarn s test", "pretest:coveralls": "yarn pretest", "test:coveralls": "yarn s test.coveralls", - "start:typeorm": "npx nodemon -w ./integration/crud-typeorm -e ts node_modules/.bin/ts-node integration/crud-typeorm/main.ts", + "start:typeorm": "npx nodemon -w ./integration/crud-typeorm -e ts node_modules/ts-node/dist/bin.js integration/crud-typeorm/main.ts", "db:sync:typeorm": "cd ./integration/crud-typeorm && npx ts-node -r tsconfig-paths/register ../../node_modules/typeorm/cli.js schema:sync -f=orm", "db:drop:typeorm": "cd ./integration/crud-typeorm && npx ts-node -r tsconfig-paths/register ../../node_modules/typeorm/cli.js schema:drop -f=orm", "db:seeds:typeorm": "cd ./integration/crud-typeorm && npx ts-node -r tsconfig-paths/register ../../node_modules/typeorm/cli.js migration:run -f=orm", diff --git a/packages/crud-request/package.json b/packages/crud-request/package.json index d406ea8b..37d4ad67 100644 --- a/packages/crud-request/package.json +++ b/packages/crud-request/package.json @@ -1,7 +1,7 @@ { "name": "@nestjsx/crud-request", "description": "NestJs CRUD for RESTful APIs - request query builder", - "version": "4.4.2", + "version": "4.4.3", "license": "MIT", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -47,7 +47,7 @@ "build": "npx tsc -b" }, "dependencies": { - "@nestjsx/util": "^4.4.2", + "@nestjsx/util": "^4.4.3", "qs": "^6.8.0" } } diff --git a/packages/crud-typeorm/package.json b/packages/crud-typeorm/package.json index 5d3e15ff..84194f90 100644 --- a/packages/crud-typeorm/package.json +++ b/packages/crud-typeorm/package.json @@ -1,7 +1,7 @@ { "name": "@nestjsx/crud-typeorm", "description": "NestJs CRUD for RESTful APIs - TypeORM", - "version": "4.4.2", + "version": "4.4.3", "license": "MIT", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/packages/crud-typeorm/src/typeorm-crud.service.ts b/packages/crud-typeorm/src/typeorm-crud.service.ts index ee5ecc26..15895f42 100644 --- a/packages/crud-typeorm/src/typeorm-crud.service.ts +++ b/packages/crud-typeorm/src/typeorm-crud.service.ts @@ -45,6 +45,12 @@ export class TypeOrmCrudService extends CrudService { protected entityPrimaryColumns: string[]; protected entityColumnsHash: ObjectLiteral = {}; protected entityRelationsHash: ObjectLiteral = {}; + protected sqlInjectionRegEx: RegExp[] = [ + /(%27)|(\')|(--)|(%23)|(#)/gi, + /((%3D)|(=))[^\n]*((%27)|(\')|(--)|(%3B)|(;))/gi, + /w*((%27)|(\'))((%6F)|o|(%4F))((%72)|r|(%52))/gi, + /((%27)|(\'))union/gi, + ]; constructor(protected repo: Repository) { super(); @@ -791,7 +797,9 @@ export class TypeOrmCrudService extends CrudService { const params: ObjectLiteral = {}; for (let i = 0; i < sort.length; i++) { - params[this.getFieldWithAlias(sort[i].field)] = sort[i].order; + const field = this.getFieldWithAlias(sort[i].field); + const checkedFiled = this.checkSqlInjection(field); + params[checkedFiled] = sort[i].order; } return params; @@ -895,22 +903,22 @@ export class TypeOrmCrudService extends CrudService { break; case '$startsL': - str = `${field} ${likeOperator} :${param}`; + str = `LOWER(${field}) ${likeOperator} :${param}`; params = { [param]: `${cond.value}%` }; break; case '$endsL': - str = `${field} ${likeOperator} :${param}`; + str = `LOWER(${field}) ${likeOperator} :${param}`; params = { [param]: `%${cond.value}` }; break; case '$contL': - str = `${field} ${likeOperator} :${param}`; + str = `LOWER(${field}) ${likeOperator} :${param}`; params = { [param]: `%${cond.value}%` }; break; case '$exclL': - str = `${field} NOT ${likeOperator} :${param}`; + str = `LOWER(${field}) NOT ${likeOperator} :${param}`; params = { [param]: `%${cond.value}%` }; break; @@ -947,4 +955,18 @@ export class TypeOrmCrudService extends CrudService { this.throwBadRequestException(`Invalid column '${cond.field}' value`); } } + + private checkSqlInjection(field: string): string { + /* istanbul ignore else */ + if (this.sqlInjectionRegEx.length) { + for (let i = 0; i < this.sqlInjectionRegEx.length; i++) { + /* istanbul ignore else */ + if (this.sqlInjectionRegEx[0].test(field)) { + this.throwBadRequestException(`SQL injection detected: "${field}"`); + } + } + } + + return field; + } } diff --git a/packages/crud-typeorm/test/b.query-params.spec.ts b/packages/crud-typeorm/test/b.query-params.spec.ts index f484f6d4..ef1c39f5 100644 --- a/packages/crud-typeorm/test/b.query-params.spec.ts +++ b/packages/crud-typeorm/test/b.query-params.spec.ts @@ -540,6 +540,23 @@ describe('#crud-typeorm', () => { res.body[0].company.projects[0].id, ); }); + + it('should throw 400 if SQL injection has been detected', (done) => { + const query = qb + .sortBy({ + field: ' ASC; SELECT CAST( version() AS INTEGER); --', + order: 'DESC', + }) + .query(); + + return request(server) + .get('/companies') + .query(query) + .end((_, res) => { + expect(res.status).toBe(400); + done(); + }); + }); }); describe('#search', () => { diff --git a/packages/crud/package.json b/packages/crud/package.json index 2ebf2397..da01e42e 100644 --- a/packages/crud/package.json +++ b/packages/crud/package.json @@ -1,7 +1,7 @@ { "name": "@nestjsx/crud", "description": "NestJs CRUD for RESTful APIs", - "version": "4.4.2", + "version": "4.4.3", "license": "MIT", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -39,8 +39,8 @@ "build": "npx tsc -b" }, "dependencies": { - "@nestjsx/crud-request": "^4.4.2", - "@nestjsx/util": "^4.4.2", + "@nestjsx/crud-request": "^4.4.3", + "@nestjsx/util": "^4.4.3", "deepmerge": "^3.2.0" }, "peerDependencies": { diff --git a/packages/crud/src/services/crud-service.abstract.ts b/packages/crud/src/services/crud-service.abstract.ts index 26a3584b..4e727841 100644 --- a/packages/crud/src/services/crud-service.abstract.ts +++ b/packages/crud/src/services/crud-service.abstract.ts @@ -52,12 +52,8 @@ export abstract class CrudService { data, count: data.length, total, - page: Math.floor(offset / limit) + 1, - pageCount: - limit && total - ? Math.ceil(total / limit) - : /* istanbul ignore next line */ - undefined, + page: limit ? Math.floor(offset / limit) + 1 : 1, + pageCount: limit && total ? Math.ceil(total / limit) : 1, }; } diff --git a/packages/crud/src/types/query-filter-option.type.ts b/packages/crud/src/types/query-filter-option.type.ts index 9c0f2e20..3a8504cb 100644 --- a/packages/crud/src/types/query-filter-option.type.ts +++ b/packages/crud/src/types/query-filter-option.type.ts @@ -1,7 +1,7 @@ import { QueryFilter, SCondition, -} from '@nestjsx/crud-request/lib/types/request-query.types'; +} from '@nestjsx/crud-request/src/types/request-query.types'; export type QueryFilterFunction = ( search?: SCondition, diff --git a/packages/crud/test/__fixture__/services/test.service.ts b/packages/crud/test/__fixture__/services/test.service.ts index 80dc8468..01a22218 100644 --- a/packages/crud/test/__fixture__/services/test.service.ts +++ b/packages/crud/test/__fixture__/services/test.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { ParsedRequestParams } from '@nestjsx/crud-request'; -import { CrudRequestOptions } from '../../../lib/interfaces'; +import { CrudRequestOptions } from '../../../src/interfaces'; import { CreateManyDto, CrudRequest } from '../../../src/interfaces'; import { CrudService } from '../../../src/services'; diff --git a/packages/crud/test/crud-service.abstract.spec.ts b/packages/crud/test/crud-service.abstract.spec.ts index 6eccf795..642cc83d 100644 --- a/packages/crud/test/crud-service.abstract.spec.ts +++ b/packages/crud/test/crud-service.abstract.spec.ts @@ -37,6 +37,19 @@ describe('#crud', () => { }; expect(service.createPageInfo([], 100, 10, 10)).toMatchObject(expected); }); + + it('should return an object when limit and offset undefined', () => { + const expected = { + count: 0, + data: [], + page: 1, + pageCount: 1, + total: 100, + }; + expect(service.createPageInfo([], 100, undefined, undefined)).toMatchObject( + expected, + ); + }); }); }); }); diff --git a/packages/util/package.json b/packages/util/package.json index 274f093f..b259e6b6 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,7 +1,7 @@ { "name": "@nestjsx/util", "description": "NestJs CRUD for RESTful APIs - util", - "version": "4.4.2", + "version": "4.4.3", "license": "MIT", "main": "lib/index.js", "typings": "lib/index.d.ts",