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.
Files changed (112) hide show
  1. package/dist/app.js +5 -4
  2. package/dist/auth/drivers/ldap.d.ts +2 -2
  3. package/dist/auth/drivers/ldap.js +8 -8
  4. package/dist/auth/drivers/oauth2.js +2 -2
  5. package/dist/auth/drivers/openid.js +2 -2
  6. package/dist/cache.js +4 -4
  7. package/dist/cli/commands/schema/apply.js +19 -17
  8. package/dist/cli/utils/create-db-connection.d.ts +2 -1
  9. package/dist/cli/utils/create-env/env-stub.liquid +1 -1
  10. package/dist/cli/utils/drivers.d.ts +3 -9
  11. package/dist/constants.d.ts +2 -8
  12. package/dist/constants.js +3 -7
  13. package/dist/controllers/assets.js +5 -5
  14. package/dist/controllers/extensions.js +7 -7
  15. package/dist/controllers/files.js +1 -1
  16. package/dist/controllers/graphql.js +8 -0
  17. package/dist/controllers/schema.d.ts +2 -0
  18. package/dist/controllers/schema.js +98 -0
  19. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -1
  20. package/dist/database/helpers/schema/dialects/oracle.d.ts +4 -1
  21. package/dist/database/helpers/schema/dialects/oracle.js +25 -0
  22. package/dist/database/helpers/schema/types.d.ts +8 -6
  23. package/dist/database/helpers/schema/types.js +7 -1
  24. package/dist/database/index.d.ts +2 -1
  25. package/dist/database/run-ast.js +2 -2
  26. package/dist/env.js +9 -2
  27. package/dist/extensions.js +1 -1
  28. package/dist/flows.js +17 -8
  29. package/dist/middleware/cache.js +2 -2
  30. package/dist/middleware/respond.js +14 -9
  31. package/dist/operations/request/index.js +2 -1
  32. package/dist/operations/trigger/index.d.ts +2 -0
  33. package/dist/operations/trigger/index.js +26 -9
  34. package/dist/request/index.d.ts +5 -0
  35. package/dist/request/index.js +18 -0
  36. package/dist/request/index.test.d.ts +1 -0
  37. package/dist/request/request-interceptor.d.ts +2 -0
  38. package/dist/request/request-interceptor.js +33 -0
  39. package/dist/request/request-interceptor.test.d.ts +1 -0
  40. package/dist/request/response-interceptor.d.ts +2 -0
  41. package/dist/request/response-interceptor.js +9 -0
  42. package/dist/request/response-interceptor.test.d.ts +1 -0
  43. package/dist/request/validate-ip.d.ts +1 -0
  44. package/dist/request/validate-ip.js +27 -0
  45. package/dist/request/validate-ip.test.d.ts +1 -0
  46. package/dist/services/assets.d.ts +1 -1
  47. package/dist/services/assets.js +11 -2
  48. package/dist/services/authentication.js +5 -5
  49. package/dist/services/fields.js +1 -0
  50. package/dist/services/files.js +44 -88
  51. package/dist/services/graphql/index.js +14 -8
  52. package/dist/services/graphql/utils/process-error.js +22 -9
  53. package/dist/services/import-export.d.ts +4 -2
  54. package/dist/services/import-export.js +17 -3
  55. package/dist/services/import-export.test.d.ts +1 -0
  56. package/dist/services/index.d.ts +1 -0
  57. package/dist/services/index.js +1 -0
  58. package/dist/services/items.js +34 -15
  59. package/dist/services/relations.js +2 -0
  60. package/dist/services/roles.js +32 -11
  61. package/dist/services/schema.d.ts +15 -0
  62. package/dist/services/schema.js +58 -0
  63. package/dist/services/schema.test.d.ts +1 -0
  64. package/dist/services/shares.d.ts +2 -2
  65. package/dist/services/shares.js +9 -9
  66. package/dist/services/users.js +74 -47
  67. package/dist/types/assets.d.ts +1 -1
  68. package/dist/types/database.d.ts +3 -0
  69. package/dist/types/database.js +4 -0
  70. package/dist/types/index.d.ts +1 -0
  71. package/dist/types/index.js +1 -0
  72. package/dist/types/items.d.ts +5 -0
  73. package/dist/types/snapshot.d.ts +22 -0
  74. package/dist/types/snapshot.js +14 -0
  75. package/dist/utils/apply-diff.d.ts +9 -0
  76. package/dist/utils/apply-diff.js +259 -0
  77. package/dist/utils/apply-diff.test.d.ts +1 -0
  78. package/dist/utils/apply-query.js +8 -6
  79. package/dist/utils/apply-snapshot.d.ts +1 -3
  80. package/dist/utils/apply-snapshot.js +4 -234
  81. package/dist/utils/get-cache-headers.d.ts +3 -1
  82. package/dist/utils/get-cache-headers.js +20 -19
  83. package/dist/utils/get-cache-headers.test.d.ts +1 -0
  84. package/dist/utils/get-milliseconds.d.ts +4 -0
  85. package/dist/utils/get-milliseconds.js +15 -0
  86. package/dist/utils/get-milliseconds.test.d.ts +1 -0
  87. package/dist/utils/get-snapshot-diff.js +11 -7
  88. package/dist/utils/get-snapshot.js +29 -6
  89. package/dist/utils/get-versioned-hash.d.ts +1 -0
  90. package/dist/utils/get-versioned-hash.js +12 -0
  91. package/dist/utils/get-versioned-hash.test.d.ts +1 -0
  92. package/dist/utils/map-values-deep.d.ts +1 -0
  93. package/dist/utils/map-values-deep.js +29 -0
  94. package/dist/utils/map-values-deep.test.d.ts +1 -0
  95. package/dist/utils/sanitize-schema.d.ts +30 -0
  96. package/dist/utils/sanitize-schema.js +80 -0
  97. package/dist/utils/sanitize-schema.test.d.ts +1 -0
  98. package/dist/utils/track.js +3 -3
  99. package/dist/utils/url.js +2 -6
  100. package/dist/utils/url.test.d.ts +1 -0
  101. package/dist/utils/validate-diff.d.ts +7 -0
  102. package/dist/utils/validate-diff.js +114 -0
  103. package/dist/utils/validate-diff.test.d.ts +1 -0
  104. package/dist/utils/validate-query.js +1 -1
  105. package/dist/utils/validate-query.test.d.ts +1 -0
  106. package/dist/utils/validate-snapshot.d.ts +5 -0
  107. package/dist/utils/validate-snapshot.js +71 -0
  108. package/dist/utils/validate-snapshot.test.d.ts +1 -0
  109. package/dist/utils/with-timeout.d.ts +1 -0
  110. package/dist/utils/with-timeout.js +16 -0
  111. package/dist/webhooks.js +3 -2
  112. 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
