json-rest-api 1.0.14 → 1.0.16
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/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +99 -99
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +11 -15
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
4
|
import {
|
|
5
5
|
validateJsonApiStructure,
|
|
6
6
|
cleanTables,
|
|
7
7
|
createJsonApiDocument,
|
|
8
8
|
assertResourceAttributes,
|
|
9
9
|
assertResourceRelationship
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
import { createAccessControlApi } from './fixtures/api-configs.js'
|
|
10
|
+
} from './helpers/test-utils.js'
|
|
11
|
+
import { createAccessControlApi } from './fixtures/api-configs.js'
|
|
12
12
|
|
|
13
13
|
const knex = knexLib({
|
|
14
14
|
client: 'better-sqlite3',
|
|
@@ -16,191 +16,191 @@ const knex = knexLib({
|
|
|
16
16
|
filename: ':memory:'
|
|
17
17
|
},
|
|
18
18
|
useNullAsDefault: true
|
|
19
|
-
})
|
|
19
|
+
})
|
|
20
20
|
|
|
21
|
-
let api
|
|
21
|
+
let api
|
|
22
22
|
|
|
23
|
-
async function createUser(email, displayName = 'Test User') {
|
|
23
|
+
async function createUser (email, displayName = 'Test User') {
|
|
24
24
|
const userDoc = createJsonApiDocument('users', {
|
|
25
25
|
email,
|
|
26
26
|
display_name: displayName
|
|
27
|
-
})
|
|
27
|
+
})
|
|
28
28
|
|
|
29
29
|
const result = await api.resources.users.post({
|
|
30
30
|
inputRecord: userDoc,
|
|
31
31
|
simplified: false
|
|
32
|
-
}, { auth: { system: true } })
|
|
32
|
+
}, { auth: { system: true } })
|
|
33
33
|
|
|
34
|
-
validateJsonApiStructure(result)
|
|
34
|
+
validateJsonApiStructure(result)
|
|
35
35
|
assertResourceAttributes(result.data, {
|
|
36
36
|
email,
|
|
37
37
|
display_name: displayName
|
|
38
|
-
})
|
|
38
|
+
})
|
|
39
39
|
|
|
40
|
-
return Number(result.data.id)
|
|
40
|
+
return Number(result.data.id)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function createProjectForUser(userId, name, status = null) {
|
|
43
|
+
async function createProjectForUser (userId, name, status = null) {
|
|
44
44
|
const projectDoc = createJsonApiDocument('projects', {
|
|
45
45
|
name,
|
|
46
46
|
status
|
|
47
|
-
})
|
|
47
|
+
})
|
|
48
48
|
|
|
49
49
|
const result = await api.resources.projects.post({
|
|
50
50
|
inputRecord: projectDoc,
|
|
51
51
|
simplified: false
|
|
52
|
-
}, { auth: { userId } })
|
|
52
|
+
}, { auth: { userId } })
|
|
53
53
|
|
|
54
|
-
validateJsonApiStructure(result)
|
|
55
|
-
return result.data
|
|
54
|
+
validateJsonApiStructure(result)
|
|
55
|
+
return result.data
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
describe('Access plugin', () => {
|
|
59
59
|
before(async () => {
|
|
60
|
-
api = await createAccessControlApi(knex)
|
|
61
|
-
})
|
|
60
|
+
api = await createAccessControlApi(knex)
|
|
61
|
+
})
|
|
62
62
|
|
|
63
63
|
after(async () => {
|
|
64
|
-
await knex.destroy()
|
|
65
|
-
})
|
|
64
|
+
await knex.destroy()
|
|
65
|
+
})
|
|
66
66
|
|
|
67
67
|
beforeEach(async () => {
|
|
68
68
|
await cleanTables(knex, [
|
|
69
69
|
'access_notes',
|
|
70
70
|
'access_projects',
|
|
71
71
|
'access_users'
|
|
72
|
-
])
|
|
73
|
-
})
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
74
|
|
|
75
75
|
it('rejects unauthenticated requests for protected operations', async () => {
|
|
76
76
|
const projectDoc = createJsonApiDocument('projects', {
|
|
77
77
|
name: 'Unauthenticated project'
|
|
78
|
-
})
|
|
78
|
+
})
|
|
79
79
|
|
|
80
80
|
await assert.rejects(async () => {
|
|
81
81
|
await api.resources.projects.post({
|
|
82
82
|
inputRecord: projectDoc,
|
|
83
83
|
simplified: false
|
|
84
|
-
})
|
|
84
|
+
})
|
|
85
85
|
}, (error) => {
|
|
86
|
-
assert.equal(error.statusCode, 403)
|
|
87
|
-
assert.match(error.message, /Access denied/)
|
|
88
|
-
return true
|
|
89
|
-
})
|
|
90
|
-
})
|
|
86
|
+
assert.equal(error.statusCode, 403)
|
|
87
|
+
assert.match(error.message, /Access denied/)
|
|
88
|
+
return true
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
91
|
|
|
92
92
|
it('auto assigns ownership when creating a project', async () => {
|
|
93
|
-
const ownerId = await createUser('owner@example.com', 'Owner One')
|
|
93
|
+
const ownerId = await createUser('owner@example.com', 'Owner One')
|
|
94
94
|
|
|
95
95
|
const projectDoc = createJsonApiDocument('projects', {
|
|
96
96
|
name: 'Owned Project',
|
|
97
97
|
status: 'active'
|
|
98
|
-
})
|
|
98
|
+
})
|
|
99
99
|
|
|
100
100
|
const createResult = await api.resources.projects.post({
|
|
101
101
|
inputRecord: projectDoc,
|
|
102
102
|
simplified: false
|
|
103
|
-
}, { auth: { userId: ownerId } })
|
|
103
|
+
}, { auth: { userId: ownerId } })
|
|
104
104
|
|
|
105
|
-
validateJsonApiStructure(createResult)
|
|
105
|
+
validateJsonApiStructure(createResult)
|
|
106
106
|
assertResourceAttributes(createResult.data, {
|
|
107
107
|
name: 'Owned Project',
|
|
108
108
|
status: 'active'
|
|
109
|
-
})
|
|
109
|
+
})
|
|
110
110
|
assertResourceRelationship(createResult.data, 'user', {
|
|
111
111
|
type: 'users',
|
|
112
112
|
id: String(ownerId)
|
|
113
|
-
})
|
|
113
|
+
})
|
|
114
114
|
|
|
115
115
|
const getResult = await api.resources.projects.get({
|
|
116
116
|
id: createResult.data.id,
|
|
117
117
|
simplified: false
|
|
118
|
-
}, { auth: { userId: ownerId } })
|
|
118
|
+
}, { auth: { userId: ownerId } })
|
|
119
119
|
|
|
120
|
-
validateJsonApiStructure(getResult)
|
|
120
|
+
validateJsonApiStructure(getResult)
|
|
121
121
|
assertResourceRelationship(getResult.data, 'user', {
|
|
122
122
|
type: 'users',
|
|
123
123
|
id: String(ownerId)
|
|
124
|
-
})
|
|
125
|
-
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
126
|
|
|
127
127
|
it('filters query results by ownership for non-admin users', async () => {
|
|
128
|
-
const aliceId = await createUser('alice@example.com', 'Alice')
|
|
129
|
-
const bobId = await createUser('bob@example.com', 'Bob')
|
|
128
|
+
const aliceId = await createUser('alice@example.com', 'Alice')
|
|
129
|
+
const bobId = await createUser('bob@example.com', 'Bob')
|
|
130
130
|
|
|
131
|
-
const aliceProject = await createProjectForUser(aliceId, 'Alice Project', 'active')
|
|
132
|
-
await createProjectForUser(bobId, 'Bob Project', 'planning')
|
|
131
|
+
const aliceProject = await createProjectForUser(aliceId, 'Alice Project', 'active')
|
|
132
|
+
await createProjectForUser(bobId, 'Bob Project', 'planning')
|
|
133
133
|
|
|
134
134
|
const queryResult = await api.resources.projects.query({
|
|
135
135
|
simplified: false
|
|
136
|
-
}, { auth: { userId: aliceId } })
|
|
136
|
+
}, { auth: { userId: aliceId } })
|
|
137
137
|
|
|
138
|
-
validateJsonApiStructure(queryResult, true)
|
|
139
|
-
assert.equal(queryResult.data.length, 1)
|
|
140
|
-
assert.equal(queryResult.data[0].id, aliceProject.id)
|
|
138
|
+
validateJsonApiStructure(queryResult, true)
|
|
139
|
+
assert.equal(queryResult.data.length, 1)
|
|
140
|
+
assert.equal(queryResult.data[0].id, aliceProject.id)
|
|
141
141
|
assertResourceRelationship(queryResult.data[0], 'user', {
|
|
142
142
|
type: 'users',
|
|
143
143
|
id: String(aliceId)
|
|
144
|
-
})
|
|
145
|
-
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
146
|
|
|
147
147
|
it('allows admin roles to bypass ownership filtering', async () => {
|
|
148
|
-
const userA = await createUser('admin_a@example.com', 'Admin A')
|
|
149
|
-
const userB = await createUser('admin_b@example.com', 'Admin B')
|
|
148
|
+
const userA = await createUser('admin_a@example.com', 'Admin A')
|
|
149
|
+
const userB = await createUser('admin_b@example.com', 'Admin B')
|
|
150
150
|
|
|
151
|
-
await createProjectForUser(userA, 'Admin Project A')
|
|
152
|
-
await createProjectForUser(userB, 'Admin Project B')
|
|
151
|
+
await createProjectForUser(userA, 'Admin Project A')
|
|
152
|
+
await createProjectForUser(userB, 'Admin Project B')
|
|
153
153
|
|
|
154
154
|
const adminQuery = await api.resources.projects.query({
|
|
155
155
|
simplified: false
|
|
156
|
-
}, { auth: { userId: 999, roles: ['admin'] } })
|
|
156
|
+
}, { auth: { userId: 999, roles: ['admin'] } })
|
|
157
157
|
|
|
158
|
-
validateJsonApiStructure(adminQuery, true)
|
|
159
|
-
assert.equal(adminQuery.data.length, 2)
|
|
158
|
+
validateJsonApiStructure(adminQuery, true)
|
|
159
|
+
assert.equal(adminQuery.data.length, 2)
|
|
160
160
|
|
|
161
|
-
const relatedUserIds = adminQuery.data.map((resource) => resource.relationships.user.data.id).sort()
|
|
162
|
-
assert.deepEqual(relatedUserIds, [String(userA), String(userB)])
|
|
163
|
-
})
|
|
161
|
+
const relatedUserIds = adminQuery.data.map((resource) => resource.relationships.user.data.id).sort()
|
|
162
|
+
assert.deepEqual(relatedUserIds, [String(userA), String(userB)])
|
|
163
|
+
})
|
|
164
164
|
|
|
165
165
|
it('prevents users from accessing records they do not own', async () => {
|
|
166
|
-
const ownerId = await createUser('primary@example.com', 'Primary')
|
|
167
|
-
const otherId = await createUser('secondary@example.com', 'Secondary')
|
|
166
|
+
const ownerId = await createUser('primary@example.com', 'Primary')
|
|
167
|
+
const otherId = await createUser('secondary@example.com', 'Secondary')
|
|
168
168
|
|
|
169
|
-
const project = await createProjectForUser(ownerId, 'Primary Project')
|
|
169
|
+
const project = await createProjectForUser(ownerId, 'Primary Project')
|
|
170
170
|
|
|
171
171
|
await assert.rejects(async () => {
|
|
172
172
|
await api.resources.projects.get({
|
|
173
173
|
id: project.id,
|
|
174
174
|
simplified: false
|
|
175
|
-
}, { auth: { userId: otherId } })
|
|
175
|
+
}, { auth: { userId: otherId } })
|
|
176
176
|
}, (error) => {
|
|
177
|
-
assert.equal(error.statusCode, 403)
|
|
178
|
-
assert.match(error.message, /Access denied/)
|
|
179
|
-
return true
|
|
180
|
-
})
|
|
181
|
-
})
|
|
177
|
+
assert.equal(error.statusCode, 403)
|
|
178
|
+
assert.match(error.message, /Access denied/)
|
|
179
|
+
return true
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
182
|
|
|
183
183
|
it('enforces helper ownership checks', () => {
|
|
184
|
-
const context = { auth: { userId: 123 } }
|
|
185
|
-
const resource = { user_id: 123 }
|
|
184
|
+
const context = { auth: { userId: 123 } }
|
|
185
|
+
const resource = { user_id: 123 }
|
|
186
186
|
|
|
187
|
-
const auth = api.helpers.auth.requireOwnership(context, resource)
|
|
188
|
-
assert.equal(auth.userId, 123)
|
|
187
|
+
const auth = api.helpers.auth.requireOwnership(context, resource)
|
|
188
|
+
assert.equal(auth.userId, 123)
|
|
189
189
|
|
|
190
190
|
assert.throws(() => {
|
|
191
|
-
api.helpers.auth.requireOwnership({ auth: { userId: 123 } }, { user_id: 456 })
|
|
191
|
+
api.helpers.auth.requireOwnership({ auth: { userId: 123 } }, { user_id: 456 })
|
|
192
192
|
}, (error) => {
|
|
193
|
-
assert.equal(error.statusCode, 403)
|
|
194
|
-
assert.match(error.message, /Access denied/)
|
|
195
|
-
return true
|
|
196
|
-
})
|
|
193
|
+
assert.equal(error.statusCode, 403)
|
|
194
|
+
assert.match(error.message, /Access denied/)
|
|
195
|
+
return true
|
|
196
|
+
})
|
|
197
197
|
|
|
198
198
|
assert.throws(() => {
|
|
199
|
-
api.helpers.auth.requireAuth({})
|
|
199
|
+
api.helpers.auth.requireAuth({})
|
|
200
200
|
}, (error) => {
|
|
201
|
-
assert.equal(error.statusCode, 401)
|
|
202
|
-
assert.match(error.message, /Authentication required/)
|
|
203
|
-
return true
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
})
|
|
201
|
+
assert.equal(error.statusCode, 401)
|
|
202
|
+
assert.match(error.message, /Authentication required/)
|
|
203
|
+
return true
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
})
|