next-data-kit 7.2.0 → 7.3.0

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.
package/README.md CHANGED
@@ -292,6 +292,7 @@ dataKitServerAction({
292
292
  maxLimit?: number, // Default: 100
293
293
  queryAllowed?: string[], // Whitelist for query fields
294
294
  filterAllowed?: string[], // Auto-derived from filterCustom
295
+ sortAllowed?: string[], // Whitelist for sortable fields
295
296
  });
296
297
  ```
297
298
 
@@ -320,12 +321,13 @@ dataKitServerAction({
320
321
  maxLimit?: number,
321
322
  queryAllowed?: string[],
322
323
  filterAllowed?: string[],
324
+ sortAllowed?: string[],
323
325
  });
324
326
  ```
325
327
 
326
328
  ### Security & Filtering
327
329
 
328
- **Two ways to query data:**
330
+ **Three security whitelists:**
329
331
 
330
332
  1. **`filterCustom`** - User-facing filters (search, dropdowns, etc.)
331
333
  - Client `filters` prop → validated against `filterCustom` keys
@@ -335,6 +337,10 @@ dataKitServerAction({
335
337
  - Explicit whitelist required
336
338
  - Use for: `{ active: true }`, user-specific queries
337
339
 
340
+ 3. **`sortAllowed`** - Sortable fields whitelist
341
+ - Prevents sorting on arbitrary/sensitive fields
342
+ - Recommended for production security
343
+
338
344
  ```typescript
339
345
  dataKitServerAction({
340
346
  model: UserModel,
@@ -345,6 +351,7 @@ dataKitServerAction({
345
351
  role: value => ({ role: value }),
346
352
  },
347
353
  queryAllowed: ['organizationId', 'active'],
354
+ sortAllowed: ['name', 'email', 'createdAt'], // Only allow sorting these fields
348
355
  });
349
356
  ```
350
357
 
@@ -157,6 +157,7 @@ type TBaseOptions<TDoc, R> = {
157
157
  filterAllowed?: string[];
158
158
  maxLimit?: number;
159
159
  queryAllowed?: (keyof TDoc | string)[];
160
+ sortAllowed?: (keyof TDoc | string)[];
160
161
  };
161
162
  /**
162
163
  * Options when using a Mongoose model
@@ -157,6 +157,7 @@ type TBaseOptions<TDoc, R> = {
157
157
  filterAllowed?: string[];
158
158
  maxLimit?: number;
159
159
  queryAllowed?: (keyof TDoc | string)[];
160
+ sortAllowed?: (keyof TDoc | string)[];
160
161
  };
161
162
  /**
162
163
  * Options when using a Mongoose model
package/dist/index.cjs CHANGED
@@ -179,7 +179,7 @@ var mongooseAdapter = (model, options = {}) => {
179
179
  };
180
180
 
181
181
  // src/server/action.ts
182
- async function executeDataKit(input, adapter, item, maxLimit, filterAllowed, queryAllowed) {
182
+ async function executeDataKit(input, adapter, item, maxLimit, filterAllowed, queryAllowed, sortAllowed) {
183
183
  if (input.query) {
184
184
  const safeQuery = {};
185
185
  Object.keys(input.query).forEach((key) => {
@@ -212,6 +212,13 @@ async function executeDataKit(input, adapter, item, maxLimit, filterAllowed, que
212
212
  });
213
213
  input.filter = safeFilter;
214
214
  }
215
+ if (input.sorts && sortAllowed) {
216
+ input.sorts.forEach((sort) => {
217
+ if (!sortAllowed.includes(sort.path)) {
218
+ throw new Error(`[Security] Sort field '${sort.path}' is not allowed.`);
219
+ }
220
+ });
221
+ }
215
222
  switch (input.action ?? "FETCH") {
216
223
  case "FETCH": {
217
224
  if (!input.limit || !input.page) {
@@ -239,7 +246,7 @@ async function executeDataKit(input, adapter, item, maxLimit, filterAllowed, que
239
246
  }
240
247
  }
241
248
  async function dataKitServerAction(props) {
242
- const { input, item, maxLimit = 100, queryAllowed, filterAllowed: explicitFilterAllowed } = props;
249
+ const { input, item, maxLimit = 100, queryAllowed, filterAllowed: explicitFilterAllowed, sortAllowed } = props;
243
250
  const filterCustom = "filterCustom" in props ? props.filterCustom : void 0;
244
251
  const filterAllowed = explicitFilterAllowed ?? (filterCustom ? Object.keys(filterCustom) : void 0);
245
252
  let finalAdapter;
@@ -255,7 +262,7 @@ async function dataKitServerAction(props) {
255
262
  } else {
256
263
  throw new Error("Either model or adapter must be provided");
257
264
  }
258
- return executeDataKit(input, finalAdapter, item, maxLimit, filterAllowed, queryAllowed);
265
+ return executeDataKit(input, finalAdapter, item, maxLimit, filterAllowed, queryAllowed, sortAllowed);
259
266
  }
260
267
 
261
268
  // src/server/adapters/memory.ts