@directus/api 16.0.0 → 17.0.1
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/dist/controllers/items.js +8 -7
- package/dist/controllers/permissions.js +11 -2
- package/dist/controllers/utils.js +13 -32
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
- package/dist/flows.js +2 -1
- package/dist/middleware/collection-exists.js +6 -6
- package/dist/middleware/respond.js +1 -1
- package/dist/operations/request/index.js +5 -5
- package/dist/request/agent-with-ip-validation.d.ts +11 -0
- package/dist/request/agent-with-ip-validation.js +34 -0
- package/dist/request/index.js +6 -5
- package/dist/request/is-denied-ip.d.ts +1 -0
- package/dist/request/{validate-ip.js → is-denied-ip.js} +10 -12
- package/dist/services/assets.js +1 -3
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +15 -3
- package/dist/services/collections.d.ts +3 -2
- package/dist/services/collections.js +1 -1
- package/dist/services/fields.js +2 -1
- package/dist/services/files.js +4 -3
- package/dist/services/graphql/index.js +3 -2
- package/dist/services/{import-export/index.d.ts → import-export.d.ts} +1 -1
- package/dist/services/{import-export/index.js → import-export.js} +12 -11
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/items.js +2 -1
- package/dist/services/permissions.d.ts +3 -2
- package/dist/services/permissions.js +77 -2
- package/dist/services/relations.js +1 -1
- package/dist/services/roles.js +83 -15
- package/dist/services/server.js +2 -1
- package/dist/services/specifications.js +4 -3
- package/dist/services/utils.js +1 -1
- package/dist/telemetry/utils/get-user-item-count.js +2 -1
- package/dist/types/collection.d.ts +2 -13
- package/dist/types/items.d.ts +4 -12
- package/dist/types/items.js +0 -4
- package/dist/utils/get-field-system-rows.d.ts +2 -0
- package/dist/utils/get-field-system-rows.js +17 -0
- package/dist/utils/get-permissions.js +1 -1
- package/dist/utils/get-schema.js +3 -2
- package/dist/utils/merge-permissions-for-share.js +1 -1
- package/dist/utils/should-skip-cache.js +1 -1
- package/dist/websocket/handlers/items.js +2 -1
- package/dist/websocket/messages.d.ts +18 -18
- package/package.json +36 -35
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -107
- package/dist/database/system-data/app-access-permissions/index.d.ts +0 -3
- package/dist/database/system-data/app-access-permissions/index.js +0 -17
- package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +0 -17
- package/dist/database/system-data/collections/collections.yaml +0 -103
- package/dist/database/system-data/collections/index.d.ts +0 -2
- package/dist/database/system-data/collections/index.js +0 -9
- package/dist/database/system-data/fields/_defaults.yaml +0 -16
- package/dist/database/system-data/fields/activity.yaml +0 -83
- package/dist/database/system-data/fields/collections.yaml +0 -249
- package/dist/database/system-data/fields/dashboards.yaml +0 -20
- package/dist/database/system-data/fields/extensions.yaml +0 -10
- package/dist/database/system-data/fields/fields.yaml +0 -104
- package/dist/database/system-data/fields/files.yaml +0 -160
- package/dist/database/system-data/fields/flows.yaml +0 -26
- package/dist/database/system-data/fields/folders.yaml +0 -14
- package/dist/database/system-data/fields/index.d.ts +0 -2
- package/dist/database/system-data/fields/index.js +0 -33
- package/dist/database/system-data/fields/migrations.yaml +0 -10
- package/dist/database/system-data/fields/notifications.yaml +0 -15
- package/dist/database/system-data/fields/operations.yaml +0 -23
- package/dist/database/system-data/fields/panels.yaml +0 -29
- package/dist/database/system-data/fields/permissions.yaml +0 -37
- package/dist/database/system-data/fields/presets.yaml +0 -56
- package/dist/database/system-data/fields/relations.yaml +0 -34
- package/dist/database/system-data/fields/revisions.yaml +0 -30
- package/dist/database/system-data/fields/roles.yaml +0 -61
- package/dist/database/system-data/fields/sessions.yaml +0 -16
- package/dist/database/system-data/fields/settings.yaml +0 -471
- package/dist/database/system-data/fields/shares.yaml +0 -83
- package/dist/database/system-data/fields/translations.yaml +0 -27
- package/dist/database/system-data/fields/users.yaml +0 -224
- package/dist/database/system-data/fields/versions.yaml +0 -38
- package/dist/database/system-data/fields/webhooks.yaml +0 -141
- package/dist/database/system-data/relations/index.d.ts +0 -2
- package/dist/database/system-data/relations/index.js +0 -9
- package/dist/database/system-data/relations/relations.yaml +0 -197
- package/dist/request/request-interceptor.d.ts +0 -2
- package/dist/request/request-interceptor.js +0 -28
- package/dist/request/response-interceptor.d.ts +0 -2
- package/dist/request/response-interceptor.js +0 -5
- package/dist/request/validate-ip.d.ts +0 -1
- package/dist/services/import-export/import-worker.d.ts +0 -9
- package/dist/services/import-export/import-worker.js +0 -9
- package/dist/worker-pool.d.ts +0 -2
- package/dist/worker-pool.js +0 -19
|
@@ -8,9 +8,10 @@ import { ItemsService } from '../services/items.js';
|
|
|
8
8
|
import { MetaService } from '../services/meta.js';
|
|
9
9
|
import asyncHandler from '../utils/async-handler.js';
|
|
10
10
|
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
11
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
11
12
|
const router = express.Router();
|
|
12
13
|
router.post('/:collection', collectionExists, asyncHandler(async (req, res, next) => {
|
|
13
|
-
if (req.params['collection']
|
|
14
|
+
if (isSystemCollection(req.params['collection']))
|
|
14
15
|
throw new ForbiddenError();
|
|
15
16
|
if (req.singleton) {
|
|
16
17
|
throw new RouteNotFoundError({ path: req.path });
|
|
@@ -47,7 +48,7 @@ router.post('/:collection', collectionExists, asyncHandler(async (req, res, next
|
|
|
47
48
|
return next();
|
|
48
49
|
}), respond);
|
|
49
50
|
const readHandler = asyncHandler(async (req, res, next) => {
|
|
50
|
-
if (req.params['collection']
|
|
51
|
+
if (isSystemCollection(req.params['collection']))
|
|
51
52
|
throw new ForbiddenError();
|
|
52
53
|
const service = new ItemsService(req.collection, {
|
|
53
54
|
accountability: req.accountability,
|
|
@@ -77,7 +78,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
|
|
|
77
78
|
router.search('/:collection', collectionExists, validateBatch('read'), readHandler, respond);
|
|
78
79
|
router.get('/:collection', collectionExists, readHandler, respond);
|
|
79
80
|
router.get('/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => {
|
|
80
|
-
if (req.params['collection']
|
|
81
|
+
if (isSystemCollection(req.params['collection']))
|
|
81
82
|
throw new ForbiddenError();
|
|
82
83
|
const service = new ItemsService(req.collection, {
|
|
83
84
|
accountability: req.accountability,
|
|
@@ -90,7 +91,7 @@ router.get('/:collection/:pk', collectionExists, asyncHandler(async (req, res, n
|
|
|
90
91
|
return next();
|
|
91
92
|
}), respond);
|
|
92
93
|
router.patch('/:collection', collectionExists, validateBatch('update'), asyncHandler(async (req, res, next) => {
|
|
93
|
-
if (req.params['collection']
|
|
94
|
+
if (isSystemCollection(req.params['collection']))
|
|
94
95
|
throw new ForbiddenError();
|
|
95
96
|
const service = new ItemsService(req.collection, {
|
|
96
97
|
accountability: req.accountability,
|
|
@@ -126,7 +127,7 @@ router.patch('/:collection', collectionExists, validateBatch('update'), asyncHan
|
|
|
126
127
|
return next();
|
|
127
128
|
}), respond);
|
|
128
129
|
router.patch('/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => {
|
|
129
|
-
if (req.params['collection']
|
|
130
|
+
if (isSystemCollection(req.params['collection']))
|
|
130
131
|
throw new ForbiddenError();
|
|
131
132
|
if (req.singleton) {
|
|
132
133
|
throw new RouteNotFoundError({ path: req.path });
|
|
@@ -149,7 +150,7 @@ router.patch('/:collection/:pk', collectionExists, asyncHandler(async (req, res,
|
|
|
149
150
|
return next();
|
|
150
151
|
}), respond);
|
|
151
152
|
router.delete('/:collection', collectionExists, validateBatch('delete'), asyncHandler(async (req, _res, next) => {
|
|
152
|
-
if (req.params['collection']
|
|
153
|
+
if (isSystemCollection(req.params['collection']))
|
|
153
154
|
throw new ForbiddenError();
|
|
154
155
|
const service = new ItemsService(req.collection, {
|
|
155
156
|
accountability: req.accountability,
|
|
@@ -168,7 +169,7 @@ router.delete('/:collection', collectionExists, validateBatch('delete'), asyncHa
|
|
|
168
169
|
return next();
|
|
169
170
|
}), respond);
|
|
170
171
|
router.delete('/:collection/:pk', collectionExists, asyncHandler(async (req, _res, next) => {
|
|
171
|
-
if (req.params['collection']
|
|
172
|
+
if (isSystemCollection(req.params['collection']))
|
|
172
173
|
throw new ForbiddenError();
|
|
173
174
|
const service = new ItemsService(req.collection, {
|
|
174
175
|
accountability: req.accountability,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { isDirectusError } from '@directus/errors';
|
|
1
|
+
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
2
2
|
import express from 'express';
|
|
3
|
-
import { ErrorCode } from '@directus/errors';
|
|
4
3
|
import { respond } from '../middleware/respond.js';
|
|
5
4
|
import useCollection from '../middleware/use-collection.js';
|
|
6
5
|
import { validateBatch } from '../middleware/validate-batch.js';
|
|
@@ -152,4 +151,14 @@ router.delete('/:pk', asyncHandler(async (req, _res, next) => {
|
|
|
152
151
|
await service.deleteOne(req.params['pk']);
|
|
153
152
|
return next();
|
|
154
153
|
}), respond);
|
|
154
|
+
router.get('/me/:collection/:pk?', asyncHandler(async (req, res, next) => {
|
|
155
|
+
const { collection, pk } = req.params;
|
|
156
|
+
const service = new PermissionsService({
|
|
157
|
+
accountability: req.accountability,
|
|
158
|
+
schema: req.schema,
|
|
159
|
+
});
|
|
160
|
+
const itemPermissions = await service.getItemPermissions(collection, pk);
|
|
161
|
+
res.locals['payload'] = { data: itemPermissions };
|
|
162
|
+
return next();
|
|
163
|
+
}), respond);
|
|
155
164
|
export default router;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
|
|
1
2
|
import argon2 from 'argon2';
|
|
2
3
|
import Busboy from 'busboy';
|
|
3
4
|
import { Router } from 'express';
|
|
4
5
|
import Joi from 'joi';
|
|
5
|
-
import fs from 'node:fs';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
import { InvalidPayloadError, InvalidQueryError, UnsupportedMediaTypeError } from '@directus/errors';
|
|
8
6
|
import collectionExists from '../middleware/collection-exists.js';
|
|
9
7
|
import { respond } from '../middleware/respond.js';
|
|
10
|
-
import { ExportService } from '../services/import-export
|
|
8
|
+
import { ExportService, ImportService } from '../services/import-export.js';
|
|
11
9
|
import { RevisionsService } from '../services/revisions.js';
|
|
12
10
|
import { UtilsService } from '../services/utils.js';
|
|
13
11
|
import asyncHandler from '../utils/async-handler.js';
|
|
@@ -66,6 +64,10 @@ router.post('/import/:collection', collectionExists, asyncHandler(async (req, re
|
|
|
66
64
|
if (req.is('multipart/form-data') === false) {
|
|
67
65
|
throw new UnsupportedMediaTypeError({ mediaType: req.headers['content-type'], where: 'Content-Type header' });
|
|
68
66
|
}
|
|
67
|
+
const service = new ImportService({
|
|
68
|
+
accountability: req.accountability,
|
|
69
|
+
schema: req.schema,
|
|
70
|
+
});
|
|
69
71
|
let headers;
|
|
70
72
|
if (req.headers['content-type']) {
|
|
71
73
|
headers = req.headers;
|
|
@@ -78,34 +80,13 @@ router.post('/import/:collection', collectionExists, asyncHandler(async (req, re
|
|
|
78
80
|
}
|
|
79
81
|
const busboy = Busboy({ headers });
|
|
80
82
|
busboy.on('file', async (_fieldname, fileStream, { mimeType }) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const workerPool = getWorkerPool();
|
|
89
|
-
const require = createRequire(import.meta.url);
|
|
90
|
-
const filename = require.resolve('../services/import-export/import-worker');
|
|
91
|
-
const workerData = {
|
|
92
|
-
collection: req.params['collection'],
|
|
93
|
-
mimeType,
|
|
94
|
-
filePath: tmpFile.path,
|
|
95
|
-
accountability: req.accountability,
|
|
96
|
-
schema: req.schema,
|
|
97
|
-
};
|
|
98
|
-
try {
|
|
99
|
-
await workerPool.run(workerData, { filename });
|
|
100
|
-
res.status(200).end();
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
next(error);
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
await tmpFile.cleanup();
|
|
107
|
-
}
|
|
108
|
-
});
|
|
83
|
+
try {
|
|
84
|
+
await service.import(req.params['collection'], mimeType, fileStream);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return next(err);
|
|
88
|
+
}
|
|
89
|
+
return res.status(200).end();
|
|
109
90
|
});
|
|
110
91
|
busboy.on('error', (err) => next(err));
|
|
111
92
|
req.pipe(busboy);
|
|
@@ -16,7 +16,7 @@ export declare function generateApiExtensionsSandboxEntrypoint(type: ApiExtensio
|
|
|
16
16
|
unregisterFunction: () => Promise<void>;
|
|
17
17
|
} | {
|
|
18
18
|
code: string;
|
|
19
|
-
hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "
|
|
19
|
+
hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "PATCH" | "PUT">, cb: import("isolated-vm").Reference<(req: {
|
|
20
20
|
url: string;
|
|
21
21
|
headers: import("http").IncomingHttpHeaders;
|
|
22
22
|
body: string;
|
package/dist/flows.js
CHANGED
|
@@ -19,6 +19,7 @@ import { mapValuesDeep } from './utils/map-values-deep.js';
|
|
|
19
19
|
import { redactObject } from './utils/redact-object.js';
|
|
20
20
|
import { sanitizeError } from './utils/sanitize-error.js';
|
|
21
21
|
import { scheduleSynchronizedJob, validateCron } from './utils/schedule.js';
|
|
22
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
22
23
|
let flowManager;
|
|
23
24
|
export function getFlowManager() {
|
|
24
25
|
if (flowManager) {
|
|
@@ -111,7 +112,7 @@ class FlowManager {
|
|
|
111
112
|
if (!flow.options?.['collections'])
|
|
112
113
|
return [];
|
|
113
114
|
return toArray(flow.options['collections']).map((collection) => {
|
|
114
|
-
if (collection
|
|
115
|
+
if (isSystemCollection(collection)) {
|
|
115
116
|
const action = scope.split('.')[1];
|
|
116
117
|
return collection.substring(9) + '.' + action;
|
|
117
118
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Check if requested collection exists, and save it to req.collection
|
|
3
3
|
*/
|
|
4
|
-
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
5
4
|
import { ForbiddenError } from '@directus/errors';
|
|
5
|
+
import { systemCollectionRows } from '@directus/system-data';
|
|
6
6
|
import asyncHandler from '../utils/async-handler.js';
|
|
7
7
|
const collectionExists = asyncHandler(async (req, _res, next) => {
|
|
8
8
|
if (!req.params['collection'])
|
|
@@ -11,11 +11,11 @@ const collectionExists = asyncHandler(async (req, _res, next) => {
|
|
|
11
11
|
throw new ForbiddenError();
|
|
12
12
|
}
|
|
13
13
|
req.collection = req.params['collection'];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
req.singleton = !!
|
|
14
|
+
const systemCollectionRow = systemCollectionRows.find((collection) => {
|
|
15
|
+
return collection?.collection === req.collection;
|
|
16
|
+
});
|
|
17
|
+
if (systemCollectionRow !== undefined) {
|
|
18
|
+
req.singleton = !!systemCollectionRow?.singleton;
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
21
|
req.singleton = req.schema.collections[req.collection]?.singleton ?? false;
|
|
@@ -3,7 +3,7 @@ import { parse as parseBytesConfiguration } from 'bytes';
|
|
|
3
3
|
import { assign } from 'lodash-es';
|
|
4
4
|
import { getCache, setCacheValue } from '../cache.js';
|
|
5
5
|
import { useLogger } from '../logger.js';
|
|
6
|
-
import { ExportService } from '../services/import-export
|
|
6
|
+
import { ExportService } from '../services/import-export.js';
|
|
7
7
|
import { VersionsService } from '../services/versions.js';
|
|
8
8
|
import asyncHandler from '../utils/async-handler.js';
|
|
9
9
|
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
|
|
@@ -24,12 +24,12 @@ export default defineOperationApi({
|
|
|
24
24
|
return { status: result.status, statusText: result.statusText, headers: result.headers, data: result.data };
|
|
25
25
|
}
|
|
26
26
|
catch (error) {
|
|
27
|
-
if (isAxiosError(error)) {
|
|
27
|
+
if (isAxiosError(error) && error.response) {
|
|
28
28
|
throw JSON.stringify({
|
|
29
|
-
status: error.response
|
|
30
|
-
statusText: error.response
|
|
31
|
-
headers: error.response
|
|
32
|
-
data: error.response
|
|
29
|
+
status: error.response.status,
|
|
30
|
+
statusText: error.response.statusText,
|
|
31
|
+
headers: error.response.headers,
|
|
32
|
+
data: error.response.data,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Agent, ClientRequestArgs } from 'node:http';
|
|
3
|
+
/**
|
|
4
|
+
* 'createConnection' is missing in 'Agent' type, but assigned in actual implementation:
|
|
5
|
+
* https://github.com/nodejs/node/blob/8a41d9b636be86350cd32847c3f89d327c4f6ff7/lib/_http_agent.js#L215
|
|
6
|
+
*/
|
|
7
|
+
export type _Agent = Agent & {
|
|
8
|
+
createConnection: NonNullable<ClientRequestArgs['createConnection']>;
|
|
9
|
+
};
|
|
10
|
+
/** Extends a HTTP agent with IP validation */
|
|
11
|
+
export declare const agentWithIpValidation: (agent: Agent) => Agent;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
import { isDeniedIp } from './is-denied-ip.js';
|
|
3
|
+
const deniedError = (domain) => new Error(`Requested domain "${domain}" resolves to a denied IP address`);
|
|
4
|
+
/** Extends a HTTP agent with IP validation */
|
|
5
|
+
export const agentWithIpValidation = (agent) => {
|
|
6
|
+
const _agent = agent;
|
|
7
|
+
const { createConnection } = _agent;
|
|
8
|
+
_agent.createConnection = function (options, oncreate) {
|
|
9
|
+
const { host } = options;
|
|
10
|
+
/*
|
|
11
|
+
* Unexpected, but according to the types 'host' might be undefined.
|
|
12
|
+
* In that case, the request is denied to be on the safe side,
|
|
13
|
+
* since the host cannot be verified.
|
|
14
|
+
*/
|
|
15
|
+
if (!host) {
|
|
16
|
+
throw new Error('Request cannot be verified due to missing host');
|
|
17
|
+
}
|
|
18
|
+
/*
|
|
19
|
+
* At this point, host is only verified if it's already an IP address.
|
|
20
|
+
* Otherwise it will be verified on 'lookup' event.
|
|
21
|
+
*/
|
|
22
|
+
if (isIP(host) !== 0 && isDeniedIp(host))
|
|
23
|
+
throw deniedError(host);
|
|
24
|
+
const socket = createConnection.call(this, options, oncreate);
|
|
25
|
+
// Emitted after resolving the host name but before connecting.
|
|
26
|
+
socket.on('lookup', (error, address) => {
|
|
27
|
+
if (error || !isDeniedIp(address))
|
|
28
|
+
return;
|
|
29
|
+
return socket.destroy(deniedError(host));
|
|
30
|
+
});
|
|
31
|
+
return socket;
|
|
32
|
+
};
|
|
33
|
+
return agent;
|
|
34
|
+
};
|
package/dist/request/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { requestInterceptor } from './request-interceptor.js';
|
|
2
|
-
import { responseInterceptor } from './response-interceptor.js';
|
|
3
1
|
export const _cache = {
|
|
4
2
|
axiosInstance: null,
|
|
5
3
|
};
|
|
6
4
|
export async function getAxios() {
|
|
7
5
|
if (!_cache.axiosInstance) {
|
|
8
6
|
const axios = (await import('axios')).default;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const { Agent: AgentHttp } = await import('node:http');
|
|
8
|
+
const { Agent: AgentHttps } = await import('node:https');
|
|
9
|
+
const { agentWithIpValidation } = await import('./agent-with-ip-validation.js');
|
|
10
|
+
const httpAgent = agentWithIpValidation(new AgentHttp());
|
|
11
|
+
const httpsAgent = agentWithIpValidation(new AgentHttps());
|
|
12
|
+
_cache.axiosInstance = axios.create({ httpAgent, httpsAgent });
|
|
12
13
|
}
|
|
13
14
|
return _cache.axiosInstance;
|
|
14
15
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isDeniedIp(ip: string): boolean;
|
|
@@ -2,34 +2,32 @@ import { useEnv } from '@directus/env';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { useLogger } from '../logger.js';
|
|
4
4
|
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
5
|
-
|
|
6
|
-
export function validateIp(ip, url) {
|
|
5
|
+
export function isDeniedIp(ip) {
|
|
7
6
|
const env = useEnv();
|
|
8
7
|
const logger = useLogger();
|
|
9
8
|
const ipDenyList = env['IMPORT_IP_DENY_LIST'];
|
|
10
9
|
if (ipDenyList.length === 0)
|
|
11
|
-
return;
|
|
12
|
-
let denied;
|
|
10
|
+
return false;
|
|
13
11
|
try {
|
|
14
|
-
denied = ipInNetworks(ip, ipDenyList);
|
|
12
|
+
const denied = ipInNetworks(ip, ipDenyList);
|
|
13
|
+
if (denied)
|
|
14
|
+
return true;
|
|
15
15
|
}
|
|
16
16
|
catch (error) {
|
|
17
|
-
logger.warn(`
|
|
17
|
+
logger.warn(`Cannot verify IP address due to invalid "IMPORT_IP_DENY_LIST" config`);
|
|
18
18
|
logger.warn(error);
|
|
19
|
-
|
|
19
|
+
return true;
|
|
20
20
|
}
|
|
21
|
-
if (denied)
|
|
22
|
-
throw deniedError(url);
|
|
23
21
|
if (ipDenyList.includes('0.0.0.0')) {
|
|
24
22
|
const networkInterfaces = os.networkInterfaces();
|
|
25
23
|
for (const networkInfo of Object.values(networkInterfaces)) {
|
|
26
24
|
if (!networkInfo)
|
|
27
25
|
continue;
|
|
28
26
|
for (const info of networkInfo) {
|
|
29
|
-
if (info.address === ip)
|
|
30
|
-
|
|
31
|
-
}
|
|
27
|
+
if (info.address === ip)
|
|
28
|
+
return true;
|
|
32
29
|
}
|
|
33
30
|
}
|
|
34
31
|
}
|
|
32
|
+
return false;
|
|
35
33
|
}
|
package/dist/services/assets.js
CHANGED
|
@@ -24,7 +24,7 @@ export class AssetsService {
|
|
|
24
24
|
constructor(options) {
|
|
25
25
|
this.knex = options.knex || getDatabase();
|
|
26
26
|
this.accountability = options.accountability || null;
|
|
27
|
-
this.filesService = new FilesService(options);
|
|
27
|
+
this.filesService = new FilesService({ ...options, accountability: null });
|
|
28
28
|
this.authorizationService = new AuthorizationService(options);
|
|
29
29
|
}
|
|
30
30
|
async getAsset(id, transformation, range) {
|
|
@@ -46,8 +46,6 @@ export class AssetsService {
|
|
|
46
46
|
await this.authorizationService.checkAccess('read', 'directus_files', id);
|
|
47
47
|
}
|
|
48
48
|
const file = (await this.filesService.readOne(id, { limit: 1 }));
|
|
49
|
-
if (!file)
|
|
50
|
-
throw new ForbiddenError();
|
|
51
49
|
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
52
50
|
if (!exists)
|
|
53
51
|
throw new ForbiddenError();
|
|
@@ -13,5 +13,5 @@ export declare class AuthorizationService {
|
|
|
13
13
|
* Checks if the provided payload matches the configured permissions, and adds the presets to the payload.
|
|
14
14
|
*/
|
|
15
15
|
validatePayload(action: PermissionsAction, collection: string, data: Partial<Item>): Partial<Item>;
|
|
16
|
-
checkAccess(action: PermissionsAction, collection: string, pk
|
|
16
|
+
checkAccess(action: PermissionsAction, collection: string, pk?: PrimaryKey | PrimaryKey[]): Promise<void>;
|
|
17
17
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { ForbiddenError } from '@directus/errors';
|
|
1
2
|
import { validatePayload } from '@directus/utils';
|
|
2
3
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
3
4
|
import { cloneDeep, flatten, isArray, isNil, merge, reduce, uniq, uniqWith } from 'lodash-es';
|
|
4
5
|
import { GENERATE_SPECIAL } from '../constants.js';
|
|
5
6
|
import getDatabase from '../database/index.js';
|
|
6
|
-
import { ForbiddenError } from '@directus/errors';
|
|
7
7
|
import { getRelationInfo } from '../utils/get-relation-info.js';
|
|
8
8
|
import { stripFunction } from '../utils/strip-function.js';
|
|
9
9
|
import { ItemsService } from './items.js';
|
|
@@ -430,15 +430,27 @@ export class AuthorizationService {
|
|
|
430
430
|
};
|
|
431
431
|
if (Array.isArray(pk)) {
|
|
432
432
|
const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
|
|
433
|
-
|
|
433
|
+
// for the unexpected case that the result is not an array (for example due to filter hook)
|
|
434
|
+
if (!isArray(result))
|
|
434
435
|
throw new ForbiddenError();
|
|
435
436
|
if (result.length !== pk.length)
|
|
436
437
|
throw new ForbiddenError();
|
|
437
438
|
}
|
|
438
|
-
else {
|
|
439
|
+
else if (pk) {
|
|
439
440
|
const result = await itemsService.readOne(pk, query, { permissionsAction: action });
|
|
440
441
|
if (!result)
|
|
441
442
|
throw new ForbiddenError();
|
|
442
443
|
}
|
|
444
|
+
else {
|
|
445
|
+
query.limit = 1;
|
|
446
|
+
const result = await itemsService.readByQuery(query, { permissionsAction: action });
|
|
447
|
+
// for the unexpected case that the result is not an array (for example due to filter hook)
|
|
448
|
+
if (!isArray(result))
|
|
449
|
+
throw new ForbiddenError();
|
|
450
|
+
// for create action, an empty array is expected - for other actions, the first item is expected to be available
|
|
451
|
+
const access = action === 'create' ? result.length === 0 : !!result[0];
|
|
452
|
+
if (!access)
|
|
453
|
+
throw new ForbiddenError();
|
|
454
|
+
}
|
|
443
455
|
}
|
|
444
456
|
}
|
|
@@ -3,12 +3,13 @@ import type { Accountability, RawField, SchemaOverview } from '@directus/types';
|
|
|
3
3
|
import type Keyv from 'keyv';
|
|
4
4
|
import type { Knex } from 'knex';
|
|
5
5
|
import type { Helpers } from '../database/helpers/index.js';
|
|
6
|
-
import type { AbstractServiceOptions, Collection,
|
|
6
|
+
import type { AbstractServiceOptions, Collection, MutationOptions } from '../types/index.js';
|
|
7
|
+
import { type BaseCollectionMeta } from '@directus/system-data';
|
|
7
8
|
export type RawCollection = {
|
|
8
9
|
collection: string;
|
|
9
10
|
fields?: RawField[];
|
|
10
11
|
schema?: Partial<Table> | null;
|
|
11
|
-
meta?: Partial<
|
|
12
|
+
meta?: Partial<BaseCollectionMeta> | null;
|
|
12
13
|
};
|
|
13
14
|
export declare class CollectionsService {
|
|
14
15
|
knex: Knex;
|
|
@@ -7,12 +7,12 @@ import { clearSystemCache, getCache } from '../cache.js';
|
|
|
7
7
|
import { ALIAS_TYPES } from '../constants.js';
|
|
8
8
|
import { getHelpers } from '../database/helpers/index.js';
|
|
9
9
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
10
|
-
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
11
10
|
import emitter from '../emitter.js';
|
|
12
11
|
import { getSchema } from '../utils/get-schema.js';
|
|
13
12
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
14
13
|
import { FieldsService } from './fields.js';
|
|
15
14
|
import { ItemsService } from './items.js';
|
|
15
|
+
import { systemCollectionRows } from '@directus/system-data';
|
|
16
16
|
export class CollectionsService {
|
|
17
17
|
knex;
|
|
18
18
|
helpers;
|
package/dist/services/fields.js
CHANGED
|
@@ -7,7 +7,6 @@ import { ALIAS_TYPES } from '../constants.js';
|
|
|
7
7
|
import { translateDatabaseError } from '../database/errors/translate.js';
|
|
8
8
|
import { getHelpers } from '../database/helpers/index.js';
|
|
9
9
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
10
|
-
import { systemFieldRows } from '../database/system-data/fields/index.js';
|
|
11
10
|
import emitter from '../emitter.js';
|
|
12
11
|
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
13
12
|
import { ItemsService } from './items.js';
|
|
@@ -18,6 +17,8 @@ import { getSchema } from '../utils/get-schema.js';
|
|
|
18
17
|
import { sanitizeColumn } from '../utils/sanitize-schema.js';
|
|
19
18
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
20
19
|
import { RelationsService } from './relations.js';
|
|
20
|
+
import { getSystemFieldRowsWithAuthProviders } from '../utils/get-field-system-rows.js';
|
|
21
|
+
const systemFieldRows = getSystemFieldRowsWithAuthProviders();
|
|
21
22
|
export class FieldsService {
|
|
22
23
|
knex;
|
|
23
24
|
helpers;
|
package/dist/services/files.js
CHANGED
|
@@ -288,11 +288,12 @@ export class FilesService extends ItemsService {
|
|
|
288
288
|
decompress: false,
|
|
289
289
|
});
|
|
290
290
|
}
|
|
291
|
-
catch (
|
|
292
|
-
logger.warn(
|
|
291
|
+
catch (error) {
|
|
292
|
+
logger.warn(`Couldn't fetch file from URL "${importURL}"${error.message ? `: ${error.message}` : ''}`);
|
|
293
|
+
logger.trace(error);
|
|
293
294
|
throw new ServiceUnavailableError({
|
|
294
295
|
service: 'external-file',
|
|
295
|
-
reason: `Couldn't fetch file from
|
|
296
|
+
reason: `Couldn't fetch file from URL "${importURL}"`,
|
|
296
297
|
});
|
|
297
298
|
}
|
|
298
299
|
const parsedURL = url.parse(fileResponse.request.res.responseUrl);
|
|
@@ -40,6 +40,7 @@ import { GraphQLStringOrFloat } from './types/string-or-float.js';
|
|
|
40
40
|
import { GraphQLVoid } from './types/void.js';
|
|
41
41
|
import { addPathToValidationError } from './utils/add-path-to-validation-error.js';
|
|
42
42
|
import processError from './utils/process-error.js';
|
|
43
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
43
44
|
const env = useEnv();
|
|
44
45
|
const validationRules = Array.from(specifiedRules);
|
|
45
46
|
if (env['GRAPHQL_INTROSPECTION'] === false) {
|
|
@@ -129,10 +130,10 @@ export class GraphQLService {
|
|
|
129
130
|
const { ReadCollectionTypes, VersionCollectionTypes } = getReadableTypes();
|
|
130
131
|
const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
|
|
131
132
|
const scopeFilter = (collection) => {
|
|
132
|
-
if (this.scope === 'items' && collection.collection
|
|
133
|
+
if (this.scope === 'items' && isSystemCollection(collection.collection))
|
|
133
134
|
return false;
|
|
134
135
|
if (this.scope === 'system') {
|
|
135
|
-
if (collection.collection
|
|
136
|
+
if (isSystemCollection(collection.collection) === false)
|
|
136
137
|
return false;
|
|
137
138
|
if (SYSTEM_DENY_LIST.includes(collection.collection))
|
|
138
139
|
return false;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Accountability, File, Query, SchemaOverview } from '@directus/types';
|
|
3
3
|
import type { Knex } from 'knex';
|
|
4
4
|
import type { Readable } from 'node:stream';
|
|
5
|
-
import type { AbstractServiceOptions } from '
|
|
5
|
+
import type { AbstractServiceOptions } from '../types/index.js';
|
|
6
6
|
type ExportFormat = 'csv' | 'json' | 'xml' | 'yaml';
|
|
7
7
|
export declare class ImportService {
|
|
8
8
|
knex: Knex;
|
|
@@ -10,16 +10,17 @@ import { createReadStream } from 'node:fs';
|
|
|
10
10
|
import { appendFile } from 'node:fs/promises';
|
|
11
11
|
import Papa from 'papaparse';
|
|
12
12
|
import StreamArray from 'stream-json/streamers/StreamArray.js';
|
|
13
|
-
import getDatabase from '
|
|
14
|
-
import emitter from '
|
|
15
|
-
import { useLogger } from '
|
|
16
|
-
import { getDateFormatted } from '
|
|
17
|
-
import { Url } from '
|
|
18
|
-
import { userName } from '
|
|
19
|
-
import { FilesService } from '
|
|
20
|
-
import { ItemsService } from '
|
|
21
|
-
import { NotificationsService } from '
|
|
22
|
-
import { UsersService } from '
|
|
13
|
+
import getDatabase from '../database/index.js';
|
|
14
|
+
import emitter from '../emitter.js';
|
|
15
|
+
import { useLogger } from '../logger.js';
|
|
16
|
+
import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
17
|
+
import { Url } from '../utils/url.js';
|
|
18
|
+
import { userName } from '../utils/user-name.js';
|
|
19
|
+
import { FilesService } from './files.js';
|
|
20
|
+
import { ItemsService } from './items.js';
|
|
21
|
+
import { NotificationsService } from './notifications.js';
|
|
22
|
+
import { UsersService } from './users.js';
|
|
23
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
23
24
|
const env = useEnv();
|
|
24
25
|
const logger = useLogger();
|
|
25
26
|
export class ImportService {
|
|
@@ -32,7 +33,7 @@ export class ImportService {
|
|
|
32
33
|
this.schema = options.schema;
|
|
33
34
|
}
|
|
34
35
|
async import(collection, mimetype, stream) {
|
|
35
|
-
if (this.accountability?.admin !== true && collection
|
|
36
|
+
if (this.accountability?.admin !== true && isSystemCollection(collection))
|
|
36
37
|
throw new ForbiddenError();
|
|
37
38
|
const createPermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'create');
|
|
38
39
|
const updatePermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'update');
|
package/dist/services/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './files.js';
|
|
|
10
10
|
export * from './flows.js';
|
|
11
11
|
export * from './folders.js';
|
|
12
12
|
export * from './graphql/index.js';
|
|
13
|
-
export * from './import-export
|
|
13
|
+
export * from './import-export.js';
|
|
14
14
|
export * from './items.js';
|
|
15
15
|
export * from './mail/index.js';
|
|
16
16
|
export * from './meta.js';
|
package/dist/services/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export * from './files.js';
|
|
|
10
10
|
export * from './flows.js';
|
|
11
11
|
export * from './folders.js';
|
|
12
12
|
export * from './graphql/index.js';
|
|
13
|
-
export * from './import-export
|
|
13
|
+
export * from './import-export.js';
|
|
14
14
|
export * from './items.js';
|
|
15
15
|
export * from './mail/index.js';
|
|
16
16
|
export * from './meta.js';
|
package/dist/services/items.js
CHANGED
|
@@ -13,6 +13,7 @@ import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
|
13
13
|
import { validateKeys } from '../utils/validate-keys.js';
|
|
14
14
|
import { AuthorizationService } from './authorization.js';
|
|
15
15
|
import { PayloadService } from './payload.js';
|
|
16
|
+
import { isSystemCollection } from '@directus/system-data';
|
|
16
17
|
const env = useEnv();
|
|
17
18
|
export class ItemsService {
|
|
18
19
|
collection;
|
|
@@ -25,7 +26,7 @@ export class ItemsService {
|
|
|
25
26
|
this.collection = collection;
|
|
26
27
|
this.knex = options.knex || getDatabase();
|
|
27
28
|
this.accountability = options.accountability || null;
|
|
28
|
-
this.eventScope = this.collection
|
|
29
|
+
this.eventScope = isSystemCollection(this.collection) ? this.collection.substring(9) : 'items';
|
|
29
30
|
this.schema = options.schema;
|
|
30
31
|
this.cache = getCache().cache;
|
|
31
32
|
return this;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { PermissionsAction, Query } from '@directus/types';
|
|
1
|
+
import type { ItemPermissions, PermissionsAction, Query } from '@directus/types';
|
|
2
2
|
import type Keyv from 'keyv';
|
|
3
|
+
import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
|
|
3
4
|
import type { QueryOptions } from './items.js';
|
|
4
5
|
import { ItemsService } from './items.js';
|
|
5
|
-
import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
|
|
6
6
|
export declare class PermissionsService extends ItemsService {
|
|
7
7
|
systemCache: Keyv<any>;
|
|
8
8
|
constructor(options: AbstractServiceOptions);
|
|
@@ -15,4 +15,5 @@ export declare class PermissionsService extends ItemsService {
|
|
|
15
15
|
updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
16
16
|
upsertMany(payloads: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
17
17
|
deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
18
|
+
getItemPermissions(collection: string, primaryKey?: string): Promise<ItemPermissions>;
|
|
18
19
|
}
|