directus 9.22.4 → 9.23.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/app.js +5 -4
- package/dist/auth/drivers/ldap.d.ts +2 -2
- package/dist/auth/drivers/ldap.js +8 -8
- package/dist/auth/drivers/oauth2.js +2 -2
- package/dist/auth/drivers/openid.js +2 -2
- package/dist/cache.js +4 -4
- package/dist/cli/commands/schema/apply.js +19 -17
- package/dist/cli/utils/create-db-connection.d.ts +2 -1
- package/dist/cli/utils/create-env/env-stub.liquid +1 -1
- package/dist/cli/utils/drivers.d.ts +3 -9
- package/dist/constants.d.ts +2 -8
- package/dist/constants.js +3 -7
- package/dist/controllers/assets.js +5 -5
- package/dist/controllers/extensions.js +7 -7
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/graphql.js +8 -0
- package/dist/controllers/schema.d.ts +2 -0
- package/dist/controllers/schema.js +98 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/oracle.d.ts +4 -1
- package/dist/database/helpers/schema/dialects/oracle.js +25 -0
- package/dist/database/helpers/schema/types.d.ts +8 -6
- package/dist/database/helpers/schema/types.js +7 -1
- package/dist/database/index.d.ts +2 -1
- package/dist/database/run-ast.js +2 -2
- package/dist/env.js +9 -2
- package/dist/extensions.js +1 -1
- package/dist/flows.js +17 -8
- package/dist/middleware/cache.js +2 -2
- package/dist/middleware/respond.js +14 -9
- package/dist/operations/request/index.js +2 -1
- package/dist/operations/trigger/index.d.ts +2 -0
- package/dist/operations/trigger/index.js +26 -9
- package/dist/request/index.d.ts +5 -0
- package/dist/request/index.js +18 -0
- package/dist/request/index.test.d.ts +1 -0
- package/dist/request/request-interceptor.d.ts +2 -0
- package/dist/request/request-interceptor.js +33 -0
- package/dist/request/request-interceptor.test.d.ts +1 -0
- package/dist/request/response-interceptor.d.ts +2 -0
- package/dist/request/response-interceptor.js +9 -0
- package/dist/request/response-interceptor.test.d.ts +1 -0
- package/dist/request/validate-ip.d.ts +1 -0
- package/dist/request/validate-ip.js +27 -0
- package/dist/request/validate-ip.test.d.ts +1 -0
- package/dist/services/assets.d.ts +1 -1
- package/dist/services/assets.js +11 -2
- package/dist/services/authentication.js +5 -5
- package/dist/services/fields.js +1 -0
- package/dist/services/files.js +44 -88
- package/dist/services/graphql/index.js +14 -8
- package/dist/services/graphql/utils/process-error.js +22 -9
- package/dist/services/import-export.d.ts +4 -2
- package/dist/services/import-export.js +17 -3
- package/dist/services/import-export.test.d.ts +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +34 -15
- package/dist/services/relations.js +2 -0
- package/dist/services/roles.js +32 -11
- package/dist/services/schema.d.ts +15 -0
- package/dist/services/schema.js +58 -0
- package/dist/services/schema.test.d.ts +1 -0
- package/dist/services/shares.d.ts +2 -2
- package/dist/services/shares.js +9 -9
- package/dist/services/users.js +74 -47
- package/dist/types/assets.d.ts +1 -1
- package/dist/types/database.d.ts +3 -0
- package/dist/types/database.js +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/items.d.ts +5 -0
- package/dist/types/snapshot.d.ts +22 -0
- package/dist/types/snapshot.js +14 -0
- package/dist/utils/apply-diff.d.ts +9 -0
- package/dist/utils/apply-diff.js +259 -0
- package/dist/utils/apply-diff.test.d.ts +1 -0
- package/dist/utils/apply-query.js +8 -6
- package/dist/utils/apply-snapshot.d.ts +1 -3
- package/dist/utils/apply-snapshot.js +4 -234
- package/dist/utils/get-cache-headers.d.ts +3 -1
- package/dist/utils/get-cache-headers.js +20 -19
- package/dist/utils/get-cache-headers.test.d.ts +1 -0
- package/dist/utils/get-milliseconds.d.ts +4 -0
- package/dist/utils/get-milliseconds.js +15 -0
- package/dist/utils/get-milliseconds.test.d.ts +1 -0
- package/dist/utils/get-snapshot-diff.js +11 -7
- package/dist/utils/get-snapshot.js +29 -6
- package/dist/utils/get-versioned-hash.d.ts +1 -0
- package/dist/utils/get-versioned-hash.js +12 -0
- package/dist/utils/get-versioned-hash.test.d.ts +1 -0
- package/dist/utils/map-values-deep.d.ts +1 -0
- package/dist/utils/map-values-deep.js +29 -0
- package/dist/utils/map-values-deep.test.d.ts +1 -0
- package/dist/utils/sanitize-schema.d.ts +30 -0
- package/dist/utils/sanitize-schema.js +80 -0
- package/dist/utils/sanitize-schema.test.d.ts +1 -0
- package/dist/utils/track.js +3 -3
- package/dist/utils/url.js +2 -6
- package/dist/utils/url.test.d.ts +1 -0
- package/dist/utils/validate-diff.d.ts +7 -0
- package/dist/utils/validate-diff.js +114 -0
- package/dist/utils/validate-diff.test.d.ts +1 -0
- package/dist/utils/validate-query.js +1 -1
- package/dist/utils/validate-query.test.d.ts +1 -0
- package/dist/utils/validate-snapshot.d.ts +5 -0
- package/dist/utils/validate-snapshot.js +71 -0
- package/dist/utils/validate-snapshot.test.d.ts +1 -0
- package/dist/utils/with-timeout.d.ts +1 -0
- package/dist/utils/with-timeout.js +16 -0
- package/dist/webhooks.js +3 -2
- package/package.json +54 -53
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { DatabaseHelper } from '../types';
|
|
2
1
|
import { KNEX_TYPES } from '@directus/shared/constants';
|
|
2
|
+
import { Field, Relation, Type } from '@directus/shared/types';
|
|
3
3
|
import { Knex } from 'knex';
|
|
4
|
-
|
|
4
|
+
import { DatabaseClient } from '../../../types';
|
|
5
|
+
import { DatabaseHelper } from '../types';
|
|
5
6
|
export type Options = {
|
|
6
7
|
nullable?: boolean;
|
|
7
8
|
default?: any;
|
|
8
9
|
length?: number;
|
|
9
10
|
};
|
|
10
11
|
export declare abstract class SchemaHelper extends DatabaseHelper {
|
|
11
|
-
isOneOfClients(clients:
|
|
12
|
+
isOneOfClients(clients: DatabaseClient[]): boolean;
|
|
12
13
|
changeNullable(table: string, column: string, nullable: boolean): Promise<void>;
|
|
13
|
-
changeToType(table: string, column: string, type: typeof KNEX_TYPES[number], options?: Options): Promise<void>;
|
|
14
|
-
protected changeToTypeByCopy(table: string, column: string, type: typeof KNEX_TYPES[number], options: Options): Promise<void>;
|
|
14
|
+
changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
|
|
15
|
+
protected changeToTypeByCopy(table: string, column: string, type: (typeof KNEX_TYPES)[number], options: Options): Promise<void>;
|
|
15
16
|
preColumnChange(): Promise<boolean>;
|
|
16
17
|
postColumnChange(): Promise<void>;
|
|
18
|
+
preRelationChange(_relation: Partial<Relation>): void;
|
|
19
|
+
processFieldType(field: Field): Type;
|
|
17
20
|
constraintName(existingName: string): string;
|
|
18
21
|
applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void;
|
|
19
22
|
applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void;
|
|
@@ -21,4 +24,3 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
|
|
|
21
24
|
applyMultiRelationalSort(knex: Knex, dbQuery: Knex.QueryBuilder, table: string, primaryKey: string, orderByString: string, orderByFields: Knex.Raw[]): Knex.QueryBuilder;
|
|
22
25
|
formatUUID(uuid: string): string;
|
|
23
26
|
}
|
|
24
|
-
export {};
|
|
@@ -62,6 +62,12 @@ class SchemaHelper extends types_1.DatabaseHelper {
|
|
|
62
62
|
async postColumnChange() {
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
preRelationChange(_relation) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
processFieldType(field) {
|
|
69
|
+
return field.type;
|
|
70
|
+
}
|
|
65
71
|
constraintName(existingName) {
|
|
66
72
|
// most vendors allow for dropping/creating constraints with the same name
|
|
67
73
|
// reference issue #14873
|
|
@@ -83,7 +89,7 @@ class SchemaHelper extends types_1.DatabaseHelper {
|
|
|
83
89
|
return dbQuery;
|
|
84
90
|
}
|
|
85
91
|
formatUUID(uuid) {
|
|
86
|
-
return uuid; // no-op by
|
|
92
|
+
return uuid; // no-op by default
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
exports.SchemaHelper = SchemaHelper;
|
package/dist/database/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import SchemaInspector from '@directus/schema';
|
|
2
2
|
import { Knex } from 'knex';
|
|
3
|
+
import { DatabaseClient } from '../types';
|
|
3
4
|
export default function getDatabase(): Knex;
|
|
4
5
|
export declare function getSchemaInspector(): ReturnType<typeof SchemaInspector>;
|
|
5
6
|
/**
|
|
@@ -10,7 +11,7 @@ export declare function getSchemaInspector(): ReturnType<typeof SchemaInspector>
|
|
|
10
11
|
export declare function getDatabaseVersion(): string | null;
|
|
11
12
|
export declare function hasDatabaseConnection(database?: Knex): Promise<boolean>;
|
|
12
13
|
export declare function validateDatabaseConnection(database?: Knex): Promise<void>;
|
|
13
|
-
export declare function getDatabaseClient(database?: Knex):
|
|
14
|
+
export declare function getDatabaseClient(database?: Knex): DatabaseClient;
|
|
14
15
|
export declare function isInstalled(): Promise<boolean>;
|
|
15
16
|
export declare function validateMigrations(): Promise<boolean>;
|
|
16
17
|
/**
|
package/dist/database/run-ast.js
CHANGED
|
@@ -217,6 +217,8 @@ async function getDBQuery(schema, knex, table, fieldNodes, query) {
|
|
|
217
217
|
dbQuery.select(fieldNodes.map(preProcess));
|
|
218
218
|
}
|
|
219
219
|
if (sortRecords) {
|
|
220
|
+
// Clears the order if any, eg: from MSSQL offset
|
|
221
|
+
dbQuery.clear('order');
|
|
220
222
|
if (needsInnerQuery) {
|
|
221
223
|
let orderByString = '';
|
|
222
224
|
const orderByFields = [];
|
|
@@ -245,8 +247,6 @@ async function getDBQuery(schema, knex, table, fieldNodes, query) {
|
|
|
245
247
|
}
|
|
246
248
|
}
|
|
247
249
|
else {
|
|
248
|
-
// Clears the order if any, eg: from MSSQL offset
|
|
249
|
-
dbQuery.clear('order');
|
|
250
250
|
sortRecords.map((sortRecord) => {
|
|
251
251
|
if (sortRecord.column.includes('.')) {
|
|
252
252
|
const [alias, field] = sortRecord.column.split('.');
|
package/dist/env.js
CHANGED
|
@@ -28,6 +28,7 @@ const allowedEnvironmentVars = [
|
|
|
28
28
|
'SERVE_APP',
|
|
29
29
|
'GRAPHQL_INTROSPECTION',
|
|
30
30
|
'LOGGER_.+',
|
|
31
|
+
'ROBOTS_TXT',
|
|
31
32
|
// server
|
|
32
33
|
'SERVER_.+',
|
|
33
34
|
// database
|
|
@@ -105,6 +106,7 @@ const allowedEnvironmentVars = [
|
|
|
105
106
|
'ASSETS_TRANSFORM_MAX_CONCURRENT',
|
|
106
107
|
'ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION',
|
|
107
108
|
'ASSETS_TRANSFORM_MAX_OPERATIONS',
|
|
109
|
+
'ASSETS_TRANSFORM_TIMEOUT',
|
|
108
110
|
'ASSETS_CONTENT_SECURITY_POLICY',
|
|
109
111
|
'ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL',
|
|
110
112
|
// auth
|
|
@@ -143,8 +145,10 @@ const allowedEnvironmentVars = [
|
|
|
143
145
|
'AUTH_.+_IDP.+',
|
|
144
146
|
'AUTH_.+_SP.+',
|
|
145
147
|
// extensions
|
|
148
|
+
'PACKAGE_FILE_LOCATION',
|
|
146
149
|
'EXTENSIONS_PATH',
|
|
147
150
|
'EXTENSIONS_AUTO_RELOAD',
|
|
151
|
+
'EXTENSIONS_CACHE_TTL',
|
|
148
152
|
// messenger
|
|
149
153
|
'MESSENGER_STORE',
|
|
150
154
|
'MESSENGER_NAMESPACE',
|
|
@@ -193,6 +197,7 @@ const defaults = {
|
|
|
193
197
|
PUBLIC_URL: '/',
|
|
194
198
|
MAX_PAYLOAD_SIZE: '1mb',
|
|
195
199
|
MAX_RELATIONAL_DEPTH: 10,
|
|
200
|
+
ROBOTS_TXT: 'User-agent: *\nDisallow: /',
|
|
196
201
|
DB_EXCLUDE_TABLES: 'spatial_ref_sys,sysdiagrams',
|
|
197
202
|
STORAGE_LOCATIONS: 'local',
|
|
198
203
|
STORAGE_LOCAL_DRIVER: 'local',
|
|
@@ -226,9 +231,10 @@ const defaults = {
|
|
|
226
231
|
CACHE_VALUE_MAX_SIZE: false,
|
|
227
232
|
AUTH_PROVIDERS: '',
|
|
228
233
|
AUTH_DISABLE_DEFAULT: false,
|
|
234
|
+
PACKAGE_FILE_LOCATION: '.',
|
|
229
235
|
EXTENSIONS_PATH: './extensions',
|
|
230
236
|
EXTENSIONS_AUTO_RELOAD: false,
|
|
231
|
-
EMAIL_FROM: 'no-reply@
|
|
237
|
+
EMAIL_FROM: 'no-reply@example.com',
|
|
232
238
|
EMAIL_VERIFY_SETUP: true,
|
|
233
239
|
EMAIL_TRANSPORT: 'sendmail',
|
|
234
240
|
EMAIL_SENDMAIL_NEW_LINE: 'unix',
|
|
@@ -238,10 +244,11 @@ const defaults = {
|
|
|
238
244
|
ASSETS_TRANSFORM_MAX_CONCURRENT: 1,
|
|
239
245
|
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 6000,
|
|
240
246
|
ASSETS_TRANSFORM_MAX_OPERATIONS: 5,
|
|
247
|
+
ASSETS_TRANSFORM_TIMEOUT: '7500ms',
|
|
241
248
|
ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL: 'warning',
|
|
242
249
|
IP_TRUST_PROXY: true,
|
|
243
250
|
IP_CUSTOM_HEADER: false,
|
|
244
|
-
IMPORT_IP_DENY_LIST: '0.0.0.0',
|
|
251
|
+
IMPORT_IP_DENY_LIST: ['0.0.0.0', '169.254.169.254'],
|
|
245
252
|
SERVE_APP: true,
|
|
246
253
|
RELATIONAL_BATCH_SIZE: 25000,
|
|
247
254
|
EXPORT_BATCH_SIZE: 5000,
|
package/dist/extensions.js
CHANGED
|
@@ -246,7 +246,7 @@ class ExtensionManager {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
async getExtensions() {
|
|
249
|
-
const packageExtensions = await (0, node_1.getPackageExtensions)(
|
|
249
|
+
const packageExtensions = await (0, node_1.getPackageExtensions)(env_1.default.PACKAGE_FILE_LOCATION);
|
|
250
250
|
const localPackageExtensions = await (0, node_1.resolvePackageExtensions)(env_1.default.EXTENSIONS_PATH);
|
|
251
251
|
const localExtensions = await (0, node_1.getLocalExtensions)(env_1.default.EXTENSIONS_PATH);
|
|
252
252
|
return [...packageExtensions, ...localPackageExtensions, ...localExtensions].filter((extension) => env_1.default.SERVE_APP || constants_1.APP_EXTENSION_TYPES.includes(extension.type) === false);
|
package/dist/flows.js
CHANGED
|
@@ -47,6 +47,7 @@ const revisions_1 = require("./services/revisions");
|
|
|
47
47
|
const construct_flow_tree_1 = require("./utils/construct-flow-tree");
|
|
48
48
|
const get_schema_1 = require("./utils/get-schema");
|
|
49
49
|
const job_queue_1 = require("./utils/job-queue");
|
|
50
|
+
const map_values_deep_1 = require("./utils/map-values-deep");
|
|
50
51
|
let flowManager;
|
|
51
52
|
const redactLogs = (0, fast_redact_1.default)({
|
|
52
53
|
censor: '--redacted--',
|
|
@@ -130,23 +131,26 @@ class FlowManager {
|
|
|
130
131
|
const flowTrees = flows.map((flow) => (0, construct_flow_tree_1.constructFlowTree)(flow));
|
|
131
132
|
for (const flow of flowTrees) {
|
|
132
133
|
if (flow.trigger === 'event') {
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
let events = [];
|
|
135
|
+
if ((_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope) {
|
|
136
|
+
events = (0, utils_1.toArray)(flow.options.scope)
|
|
135
137
|
.map((scope) => {
|
|
136
|
-
var _a
|
|
138
|
+
var _a;
|
|
137
139
|
if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
|
|
138
|
-
|
|
140
|
+
if (!((_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections))
|
|
141
|
+
return [];
|
|
142
|
+
return (0, utils_1.toArray)(flow.options.collections).map((collection) => {
|
|
139
143
|
if (collection.startsWith('directus_')) {
|
|
140
144
|
const action = scope.split('.')[1];
|
|
141
145
|
return collection.substring(9) + '.' + action;
|
|
142
146
|
}
|
|
143
147
|
return `${collection}.${scope}`;
|
|
144
|
-
})
|
|
148
|
+
});
|
|
145
149
|
}
|
|
146
150
|
return scope;
|
|
147
151
|
})
|
|
148
|
-
.flat()
|
|
149
|
-
|
|
152
|
+
.flat();
|
|
153
|
+
}
|
|
150
154
|
if (flow.options.type === 'filter') {
|
|
151
155
|
const handler = (payload, meta, context) => this.executeFlow(flow, { payload, ...meta }, {
|
|
152
156
|
accountability: context.accountability,
|
|
@@ -329,7 +333,7 @@ class FlowManager {
|
|
|
329
333
|
const handler = this.operations[operation.type];
|
|
330
334
|
const options = (0, utils_1.applyOptionsData)(operation.options, keyedData);
|
|
331
335
|
try {
|
|
332
|
-
|
|
336
|
+
let result = await handler(options, {
|
|
333
337
|
services,
|
|
334
338
|
exceptions: { ...exceptions, ...sharedExceptions },
|
|
335
339
|
env: env_1.default,
|
|
@@ -340,6 +344,11 @@ class FlowManager {
|
|
|
340
344
|
accountability: null,
|
|
341
345
|
...context,
|
|
342
346
|
});
|
|
347
|
+
// JSON structures don't allow for undefined values, so we need to replace them with null
|
|
348
|
+
// Otherwise the applyOptionsData function will not work correctly on the next operation
|
|
349
|
+
if (typeof result === 'object' && result !== null) {
|
|
350
|
+
result = (0, map_values_deep_1.mapValuesDeep)(result, (_, value) => (value === undefined ? null : value));
|
|
351
|
+
}
|
|
343
352
|
return { successor: operation.resolve, status: 'resolve', data: result !== null && result !== void 0 ? result : null, options };
|
|
344
353
|
}
|
|
345
354
|
catch (error) {
|
package/dist/middleware/cache.js
CHANGED
|
@@ -45,8 +45,8 @@ const checkCacheMiddleware = (0, async_handler_1.default)(async (req, res, next)
|
|
|
45
45
|
res.setHeader(`${env_1.default.CACHE_STATUS_HEADER}`, 'MISS');
|
|
46
46
|
return next();
|
|
47
47
|
}
|
|
48
|
-
const cacheTTL = cacheExpiryDate ? cacheExpiryDate - Date.now() :
|
|
49
|
-
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, cacheTTL));
|
|
48
|
+
const cacheTTL = cacheExpiryDate ? cacheExpiryDate - Date.now() : undefined;
|
|
49
|
+
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, cacheTTL, true, true));
|
|
50
50
|
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
51
51
|
if (env_1.default.CACHE_STATUS_HEADER)
|
|
52
52
|
res.setHeader(`${env_1.default.CACHE_STATUS_HEADER}`, 'HIT');
|
|
@@ -4,19 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.respond = void 0;
|
|
7
|
-
const
|
|
7
|
+
const bytes_1 = require("bytes");
|
|
8
8
|
const cache_1 = require("../cache");
|
|
9
9
|
const env_1 = __importDefault(require("../env"));
|
|
10
|
-
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
11
|
-
const get_cache_key_1 = require("../utils/get-cache-key");
|
|
12
|
-
const get_cache_headers_1 = require("../utils/get-cache-headers");
|
|
13
10
|
const logger_1 = __importDefault(require("../logger"));
|
|
14
11
|
const services_1 = require("../services");
|
|
12
|
+
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
13
|
+
const get_cache_headers_1 = require("../utils/get-cache-headers");
|
|
14
|
+
const get_cache_key_1 = require("../utils/get-cache-key");
|
|
15
15
|
const get_date_formatted_1 = require("../utils/get-date-formatted");
|
|
16
|
+
const get_milliseconds_1 = require("../utils/get-milliseconds");
|
|
16
17
|
const get_string_byte_size_1 = require("../utils/get-string-byte-size");
|
|
17
|
-
const bytes_1 = require("bytes");
|
|
18
18
|
exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
19
|
-
var _a, _b, _c, _d;
|
|
19
|
+
var _a, _b, _c, _d, _e;
|
|
20
20
|
const { cache } = (0, cache_1.getCache)();
|
|
21
21
|
let exceedsMaxSize = false;
|
|
22
22
|
if (env_1.default.CACHE_VALUE_MAX_SIZE !== false) {
|
|
@@ -32,13 +32,13 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
|
32
32
|
exceedsMaxSize === false) {
|
|
33
33
|
const key = (0, get_cache_key_1.getCacheKey)(req);
|
|
34
34
|
try {
|
|
35
|
-
await (0, cache_1.setCacheValue)(cache, key, res.locals.payload, (0,
|
|
36
|
-
await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0,
|
|
35
|
+
await (0, cache_1.setCacheValue)(cache, key, res.locals.payload, (0, get_milliseconds_1.getMilliseconds)(env_1.default.CACHE_TTL));
|
|
36
|
+
await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default.CACHE_TTL, 0) });
|
|
37
37
|
}
|
|
38
38
|
catch (err) {
|
|
39
39
|
logger_1.default.warn(err, `[cache] Couldn't set key ${key}. ${err}`);
|
|
40
40
|
}
|
|
41
|
-
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, (0,
|
|
41
|
+
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, (0, get_milliseconds_1.getMilliseconds)(env_1.default.CACHE_TTL), true, true));
|
|
42
42
|
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
@@ -71,6 +71,11 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
|
71
71
|
res.set('Content-Type', 'text/csv');
|
|
72
72
|
return res.status(200).send(exportService.transform((_d = res.locals.payload) === null || _d === void 0 ? void 0 : _d.data, 'csv'));
|
|
73
73
|
}
|
|
74
|
+
if (req.sanitizedQuery.export === 'yaml') {
|
|
75
|
+
res.attachment(`${filename}.yaml`);
|
|
76
|
+
res.set('Content-Type', 'text/yaml');
|
|
77
|
+
return res.status(200).send(exportService.transform((_e = res.locals.payload) === null || _e === void 0 ? void 0 : _e.data, 'yaml'));
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
80
|
if (Buffer.isBuffer(res.locals.payload)) {
|
|
76
81
|
return res.end(res.locals.payload);
|
|
@@ -5,11 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const utils_1 = require("@directus/shared/utils");
|
|
7
7
|
const encodeurl_1 = __importDefault(require("encodeurl"));
|
|
8
|
+
const index_1 = require("../../request/index");
|
|
8
9
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
9
10
|
id: 'request',
|
|
10
11
|
handler: async ({ url, method, body, headers }) => {
|
|
11
12
|
var _a;
|
|
12
|
-
const axios = (await import('axios')).default;
|
|
13
13
|
const customHeaders = (_a = headers === null || headers === void 0 ? void 0 : headers.reduce((acc, { header, value }) => {
|
|
14
14
|
acc[header] = value;
|
|
15
15
|
return acc;
|
|
@@ -17,6 +17,7 @@ exports.default = (0, utils_1.defineOperationApi)({
|
|
|
17
17
|
if (!customHeaders['Content-Type'] && isValidJSON(body)) {
|
|
18
18
|
customHeaders['Content-Type'] = 'application/json';
|
|
19
19
|
}
|
|
20
|
+
const axios = await (0, index_1.getAxios)();
|
|
20
21
|
const result = await axios({
|
|
21
22
|
url: (0, encodeurl_1.default)(url),
|
|
22
23
|
method,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
type Options = {
|
|
2
2
|
flow: string;
|
|
3
3
|
payload?: Record<string, any> | Record<string, any>[] | string | null;
|
|
4
|
+
iterationMode?: 'serial' | 'batch' | 'parallel';
|
|
5
|
+
batchSize?: number;
|
|
4
6
|
};
|
|
5
7
|
declare const _default: import("@directus/shared/types").OperationApiConfig<Options>;
|
|
6
8
|
export default _default;
|
|
@@ -5,19 +5,36 @@ const lodash_1 = require("lodash");
|
|
|
5
5
|
const flows_1 = require("../../flows");
|
|
6
6
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
7
7
|
id: 'trigger',
|
|
8
|
-
handler: async ({ flow, payload }, context) => {
|
|
8
|
+
handler: async ({ flow, payload, iterationMode, batchSize }, context) => {
|
|
9
9
|
var _a;
|
|
10
10
|
const flowManager = (0, flows_1.getFlowManager)();
|
|
11
11
|
const payloadObject = (_a = (0, utils_1.optionToObject)(payload)) !== null && _a !== void 0 ? _a : null;
|
|
12
|
-
let result;
|
|
13
12
|
if (Array.isArray(payloadObject)) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (iterationMode === 'serial') {
|
|
14
|
+
const result = [];
|
|
15
|
+
for (const payload of payloadObject) {
|
|
16
|
+
result.push(await flowManager.runOperationFlow(flow, payload, (0, lodash_1.omit)(context, 'data')));
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
if (iterationMode === 'batch') {
|
|
21
|
+
const size = batchSize !== null && batchSize !== void 0 ? batchSize : 10;
|
|
22
|
+
const result = [];
|
|
23
|
+
for (let i = 0; i < payloadObject.length; i += size) {
|
|
24
|
+
const batch = payloadObject.slice(i, i + size);
|
|
25
|
+
const batchResults = await Promise.all(batch.map((payload) => {
|
|
26
|
+
return flowManager.runOperationFlow(flow, payload, (0, lodash_1.omit)(context, 'data'));
|
|
27
|
+
}));
|
|
28
|
+
result.push(...batchResults);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
if (iterationMode === 'parallel' || !iterationMode) {
|
|
33
|
+
return await Promise.all(payloadObject.map((payload) => {
|
|
34
|
+
return flowManager.runOperationFlow(flow, payload, (0, lodash_1.omit)(context, 'data'));
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
17
37
|
}
|
|
18
|
-
|
|
19
|
-
result = await flowManager.runOperationFlow(flow, payloadObject, (0, lodash_1.omit)(context, 'data'));
|
|
20
|
-
}
|
|
21
|
-
return result;
|
|
38
|
+
return await flowManager.runOperationFlow(flow, payloadObject, (0, lodash_1.omit)(context, 'data'));
|
|
22
39
|
},
|
|
23
40
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAxios = exports._cache = void 0;
|
|
4
|
+
const request_interceptor_1 = require("./request-interceptor");
|
|
5
|
+
const response_interceptor_1 = require("./response-interceptor");
|
|
6
|
+
exports._cache = {
|
|
7
|
+
axiosInstance: null,
|
|
8
|
+
};
|
|
9
|
+
async function getAxios() {
|
|
10
|
+
if (!exports._cache.axiosInstance) {
|
|
11
|
+
const axios = (await import('axios')).default;
|
|
12
|
+
exports._cache.axiosInstance = axios.create();
|
|
13
|
+
exports._cache.axiosInstance.interceptors.request.use(request_interceptor_1.requestInterceptor);
|
|
14
|
+
exports._cache.axiosInstance.interceptors.response.use(response_interceptor_1.responseInterceptor);
|
|
15
|
+
}
|
|
16
|
+
return exports._cache.axiosInstance;
|
|
17
|
+
}
|
|
18
|
+
exports.getAxios = getAxios;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.requestInterceptor = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const promises_1 = require("node:dns/promises");
|
|
9
|
+
const node_net_1 = require("node:net");
|
|
10
|
+
const node_url_1 = require("node:url");
|
|
11
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
12
|
+
const validate_ip_1 = require("./validate-ip");
|
|
13
|
+
const requestInterceptor = async (config) => {
|
|
14
|
+
const uri = axios_1.default.getUri(config);
|
|
15
|
+
const { hostname } = new node_url_1.URL(uri);
|
|
16
|
+
let ip;
|
|
17
|
+
if ((0, node_net_1.isIP)(hostname) === 0) {
|
|
18
|
+
try {
|
|
19
|
+
const dns = await (0, promises_1.lookup)(hostname);
|
|
20
|
+
ip = dns.address;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
logger_1.default.warn(err, `Couldn't lookup the DNS for url "${uri}"`);
|
|
24
|
+
throw new Error(`Requested URL "${uri}" resolves to a denied IP address`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
ip = hostname;
|
|
29
|
+
}
|
|
30
|
+
await (0, validate_ip_1.validateIP)(ip, uri);
|
|
31
|
+
return config;
|
|
32
|
+
};
|
|
33
|
+
exports.requestInterceptor = requestInterceptor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.responseInterceptor = void 0;
|
|
4
|
+
const validate_ip_1 = require("./validate-ip");
|
|
5
|
+
const responseInterceptor = async (config) => {
|
|
6
|
+
await (0, validate_ip_1.validateIP)(config.request.socket.remoteAddress, config.request.url);
|
|
7
|
+
return config;
|
|
8
|
+
};
|
|
9
|
+
exports.responseInterceptor = responseInterceptor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateIP: (ip: string, url: string) => Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateIP = void 0;
|
|
7
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
8
|
+
const env_1 = require("../env");
|
|
9
|
+
const validateIP = async (ip, url) => {
|
|
10
|
+
const env = (0, env_1.getEnv)();
|
|
11
|
+
if (env.IMPORT_IP_DENY_LIST.includes(ip)) {
|
|
12
|
+
throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
|
|
13
|
+
}
|
|
14
|
+
if (env.IMPORT_IP_DENY_LIST.includes('0.0.0.0')) {
|
|
15
|
+
const networkInterfaces = node_os_1.default.networkInterfaces();
|
|
16
|
+
for (const networkInfo of Object.values(networkInterfaces)) {
|
|
17
|
+
if (!networkInfo)
|
|
18
|
+
continue;
|
|
19
|
+
for (const info of networkInfo) {
|
|
20
|
+
if (info.address === ip) {
|
|
21
|
+
throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.validateIP = validateIP;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { Range, Stat } from '@directus/storage';
|
|
3
3
|
import { Accountability } from '@directus/shared/types';
|
|
4
|
-
import type { Readable } from 'node:stream';
|
|
5
4
|
import { Knex } from 'knex';
|
|
5
|
+
import type { Readable } from 'node:stream';
|
|
6
6
|
import { AbstractServiceOptions, TransformationParams, TransformationPreset } from '../types';
|
|
7
7
|
import { AuthorizationService } from './authorization';
|
|
8
8
|
export declare class AssetsService {
|
package/dist/services/assets.js
CHANGED
|
@@ -38,7 +38,9 @@ const env_1 = __importDefault(require("../env"));
|
|
|
38
38
|
const exceptions_1 = require("../exceptions");
|
|
39
39
|
const logger_1 = __importDefault(require("../logger"));
|
|
40
40
|
const storage_1 = require("../storage");
|
|
41
|
+
const get_milliseconds_1 = require("../utils/get-milliseconds");
|
|
41
42
|
const TransformationUtils = __importStar(require("../utils/transformations"));
|
|
43
|
+
const with_timeout_1 = require("../utils/with-timeout");
|
|
42
44
|
const authorization_1 = require("./authorization");
|
|
43
45
|
sharp_1.default.concurrency(1);
|
|
44
46
|
// Note: don't put this in the service. The service can be initialized in multiple places, but they
|
|
@@ -135,13 +137,20 @@ class AssetsService {
|
|
|
135
137
|
height > env_1.default.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION) {
|
|
136
138
|
throw new exceptions_1.IllegalAssetTransformation(`Image is too large to be transformed, or image size couldn't be determined.`);
|
|
137
139
|
}
|
|
138
|
-
return await semaphore.runExclusive(async () => {
|
|
140
|
+
return await semaphore.runExclusive((0, with_timeout_1.withTimeout)(async () => {
|
|
139
141
|
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
|
|
140
142
|
const transformer = (0, sharp_1.default)({
|
|
141
143
|
limitInputPixels: Math.pow(env_1.default.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION, 2),
|
|
142
144
|
sequentialRead: true,
|
|
143
145
|
failOn: env_1.default.ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL,
|
|
144
146
|
});
|
|
147
|
+
// This "duplicate" timeout is required as the overall timeout applies to the
|
|
148
|
+
// whole promise block, rather than just Sharp. This ensures that sharp itself
|
|
149
|
+
// doesn't hang indefinitely, even when the outer function scope is already
|
|
150
|
+
// skipped.
|
|
151
|
+
transformer.timeout({
|
|
152
|
+
seconds: Math.min(3600, Math.round((0, get_milliseconds_1.getMilliseconds)(env_1.default.ASSETS_TRANSFORM_TIMEOUT) * 1000)),
|
|
153
|
+
});
|
|
145
154
|
if (transforms.find((transform) => transform[0] === 'rotate') === undefined)
|
|
146
155
|
transformer.rotate();
|
|
147
156
|
transforms.forEach(([method, ...args]) => transformer[method].apply(transformer, args));
|
|
@@ -155,7 +164,7 @@ class AssetsService {
|
|
|
155
164
|
stat: await storage.location(file.storage).stat(assetFilename),
|
|
156
165
|
file,
|
|
157
166
|
};
|
|
158
|
-
});
|
|
167
|
+
}, (0, get_milliseconds_1.getMilliseconds)(env_1.default.ASSETS_TRANSFORM_TIMEOUT), new exceptions_1.IllegalAssetTransformation(`Image took too long to transform`)));
|
|
159
168
|
}
|
|
160
169
|
else {
|
|
161
170
|
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
|
|
@@ -7,7 +7,6 @@ exports.AuthenticationService = void 0;
|
|
|
7
7
|
const types_1 = require("@directus/shared/types");
|
|
8
8
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
9
9
|
const lodash_1 = require("lodash");
|
|
10
|
-
const ms_1 = __importDefault(require("ms"));
|
|
11
10
|
const perf_hooks_1 = require("perf_hooks");
|
|
12
11
|
const auth_1 = require("../auth");
|
|
13
12
|
const constants_1 = require("../constants");
|
|
@@ -16,6 +15,7 @@ const emitter_1 = __importDefault(require("../emitter"));
|
|
|
16
15
|
const env_1 = __importDefault(require("../env"));
|
|
17
16
|
const exceptions_1 = require("../exceptions");
|
|
18
17
|
const rate_limiter_1 = require("../rate-limiter");
|
|
18
|
+
const get_milliseconds_1 = require("../utils/get-milliseconds");
|
|
19
19
|
const stall_1 = require("../utils/stall");
|
|
20
20
|
const activity_1 = require("./activity");
|
|
21
21
|
const settings_1 = require("./settings");
|
|
@@ -152,7 +152,7 @@ class AuthenticationService {
|
|
|
152
152
|
issuer: 'directus',
|
|
153
153
|
});
|
|
154
154
|
const refreshToken = nanoid(64);
|
|
155
|
-
const refreshTokenExpiration = new Date(Date.now() + (0,
|
|
155
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default.REFRESH_TOKEN_TTL, 0));
|
|
156
156
|
await this.knex('directus_sessions').insert({
|
|
157
157
|
token: refreshToken,
|
|
158
158
|
user: user.id,
|
|
@@ -182,7 +182,7 @@ class AuthenticationService {
|
|
|
182
182
|
return {
|
|
183
183
|
accessToken,
|
|
184
184
|
refreshToken,
|
|
185
|
-
expires: (0,
|
|
185
|
+
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default.ACCESS_TOKEN_TTL),
|
|
186
186
|
id: user.id,
|
|
187
187
|
};
|
|
188
188
|
}
|
|
@@ -282,7 +282,7 @@ class AuthenticationService {
|
|
|
282
282
|
issuer: 'directus',
|
|
283
283
|
});
|
|
284
284
|
const newRefreshToken = nanoid(64);
|
|
285
|
-
const refreshTokenExpiration = new Date(Date.now() + (0,
|
|
285
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default.REFRESH_TOKEN_TTL, 0));
|
|
286
286
|
await this.knex('directus_sessions')
|
|
287
287
|
.update({
|
|
288
288
|
token: newRefreshToken,
|
|
@@ -295,7 +295,7 @@ class AuthenticationService {
|
|
|
295
295
|
return {
|
|
296
296
|
accessToken,
|
|
297
297
|
refreshToken: newRefreshToken,
|
|
298
|
-
expires: (0,
|
|
298
|
+
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default.ACCESS_TOKEN_TTL),
|
|
299
299
|
id: record.user_id,
|
|
300
300
|
};
|
|
301
301
|
}
|
package/dist/services/fields.js
CHANGED
|
@@ -167,6 +167,7 @@ class FieldsService {
|
|
|
167
167
|
else if ((_d = (_c = field.meta) === null || _c === void 0 ? void 0 : _c.special) === null || _d === void 0 ? void 0 : _d.includes('cast-datetime')) {
|
|
168
168
|
field.type = 'dateTime';
|
|
169
169
|
}
|
|
170
|
+
field.type = this.helpers.schema.processFieldType(field);
|
|
170
171
|
}
|
|
171
172
|
return result;
|
|
172
173
|
}
|