@stonyx/orm 0.3.2-alpha.94 → 0.3.2-alpha.95

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.
Files changed (2) hide show
  1. package/README.md +46 -4
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -323,15 +323,22 @@ export default class OwnerAccess {
323
323
 
324
324
  access(request) {
325
325
  // `access` runs after route matching, so `request.params` is populated and
326
- // `id` has already been URL-decoded. Authorize on it.
326
+ // `id` has already been URL-decoded. Authorize on it, never on a URL.
327
327
  const { id } = request.params;
328
328
 
329
+ // `id` is still raw client text. Normalise it the way the record lookup
330
+ // does, or your predicate and the lookup disagree — see "Numeric ids" below.
331
+ // No radix on parseInt: that is deliberate, and it must stay that way.
332
+ const recordId = id === undefined ? undefined : (isNaN(id) ? id : parseInt(id));
333
+
329
334
  // Returning false explicitly denies access to this record
330
- if (id === 'angela') return false;
335
+ if (recordId === 'angela') return false;
331
336
 
332
337
  // No `id` means the collection route. Returning a function plugs it in to
333
- // the response object as a filter
334
- if (id === undefined) return record => record.id !== 'angela';
338
+ // the response object as a filter. NOTE: a function return authorizes the
339
+ // request outright the operations list below is not consulted — so this
340
+ // branch permits POST /owners as well as reads.
341
+ if (recordId === undefined) return record => record.id !== 'angela';
335
342
 
336
343
  // Returning a list of operations allows full access to everything else
337
344
  return ['read', 'create', 'update', 'delete'];
@@ -355,6 +362,41 @@ form that is safe to match on — `request.baseUrl` is the mount text as the
355
362
  client spelled it (`/OWNERS`), not the model. A class may still list several
356
363
  models in `models` when they share one rule.
357
364
 
365
+ **Numeric ids: normalise before you compare.** `request.params.id` is raw text
366
+ from the client. When it looks numeric the ORM coerces it — `isNaN(id) ? id :
367
+ parseInt(id)` — *before* it resolves the record, so `7`, `007`, `7.0`, `7.9`,
368
+ `7e0`, `0x7`, `+7`, `%207` (a leading space), `%09 7` (a tab) and `7%0A` (a
369
+ trailing newline) all address record `7`, while a `===` against the raw text
370
+ matches only the one spelling you wrote down. Every other spelling falls through
371
+ to whatever your method returns next — which, in the shape above, is a full CRUD
372
+ grant. All of them are plain address-bar requests.
373
+
374
+ Two details are load-bearing. `parseInt` is called with **no radix**, so `0x7`
375
+ is `7` and not `0`; writing `parseInt(id, 10)` in your predicate re-opens the
376
+ hex spelling. And the coercion applies only when the id looks numeric, so a
377
+ model with string ids (like `owner` above) is unaffected — which is exactly why
378
+ this is easy to miss. Normalise the same way the lookup does:
379
+
380
+ ```javascript
381
+ export default class AnimalAccess {
382
+ models = ['animal'];
383
+
384
+ access(request) {
385
+ const { id } = request.params;
386
+
387
+ // Agrees with the lookup for every spelling of 7 above. Compare the
388
+ // coerced value, which for a numeric-id model is a number, not a string.
389
+ const recordId = id === undefined ? undefined : (isNaN(id) ? id : parseInt(id));
390
+
391
+ if (recordId === 7) return false;
392
+
393
+ if (recordId === undefined) return record => record.id !== 7;
394
+
395
+ return ['read', 'create', 'update', 'delete'];
396
+ }
397
+ }
398
+ ```
399
+
358
400
  The sample above is executed verbatim against a live server by
359
401
  `test/integration/readme-access/`, which reads it out of this file: the request
360
402
  `DELETE /owners/angela` is asserted to return 403 with the record intact, along
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-alpha.94",
7
+ "version": "0.3.2-alpha.95",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",