json-rest-api 1.0.16 → 1.0.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-rest-api",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -24,6 +24,13 @@ export const AccessPlugin = {
24
24
 
25
25
  const ownershipField = () => config.ownership.field
26
26
 
27
+ // Minimal helper: treat ownerField false/null as ownership disabled (alias)
28
+ const isOwnershipDisabled = (scopeOptions = {}) => (
29
+ scopeOptions?.ownership === false ||
30
+ scopeOptions?.ownerField === false ||
31
+ scopeOptions?.ownerField === null
32
+ )
33
+
27
34
  const evaluateOwnership = ({ record, field, schemaInfo, userId }) => {
28
35
  if (!record) return 'unknown'
29
36
 
@@ -161,7 +168,7 @@ export const AccessPlugin = {
161
168
  if (!config.ownership.enabled) return
162
169
  const { fields, scopeName, scopeOptions = {} } = context
163
170
  if (config.ownership.excludeResources.includes(scopeName)) return
164
- if (scopeOptions.ownership === false) return
171
+ if (isOwnershipDisabled(scopeOptions)) return
165
172
 
166
173
  const field = ownershipField()
167
174
  const relationshipName = fields[field]?.as || fields[field]?.belongsTo || field
@@ -196,7 +203,7 @@ export const AccessPlugin = {
196
203
  return
197
204
  }
198
205
 
199
- if (scopeOptions?.ownership === false) return
206
+ if (isOwnershipDisabled(scopeOptions)) return
200
207
 
201
208
  const field = ownershipField()
202
209
  const scope = scopes[scopeName]
@@ -257,7 +264,7 @@ export const AccessPlugin = {
257
264
  }
258
265
 
259
266
  if (context.auth.roles?.includes?.('admin')) return
260
- if (scopeOptions?.ownership === false) return
267
+ if (isOwnershipDisabled(scopeOptions)) return
261
268
 
262
269
  const field = ownershipField()
263
270
  const scope = scopes[scopeName]
@@ -342,7 +349,7 @@ export const AccessPlugin = {
342
349
  const auth = context.originalContext?.auth || context.auth
343
350
  if (!auth?.userId) return
344
351
  if (auth.roles?.includes?.('admin')) return
345
- if (scopeOptions?.ownership === false) return
352
+ if (isOwnershipDisabled(scopeOptions)) return
346
353
 
347
354
  const field = ownershipField()
348
355
  const schemaInfo = scope?.vars?.schemaInfo || {}
@@ -1872,3 +1872,77 @@ export async function createSearchSchemaMergeApi (knex, pluginOptions = {}) {
1872
1872
 
1873
1873
  return api
1874
1874
  }
1875
+
1876
+
1877
+ /**
1878
+ * Creates an API with resources that OPT OUT of ownership
1879
+ * - public_items_ownerfield: uses ownerField: false
1880
+ * - public_items_ownership: uses ownership: false
1881
+ * Both tables intentionally DO NOT have a user_id column.
1882
+ */
1883
+ export async function createOwnershipOptOutApi (knex, pluginOptions = {}) {
1884
+ const apiName = pluginOptions.apiName || 'ownership-optout-api'
1885
+ const tablePrefix = pluginOptions.tablePrefix || 'ownopt'
1886
+ if (storageMode.isAnyApi()) {
1887
+ storageMode.clearRegistry()
1888
+ }
1889
+
1890
+ const api = new Api({
1891
+ name: apiName,
1892
+ log: { level: process.env.LOG_LEVEL || 'info' }
1893
+ })
1894
+
1895
+ const previousTenant = storageMode.currentTenant
1896
+ const tenantId = storageMode.isAnyApi()
1897
+ ? (pluginOptions.tenantId || `${tablePrefix}_tenant`)
1898
+ : storageMode.defaultTenant
1899
+ if (storageMode.isAnyApi()) {
1900
+ storageMode.setCurrentTenant(tenantId)
1901
+ }
1902
+
1903
+ await api.use(RestApiPlugin, {
1904
+ simplifiedApi: false,
1905
+ simplifiedTransport: false,
1906
+ returnRecordApi: { post: true, put: false, patch: false },
1907
+ returnRecordTransport: { post: 'full', put: 'no', patch: 'minimal' },
1908
+ sortableFields: ['id', 'name']
1909
+ })
1910
+ await useStoragePlugin(api, knex, { tenantId })
1911
+ await resetAnyApiTables(knex)
1912
+
1913
+ try {
1914
+ // Access plugin (defaults: ownership enabled, filterByOwner true)
1915
+ await api.use(AccessPlugin, pluginOptions.access || {})
1916
+
1917
+ // Resource A: disable via ownerField:false
1918
+ await api.addResource('public_items_ownerfield', {
1919
+ schema: {
1920
+ id: { type: 'id' },
1921
+ name: { type: 'string', required: true }
1922
+ },
1923
+ // Important: no user_id column in schema; ownerField disables ownership logic
1924
+ ownerField: false,
1925
+ tableName: `${tablePrefix}_public_items_of`
1926
+ })
1927
+ await api.resources.public_items_ownerfield.createKnexTable()
1928
+ mapTable(`${tablePrefix}_public_items_of`, 'public_items_ownerfield')
1929
+
1930
+ // Resource B: disable via ownership:false
1931
+ await api.addResource('public_items_ownership', {
1932
+ schema: {
1933
+ id: { type: 'id' },
1934
+ name: { type: 'string', required: true }
1935
+ },
1936
+ ownership: false,
1937
+ tableName: `${tablePrefix}_public_items_own`
1938
+ })
1939
+ await api.resources.public_items_ownership.createKnexTable()
1940
+ mapTable(`${tablePrefix}_public_items_own`, 'public_items_ownership')
1941
+
1942
+ return api
1943
+ } finally {
1944
+ if (storageMode.isAnyApi()) {
1945
+ storageMode.setCurrentTenant(previousTenant)
1946
+ }
1947
+ }
1948
+ }
@@ -0,0 +1,76 @@
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import {
5
+ validateJsonApiStructure,
6
+ cleanTables,
7
+ createJsonApiDocument,
8
+ assertResourceAttributes
9
+ } from './helpers/test-utils.js'
10
+ import { createOwnershipOptOutApi } from './fixtures/api-configs.js'
11
+
12
+ const knex = knexLib({
13
+ client: 'better-sqlite3',
14
+ connection: { filename: ':memory:' },
15
+ useNullAsDefault: true
16
+ })
17
+
18
+ let api
19
+
20
+ async function createPublicItem (resourceName, name, authUserId) {
21
+ const doc = createJsonApiDocument(resourceName, { name })
22
+ const res = await api.resources[resourceName].post({
23
+ inputRecord: doc,
24
+ simplified: false
25
+ }, { auth: { userId: authUserId } })
26
+
27
+ validateJsonApiStructure(res)
28
+ assertResourceAttributes(res.data, { name })
29
+ return res.data
30
+ }
31
+
32
+ describe('Ownership opt-out (ownerField:false / ownership:false)', () => {
33
+ before(async () => {
34
+ api = await createOwnershipOptOutApi(knex)
35
+ })
36
+
37
+ after(async () => {
38
+ await knex.destroy()
39
+ })
40
+
41
+ beforeEach(async () => {
42
+ await cleanTables(knex, [
43
+ 'ownopt_public_items_of',
44
+ 'ownopt_public_items_own'
45
+ ])
46
+ })
47
+
48
+ it('ownerField:false - allows create+query without user_id column present', async () => {
49
+ await createPublicItem('public_items_ownerfield', 'Alpha', 1)
50
+ await createPublicItem('public_items_ownerfield', 'Beta', 2)
51
+
52
+ // Query with auth
53
+ const resultAuth = await api.resources.public_items_ownerfield.query({ simplified: false }, { auth: { userId: 1 } })
54
+ validateJsonApiStructure(resultAuth, true)
55
+ assert.equal(resultAuth.data.length, 2)
56
+
57
+ // Query without auth (ownership disabled -> no filtering)
58
+ const resultNoAuth = await api.resources.public_items_ownerfield.query({ simplified: false })
59
+ validateJsonApiStructure(resultNoAuth, true)
60
+ assert.equal(resultNoAuth.data.length, 2)
61
+ })
62
+
63
+ it('ownership:false - allows create+query without user_id column present', async () => {
64
+ await createPublicItem('public_items_ownership', 'Gamma', 1)
65
+ await createPublicItem('public_items_ownership', 'Delta', 2)
66
+
67
+ const resultAuth = await api.resources.public_items_ownership.query({ simplified: false }, { auth: { userId: 99 } })
68
+ validateJsonApiStructure(resultAuth, true)
69
+ assert.equal(resultAuth.data.length, 2)
70
+
71
+ const resultNoAuth = await api.resources.public_items_ownership.query({ simplified: false })
72
+ validateJsonApiStructure(resultNoAuth, true)
73
+ assert.equal(resultNoAuth.data.length, 2)
74
+ })
75
+ })
76
+