feathersjs-ecosystem / feathers-sequelize

A Feathers service adapter for the Sequelize ORM. Supporting MySQL, MariaDB, Postgres, SQLite, and SQL Server
MIT License
208 stars 75 forks source link

Bug: patch:multi only patches 10 items with default pagination #363

Closed fratzinger closed 1 year ago

fratzinger commented 3 years ago

Multi-patch without $limit in params.query results in an incomplete operation, because filterQuery adds the default $limit: -1 to the request. Not sure if this problem exists in multi-remove as well.

I'll add this to feathers-sequelize and do a hacky PR for now. But the problem goes deeper, I think. I have something in mind for @feathersjs/adapter-tests and @feathersjs/adapter-commons:

Question 1: Is this something we should add to @feathersjs/adapter-tests? I saw there are tests for multiple items (count = 2) but not for many items, at least > 10.

I find this behavior of filterQuery from @feathersjs/adapter-commons quite confusing, although I understand, why it is like it is. In another project I ended up deleting the default $limit: 10 from filterQuery(params) if params.query.$limit is not the same. Not the nicest solution, but the quickest. Question 2: Is this maybe worth a property for second options argument of filterQuery? Something like: filterQuery(params, { addDefaultLimit: false }?

I will look into this in feathers-sequelize for now. @daffl if you answered one of the two bold questions with "yes", I would be happy to help. Just let me know!

Steps to reproduce

The error comes from this line: https://github.com/feathersjs-ecosystem/feathers-sequelize/blob/master/lib/index.js#L235 filterQuery adds $limit: 10 for default pagination, although no $limit is defined in params. idList from https://github.com/feathersjs-ecosystem/feathers-sequelize/blob/master/lib/index.js#L232 has the full list of all items.

Expected behavior

this.service('posts').patch(null, { title: 'test' }, { query: { id: { $in: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } } }) should patch 12 items and return 12 items

Actual behavior

patches 10 items and returns 10 items with default pagination:

"paginate": {
  "default": 10,
  "max": 100
},

System configuration

Module versions (especially the part that's not working): "feathers-sequelize": "^6.2.0"

NodeJS version: 12

DaddyWarbucks commented 3 years ago

Does setting paginate: false work?

this.service('posts').patch(
  null,
  { title: 'test' },
  {
    query: { id: { $in: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] } },
    paginate: false
  }
);
fratzinger commented 3 years ago

Thanks for your quick response! Yep, it does. I've added this in a before-hook for multi-patches. Do you think this should stay as it is? for find the pagination is totally necessary, but for multi-patches I would say it's counter intuitive. If you think we should leave this as is, maybe we should add a note to the readme?:

patch: [
  context => {
    if (context.id) { return context; }
      context.params.paginate = false;
      return context;
    }
],
DaddyWarbucks commented 3 years ago

If setting paginate:false works as expected, then I do think it should stay as is. But it should also be mentioned in the README. Good catch! It's primarily a security concern IMO. Otherwise someone can overload the server/database by patching 1 million+ rows.

For example,

this.service('posts').patch(
  null,
  { title: 'test' },
  {
    query: { created_at: { $gt: new Date('1/1/1900') } },
    paginate: false
  }
);

Be careful with that hook you mentioned in your comment, because it is susceptible to the "attack" above. Anything that allows the client to potentially disable pagination is a security risk.

daffl commented 3 years ago

Hm, I was expecting this to work as intended (patching/removing all items) because the _getOrFind explicitly always sets paginate: false: https://github.com/feathersjs-ecosystem/feathers-sequelize/blob/master/lib/index.js#L114 but as pointed out, the filterQuery is causing the problem here. I wonder if other adapters do this differently and how a test for this would look like.

fratzinger commented 3 years ago

@DaddyWarbucks

It's totally a security issue. Maybe it's something for disablePagination from feathers-hooks-common (add 'patch' to checkContext)?

I understand the concerns. But patching the default amount of items and leave the rest as is leaves you behind with headaches. Maybe a better way would be to throw a BadRequest if no $limit and no paginate: false is set and the idList.length is larger than the default $limit?

I also thought like @daffl because _getOrFind sets paginate: false. The idList (https://github.com/feathersjs-ecosystem/feathers-sequelize/blob/master/lib/index.js#L232) has the full list of ids (for the 1 million+ rows). So if it should stay as is, I think it's worth considering to integrate the filter to the request of idList.

@daffl

A generic test would start with more than 10 items. The problem is, that @feathersjs/adapter-tests takes the servicePath. One option would be to fail, if the service doesn't have a options.paginate property. Another option would be to clone the service and add the options.paginate property to the cloned instance. But I haven't looked into @feathersjs/adapter-tests that much. Since the patch returns all items that were patched, we could compare the length of items.

What do you think @daffl? Should I come up with a PR for @feathersjs/adapter-tests?

DaddyWarbucks commented 1 year ago

Maybe relevant: https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/405

fratzinger commented 1 year ago

This is completed in dove.