- type Clients = 'mysql' | 'postgres' | 'cockroachdb' | 'sqlite' | 'oracle' | 'mssql' | 'redshift';
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: Clients[]): boolean;
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 defaut
92
+ return uuid; // no-op by default
87
93
  }
88
94
  }
89
95
  exports.SchemaHelper = SchemaHelper;
@@ -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): 'mysql' | 'postgres' | 'cockroachdb' | 'sqlite' | 'oracle' | 'mssql' | 'redshift';
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
  /**
@@ -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@directus.io',
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,
@@ -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
- const events = ((_a = flow.options) === null || _a === void 0 ? void 0 : _a.scope)
134
- ? (0, utils_1.toArray)(flow.options.scope)
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, _b, _c;
138
+ var _a;
137
139
  if (['items.create', 'items.update', 'items.delete'].includes(scope)) {
138
- return ((_c = (_b = (_a = flow.options) === null || _a === void 0 ? void 0 : _a.collections) === null || _b === void 0 ? void 0 : _b.map((collection) => {
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
- })) !== null && _c !== void 0 ? _c : []);
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
- const result = await handler(options, {
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) {
@@ -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() : null;
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 ms_1 = __importDefault(require("ms"));
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, ms_1.default)(env_1.default.CACHE_TTL));
36
- await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0, ms_1.default)(env_1.default.CACHE_TTL) });
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, ms_1.default)(env_1.default.CACHE_TTL)));
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
- result = await Promise.all(payloadObject.map((payload) => {
15
- return flowManager.runOperationFlow(flow, payload, (0, lodash_1.omit)(context, 'data'));
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
- else {
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,5 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ export declare const _cache: {
3
+ axiosInstance: AxiosInstance | null;
4
+ };
5
+ export declare function getAxios(): Promise<AxiosInstance>;
@@ -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,2 @@
1
+ import type { InternalAxiosRequestConfig } from 'axios';
2
+ export declare const requestInterceptor: (config: InternalAxiosRequestConfig) => Promise<InternalAxiosRequestConfig<any>>;
@@ -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,2 @@
1
+ import type { AxiosResponse } from 'axios';
2
+ export declare const responseInterceptor: (config: AxiosResponse<any, any>) => Promise<AxiosResponse<any, any>>;
@@ -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 {
@@ -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, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL));
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, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
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, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL));
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, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
298
+ expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default.ACCESS_TOKEN_TTL),
299
299
  id: record.user_id,
300
300
  };
301
301
  }
@@ -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
  }