json-rest-api 1.0.21 → 1.0.23
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/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/package.json +4 -4
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +210 -0
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,206 +0,0 @@
|
|
|
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
|
-
assertResourceRelationship
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
import { createAccessControlApi } from './fixtures/api-configs.js'
|
|
12
|
-
|
|
13
|
-
const knex = knexLib({
|
|
14
|
-
client: 'better-sqlite3',
|
|
15
|
-
connection: {
|
|
16
|
-
filename: ':memory:'
|
|
17
|
-
},
|
|
18
|
-
useNullAsDefault: true
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
let api
|
|
22
|
-
|
|
23
|
-
async function createUser (email, displayName = 'Test User') {
|
|
24
|
-
const userDoc = createJsonApiDocument('users', {
|
|
25
|
-
email,
|
|
26
|
-
display_name: displayName
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
const result = await api.resources.users.post({
|
|
30
|
-
inputRecord: userDoc,
|
|
31
|
-
simplified: false
|
|
32
|
-
}, { auth: { system: true } })
|
|
33
|
-
|
|
34
|
-
validateJsonApiStructure(result)
|
|
35
|
-
assertResourceAttributes(result.data, {
|
|
36
|
-
email,
|
|
37
|
-
display_name: displayName
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
return Number(result.data.id)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function createProjectForUser (userId, name, status = null) {
|
|
44
|
-
const projectDoc = createJsonApiDocument('projects', {
|
|
45
|
-
name,
|
|
46
|
-
status
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const result = await api.resources.projects.post({
|
|
50
|
-
inputRecord: projectDoc,
|
|
51
|
-
simplified: false
|
|
52
|
-
}, { auth: { userId } })
|
|
53
|
-
|
|
54
|
-
validateJsonApiStructure(result)
|
|
55
|
-
return result.data
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('Access plugin', () => {
|
|
59
|
-
before(async () => {
|
|
60
|
-
api = await createAccessControlApi(knex)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
after(async () => {
|
|
64
|
-
await knex.destroy()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
beforeEach(async () => {
|
|
68
|
-
await cleanTables(knex, [
|
|
69
|
-
'access_notes',
|
|
70
|
-
'access_projects',
|
|
71
|
-
'access_users'
|
|
72
|
-
])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('rejects unauthenticated requests for protected operations', async () => {
|
|
76
|
-
const projectDoc = createJsonApiDocument('projects', {
|
|
77
|
-
name: 'Unauthenticated project'
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
await assert.rejects(async () => {
|
|
81
|
-
await api.resources.projects.post({
|
|
82
|
-
inputRecord: projectDoc,
|
|
83
|
-
simplified: false
|
|
84
|
-
})
|
|
85
|
-
}, (error) => {
|
|
86
|
-
assert.equal(error.statusCode, 403)
|
|
87
|
-
assert.match(error.message, /Access denied/)
|
|
88
|
-
return true
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('auto assigns ownership when creating a project', async () => {
|
|
93
|
-
const ownerId = await createUser('owner@example.com', 'Owner One')
|
|
94
|
-
|
|
95
|
-
const projectDoc = createJsonApiDocument('projects', {
|
|
96
|
-
name: 'Owned Project',
|
|
97
|
-
status: 'active'
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const createResult = await api.resources.projects.post({
|
|
101
|
-
inputRecord: projectDoc,
|
|
102
|
-
simplified: false
|
|
103
|
-
}, { auth: { userId: ownerId } })
|
|
104
|
-
|
|
105
|
-
validateJsonApiStructure(createResult)
|
|
106
|
-
assertResourceAttributes(createResult.data, {
|
|
107
|
-
name: 'Owned Project',
|
|
108
|
-
status: 'active'
|
|
109
|
-
})
|
|
110
|
-
assertResourceRelationship(createResult.data, 'user', {
|
|
111
|
-
type: 'users',
|
|
112
|
-
id: String(ownerId)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const getResult = await api.resources.projects.get({
|
|
116
|
-
id: createResult.data.id,
|
|
117
|
-
simplified: false
|
|
118
|
-
}, { auth: { userId: ownerId } })
|
|
119
|
-
|
|
120
|
-
validateJsonApiStructure(getResult)
|
|
121
|
-
assertResourceRelationship(getResult.data, 'user', {
|
|
122
|
-
type: 'users',
|
|
123
|
-
id: String(ownerId)
|
|
124
|
-
})
|
|
125
|
-
})
|
|
126
|
-
|
|
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')
|
|
130
|
-
|
|
131
|
-
const aliceProject = await createProjectForUser(aliceId, 'Alice Project', 'active')
|
|
132
|
-
await createProjectForUser(bobId, 'Bob Project', 'planning')
|
|
133
|
-
|
|
134
|
-
const queryResult = await api.resources.projects.query({
|
|
135
|
-
simplified: false
|
|
136
|
-
}, { auth: { userId: aliceId } })
|
|
137
|
-
|
|
138
|
-
validateJsonApiStructure(queryResult, true)
|
|
139
|
-
assert.equal(queryResult.data.length, 1)
|
|
140
|
-
assert.equal(queryResult.data[0].id, aliceProject.id)
|
|
141
|
-
assertResourceRelationship(queryResult.data[0], 'user', {
|
|
142
|
-
type: 'users',
|
|
143
|
-
id: String(aliceId)
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
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')
|
|
150
|
-
|
|
151
|
-
await createProjectForUser(userA, 'Admin Project A')
|
|
152
|
-
await createProjectForUser(userB, 'Admin Project B')
|
|
153
|
-
|
|
154
|
-
const adminQuery = await api.resources.projects.query({
|
|
155
|
-
simplified: false
|
|
156
|
-
}, { auth: { userId: 999, roles: ['admin'] } })
|
|
157
|
-
|
|
158
|
-
validateJsonApiStructure(adminQuery, true)
|
|
159
|
-
assert.equal(adminQuery.data.length, 2)
|
|
160
|
-
|
|
161
|
-
const relatedUserIds = adminQuery.data.map((resource) => resource.relationships.user.data.id).sort()
|
|
162
|
-
assert.deepEqual(relatedUserIds, [String(userA), String(userB)])
|
|
163
|
-
})
|
|
164
|
-
|
|
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')
|
|
168
|
-
|
|
169
|
-
const project = await createProjectForUser(ownerId, 'Primary Project')
|
|
170
|
-
|
|
171
|
-
await assert.rejects(async () => {
|
|
172
|
-
await api.resources.projects.get({
|
|
173
|
-
id: project.id,
|
|
174
|
-
simplified: false
|
|
175
|
-
}, { auth: { userId: otherId } })
|
|
176
|
-
}, (error) => {
|
|
177
|
-
assert.equal(error.statusCode, 403)
|
|
178
|
-
assert.match(error.message, /Access denied/)
|
|
179
|
-
return true
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
it('enforces helper ownership checks', () => {
|
|
184
|
-
const context = { auth: { userId: 123 } }
|
|
185
|
-
const resource = { user_id: 123 }
|
|
186
|
-
|
|
187
|
-
const auth = api.helpers.auth.requireOwnership(context, resource)
|
|
188
|
-
assert.equal(auth.userId, 123)
|
|
189
|
-
|
|
190
|
-
assert.throws(() => {
|
|
191
|
-
api.helpers.auth.requireOwnership({ auth: { userId: 123 } }, { user_id: 456 })
|
|
192
|
-
}, (error) => {
|
|
193
|
-
assert.equal(error.statusCode, 403)
|
|
194
|
-
assert.match(error.message, /Access denied/)
|
|
195
|
-
return true
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
assert.throws(() => {
|
|
199
|
-
api.helpers.auth.requireAuth({})
|
|
200
|
-
}, (error) => {
|
|
201
|
-
assert.equal(error.statusCode, 401)
|
|
202
|
-
assert.match(error.message, /Authentication required/)
|
|
203
|
-
return true
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
})
|