@stonyx/orm 0.3.2-alpha.92 → 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.
- package/README.md +65 -3
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -318,16 +318,78 @@ await setupRestServer('/', './access');
|
|
|
318
318
|
Access classes define models and provide custom filtering/authorization logic:
|
|
319
319
|
|
|
320
320
|
```js
|
|
321
|
-
export default class
|
|
322
|
-
models = ['owner'
|
|
321
|
+
export default class OwnerAccess {
|
|
322
|
+
models = ['owner'];
|
|
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.
|
|
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
|
+
|
|
363
|
+
### Upgrading: behaviour changes
|
|
364
|
+
|
|
365
|
+
**Advertised `links.self` / `links.related` now carry the REST mount route.**
|
|
366
|
+
Consumer-visible for any deployment where `orm.restServer.route` is not the
|
|
367
|
+
default `'/'`.
|
|
368
|
+
|
|
369
|
+
Measured on this repo's mounted-route harness at `ORM_REST_ROUTE='/api'`, resource
|
|
370
|
+
`links.self` in the response to `GET /api/animals/1`:
|
|
371
|
+
|
|
372
|
+
| | published `links.self` | fetching that URL |
|
|
373
|
+
|---|---|---|
|
|
374
|
+
| before | `http://host/animals/1` | **404** |
|
|
375
|
+
| after | `http://host/api/animals/1` | **200** |
|
|
376
|
+
|
|
377
|
+
The ORM previously built links from the request origin alone, so at any non-default
|
|
378
|
+
mount every URL it advertised pointed at a route that did not exist
|
|
379
|
+
(abofs/stonyx-orm#254). Links are now built from the path the routes are actually
|
|
380
|
+
mounted at, and are followable verbatim.
|
|
381
|
+
|
|
382
|
+
**⚠️ Breaking if you carry a prepending workaround.** The usual workaround for #254
|
|
383
|
+
was for the client to prepend the mount to every link the ORM published. That now
|
|
384
|
+
double-prefixes: prepending `/api` to the new `http://host/api/animals/1` yields
|
|
385
|
+
`http://host/api/api/animals/1`, measured **404**. Remove the prepending. There is no
|
|
386
|
+
configuration flag that restores the old link shape.
|
|
387
|
+
|
|
388
|
+
**Unaffected.** Deployments on the default `ORM_REST_ROUTE='/'` see byte-identical
|
|
389
|
+
output — the prefix is empty, and this is pinned by a byte-identity test against a
|
|
390
|
+
golden fixture captured *before* the fix. Response structure, field names and the
|
|
391
|
+
public API are unchanged; only the URL value inside `links` changes.
|
|
392
|
+
|
|
331
393
|
### Include Parameter (Sideloading Relationships)
|
|
332
394
|
|
|
333
395
|
The ORM supports JSON API-compliant relationship sideloading via the `include` query parameter. This reduces the need for multiple API requests by embedding related records in a single response.
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"stonyx-async",
|
|
5
5
|
"stonyx-module"
|
|
6
6
|
],
|
|
7
|
-
"version": "0.3.2-alpha.
|
|
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.
|
|
65
|
-
"@stonyx/events": "0.1.1-beta.
|
|
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.
|
|
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
|
}
|