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

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 +35 -3
  2. package/package.json +7 -5
package/README.md CHANGED
@@ -318,16 +318,48 @@ await setupRestServer('/', './access');
318
318
  Access classes define models and provide custom filtering/authorization logic:
319
319
 
320
320
  ```js
321
- export default class GlobalAccess {
322
- models = ['owner', 'animal'];
321
+ export default class OwnerAccess {
322
+ models = ['owner'];
323
323
 
324
324
  access(request) {
325
- if (request.url.endsWith('/owner/angela')) return false;
325
+ // `access` runs after route matching, so `request.params` is populated and
326
+ // `id` has already been URL-decoded. Authorize on it.
327
+ const { id } = request.params;
328
+
329
+ // Returning false explicitly denies access to this record
330
+ if (id === 'angela') return false;
331
+
332
+ // 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';
335
+
336
+ // Returning a list of operations allows full access to everything else
326
337
  return ['read', 'create', 'update', 'delete'];
327
338
  }
328
339
  }
329
340
  ```
330
341
 
342
+ **Do not authorize on the request URL.** `request.url` is rewritten relative to
343
+ the mount point, so inside the REST server it is `/angela`, not `/owners/angela`
344
+ — a suffix comparison against it never matches and the request falls through to
345
+ whatever the method returns next. `request.originalUrl` keeps the full path but
346
+ is still the raw text the client sent, so it varies with query strings
347
+ (`/owners?x=1`), trailing slashes (`/owners/angela/`), casing (`/OwNeRs/angela`,
348
+ which Express routes to the same handler) and percent-encoding
349
+ (`/owners/%61ngela`). Each of those is a plain address-bar request, and each one
350
+ slips past a URL predicate. `request.params.id` is identical for all of them.
351
+
352
+ **One access class per model when the rules are model-specific.** `access()`
353
+ receives only the request, and the request does not carry the model name in any
354
+ form that is safe to match on — `request.baseUrl` is the mount text as the
355
+ client spelled it (`/OWNERS`), not the model. A class may still list several
356
+ models in `models` when they share one rule.
357
+
358
+ The sample above is executed verbatim against a live server by
359
+ `test/integration/readme-access/`, which reads it out of this file: the request
360
+ `DELETE /owners/angela` is asserted to return 403 with the record intact, along
361
+ with each of the spellings named above.
362
+
331
363
  ### Upgrading: behaviour changes
332
364
 
333
365
  **Advertised `links.self` / `links.related` now carry the REST mount route.**
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.3.2-alpha.93",
7
+ "version": "0.3.2-alpha.94",
8
8
  "description": "",
9
9
  "main": "dist/index.js",
10
10
  "type": "module",
@@ -61,8 +61,8 @@
61
61
  },
62
62
  "homepage": "https://github.com/abofs/stonyx-orm#readme",
63
63
  "dependencies": {
64
- "@stonyx/cron": "0.2.1-beta.95",
65
- "@stonyx/events": "0.1.1-beta.52",
64
+ "@stonyx/cron": "0.2.1-beta.98",
65
+ "@stonyx/events": "0.1.1-beta.54",
66
66
  "@stonyx/utils": "0.2.3-beta.26",
67
67
  "stonyx": "0.2.3-beta.81"
68
68
  },
@@ -91,7 +91,7 @@
91
91
  }
92
92
  },
93
93
  "devDependencies": {
94
- "@stonyx/rest-server": "0.2.1-beta.98",
94
+ "@stonyx/rest-server": "0.2.1-beta.100",
95
95
  "@types/node": "^25.6.0",
96
96
  "mysql2": "^3.20.0",
97
97
  "pg": "^8.20.0",
@@ -103,8 +103,10 @@
103
103
  "scripts": {
104
104
  "build": "tsc",
105
105
  "build:test": "tsc -p tsconfig.test.json",
106
- "test": "pnpm build && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts' && ORM_TEST_ROUTE=/ pnpm test:mounted && ORM_TEST_ROUTE=/api pnpm test:mounted && ORM_TEST_ROUTE=api pnpm test:mounted && ORM_TEST_ROUTE=/api/v1 pnpm test:mounted && ORM_TEST_ROUTE=/api/ pnpm test:mounted",
106
+ "test": "pnpm build && NODE_ENV=test node --import tsx/esm --import ./test/setup.ts node_modules/qunit/bin/qunit.js 'test/**/*-test.ts' && ORM_TEST_ROUTE=/ pnpm test:mounted && ORM_TEST_ROUTE=/api pnpm test:mounted && ORM_TEST_ROUTE=api pnpm test:mounted && ORM_TEST_ROUTE=/api/v1 pnpm test:mounted && ORM_TEST_ROUTE=/api/ pnpm test:mounted && pnpm test:readme && pnpm test:reference",
107
107
  "test:mounted": "node --import tsx/esm --import ./test/integration/mounted-route/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/mounted-route/links-mounted.ts' 'test/zz-exit-test.ts'",
108
+ "test:readme": "node --import tsx/esm --import ./test/integration/readme-access/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/readme-access/readme-sample.ts' 'test/zz-exit-test.ts'",
109
+ "test:reference": "node --import tsx/esm --import ./test/integration/reference-access/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/reference-access/reference-sample.ts' 'test/zz-exit-test.ts'",
108
110
  "test:dynamodb": "pnpm build && node --import tsx/esm --import ./test/integration/dynamodb/setup.ts node_modules/qunit/bin/qunit.js 'test/integration/dynamodb/**/*-test.ts'"
109
111
  }
110
112
  }