@directus/api 32.2.0 → 33.0.0

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 (201) hide show
  1. package/dist/ai/chat/lib/create-ui-stream.d.ts +1 -1
  2. package/dist/ai/tools/assets/index.js +1 -1
  3. package/dist/ai/tools/collections/index.js +2 -2
  4. package/dist/ai/tools/fields/index.js +2 -2
  5. package/dist/ai/tools/files/index.js +1 -1
  6. package/dist/ai/tools/flows/index.js +1 -1
  7. package/dist/ai/tools/folders/index.js +1 -1
  8. package/dist/ai/tools/items/index.js +2 -2
  9. package/dist/ai/tools/relations/index.js +1 -1
  10. package/dist/ai/tools/trigger-flow/index.js +1 -1
  11. package/dist/app.js +8 -8
  12. package/dist/auth/drivers/ldap.js +2 -2
  13. package/dist/auth/drivers/local.js +1 -1
  14. package/dist/auth/drivers/oauth2.d.ts +1 -2
  15. package/dist/auth/drivers/oauth2.js +22 -17
  16. package/dist/auth/drivers/openid.d.ts +1 -2
  17. package/dist/auth/drivers/openid.js +18 -13
  18. package/dist/auth/drivers/saml.js +3 -3
  19. package/dist/auth/utils/generate-callback-url.d.ts +11 -0
  20. package/dist/auth/utils/generate-callback-url.js +40 -0
  21. package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
  22. package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
  23. package/dist/cache.js +2 -2
  24. package/dist/cli/commands/bootstrap/index.js +2 -2
  25. package/dist/cli/commands/database/install.js +1 -1
  26. package/dist/cli/commands/database/migrate.js +1 -1
  27. package/dist/cli/commands/init/index.js +2 -2
  28. package/dist/cli/commands/roles/create.js +4 -4
  29. package/dist/cli/commands/schema/apply.js +3 -3
  30. package/dist/cli/commands/schema/snapshot.js +1 -1
  31. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  32. package/dist/cli/utils/create-db-connection.js +1 -1
  33. package/dist/cli/utils/create-env/index.js +1 -1
  34. package/dist/constants.d.ts +7 -3
  35. package/dist/constants.js +7 -3
  36. package/dist/controllers/access.js +1 -1
  37. package/dist/controllers/assets.js +1 -1
  38. package/dist/controllers/extensions.js +1 -1
  39. package/dist/controllers/fields.js +2 -2
  40. package/dist/controllers/files.js +1 -1
  41. package/dist/controllers/items.js +1 -1
  42. package/dist/controllers/not-found.js +1 -1
  43. package/dist/controllers/relations.js +1 -1
  44. package/dist/database/errors/dialects/mysql.d.ts +1 -1
  45. package/dist/database/errors/dialects/postgres.d.ts +1 -1
  46. package/dist/database/errors/dialects/sqlite.d.ts +1 -1
  47. package/dist/database/errors/translate.d.ts +1 -1
  48. package/dist/database/errors/translate.js +1 -1
  49. package/dist/database/helpers/date/dialects/mssql.js +1 -1
  50. package/dist/database/helpers/date/dialects/mysql.js +1 -1
  51. package/dist/database/helpers/date/types.js +1 -1
  52. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
  53. package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
  54. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
  55. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  56. package/dist/database/helpers/schema/dialects/mysql.js +16 -3
  57. package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
  58. package/dist/database/helpers/schema/types.d.ts +13 -0
  59. package/dist/database/helpers/schema/types.js +24 -0
  60. package/dist/database/index.js +4 -4
  61. package/dist/database/migrations/20220429A-add-flows.js +1 -1
  62. package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
  63. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
  64. package/dist/database/migrations/20240204A-marketplace.js +9 -7
  65. package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
  66. package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
  67. package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
  68. package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
  69. package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
  70. package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
  71. package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
  72. package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
  73. package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
  74. package/dist/database/migrations/run.js +3 -3
  75. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  76. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
  77. package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
  78. package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
  79. package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
  80. package/dist/database/run-ast/utils/get-column.js +1 -1
  81. package/dist/database/seeds/run.js +3 -3
  82. package/dist/extensions/lib/get-extensions-path.js +1 -1
  83. package/dist/extensions/lib/get-extensions-settings.js +1 -1
  84. package/dist/extensions/lib/get-extensions.js +1 -1
  85. package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
  86. package/dist/extensions/lib/installation/manager.js +3 -3
  87. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
  88. package/dist/extensions/lib/sync/status.js +1 -1
  89. package/dist/extensions/lib/sync/sync.js +7 -7
  90. package/dist/extensions/lib/sync/utils.js +2 -2
  91. package/dist/extensions/manager.d.ts +1 -1
  92. package/dist/extensions/manager.js +8 -8
  93. package/dist/flows.d.ts +1 -1
  94. package/dist/logger/index.js +1 -1
  95. package/dist/logger/logs-stream.d.ts +1 -1
  96. package/dist/logger/logs-stream.js +1 -1
  97. package/dist/mailer.js +1 -1
  98. package/dist/metrics/lib/create-metrics.js +2 -2
  99. package/dist/middleware/authenticate.js +3 -3
  100. package/dist/middleware/collection-exists.js +1 -1
  101. package/dist/middleware/extract-token.js +1 -1
  102. package/dist/middleware/graphql.js +2 -2
  103. package/dist/middleware/validate-batch.js +1 -1
  104. package/dist/operations/exec/index.js +2 -1
  105. package/dist/operations/mail/index.js +1 -1
  106. package/dist/operations/mail/rate-limiter.js +2 -2
  107. package/dist/permissions/cache.js +5 -0
  108. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
  109. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
  110. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
  111. package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
  112. package/dist/permissions/modules/process-ast/process-ast.js +1 -1
  113. package/dist/permissions/modules/process-payload/process-payload.js +1 -1
  114. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +13 -1
  115. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +54 -6
  116. package/dist/permissions/modules/validate-access/validate-access.js +3 -2
  117. package/dist/rate-limiter.js +1 -1
  118. package/dist/request/is-denied-ip.js +1 -1
  119. package/dist/schedules/project.js +1 -1
  120. package/dist/schedules/telemetry.js +1 -1
  121. package/dist/schedules/tus.js +1 -1
  122. package/dist/server.js +4 -4
  123. package/dist/services/assets.d.ts +2 -1
  124. package/dist/services/assets.js +35 -8
  125. package/dist/services/authentication.js +2 -2
  126. package/dist/services/collections.js +1 -1
  127. package/dist/services/extensions.d.ts +1 -1
  128. package/dist/services/files/utils/get-metadata.d.ts +1 -1
  129. package/dist/services/files/utils/get-metadata.js +1 -1
  130. package/dist/services/files.d.ts +1 -1
  131. package/dist/services/files.js +4 -4
  132. package/dist/services/graphql/index.d.ts +1 -1
  133. package/dist/services/graphql/index.js +1 -1
  134. package/dist/services/graphql/resolvers/mutation.js +1 -1
  135. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  136. package/dist/services/graphql/schema/read.js +1 -1
  137. package/dist/services/graphql/subscription.d.ts +1 -1
  138. package/dist/services/graphql/types/date.js +1 -1
  139. package/dist/services/graphql/types/hash.js +1 -1
  140. package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
  141. package/dist/services/import-export.d.ts +1 -1
  142. package/dist/services/import-export.js +2 -2
  143. package/dist/services/index.d.ts +0 -1
  144. package/dist/services/index.js +0 -1
  145. package/dist/services/mail/index.js +2 -2
  146. package/dist/services/mail/rate-limiter.js +2 -2
  147. package/dist/services/payload.js +2 -2
  148. package/dist/services/schema.js +1 -1
  149. package/dist/services/server.js +12 -4
  150. package/dist/services/settings.js +2 -2
  151. package/dist/services/tfa.js +1 -1
  152. package/dist/services/translations.js +1 -1
  153. package/dist/services/tus/data-store.d.ts +1 -3
  154. package/dist/services/tus/data-store.js +2 -5
  155. package/dist/services/tus/server.js +6 -6
  156. package/dist/services/users.js +4 -4
  157. package/dist/services/versions.js +1 -1
  158. package/dist/telemetry/lib/send-report.d.ts +1 -1
  159. package/dist/telemetry/lib/send-report.js +1 -1
  160. package/dist/telemetry/lib/track.js +1 -1
  161. package/dist/test-utils/knex.js +1 -1
  162. package/dist/types/collection.d.ts +1 -1
  163. package/dist/utils/async-handler.d.ts +1 -1
  164. package/dist/utils/calculate-field-depth.js +1 -1
  165. package/dist/utils/compress.js +1 -1
  166. package/dist/utils/deep-map-response.js +2 -2
  167. package/dist/utils/get-cache-key.js +1 -1
  168. package/dist/utils/get-field-system-rows.js +1 -1
  169. package/dist/utils/get-ip-from-req.d.ts +1 -1
  170. package/dist/utils/get-ip-from-req.js +1 -1
  171. package/dist/utils/get-local-type.js +7 -3
  172. package/dist/utils/get-service.js +1 -3
  173. package/dist/utils/get-snapshot-diff.js +1 -1
  174. package/dist/utils/is-url-allowed.js +1 -1
  175. package/dist/utils/jwt.js +1 -1
  176. package/dist/utils/sanitize-schema.d.ts +1 -1
  177. package/dist/utils/should-clear-cache.d.ts +1 -1
  178. package/dist/utils/should-skip-cache.js +2 -2
  179. package/dist/utils/validate-diff.js +1 -1
  180. package/dist/utils/validate-snapshot.js +3 -3
  181. package/dist/utils/validate-storage.js +2 -2
  182. package/dist/utils/verify-session-jwt.js +1 -1
  183. package/dist/utils/versioning/deep-map-with-schema.js +2 -2
  184. package/dist/websocket/controllers/base.d.ts +2 -2
  185. package/dist/websocket/controllers/base.js +3 -3
  186. package/dist/websocket/controllers/graphql.d.ts +1 -1
  187. package/dist/websocket/controllers/graphql.js +1 -1
  188. package/dist/websocket/controllers/logs.d.ts +1 -1
  189. package/dist/websocket/controllers/rest.d.ts +1 -1
  190. package/dist/websocket/controllers/rest.js +2 -2
  191. package/dist/websocket/handlers/heartbeat.js +1 -1
  192. package/dist/websocket/handlers/items.js +2 -2
  193. package/dist/websocket/handlers/subscribe.js +1 -1
  194. package/dist/websocket/types.d.ts +1 -1
  195. package/dist/websocket/utils/wait-for-message.js +1 -1
  196. package/package.json +24 -24
  197. package/dist/controllers/webhooks.d.ts +0 -2
  198. package/dist/controllers/webhooks.js +0 -74
  199. package/dist/services/webhooks.d.ts +0 -14
  200. package/dist/services/webhooks.js +0 -32
  201. package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
@@ -1,5 +1,9 @@
1
1
  import { toBoolean } from '@directus/utils';
2
2
  import { fetchPermittedAstRootFields } from '../../../../database/run-ast/modules/fetch-permitted-ast-root-fields.js';
3
+ import { fetchPermissions } from '../../../lib/fetch-permissions.js';
4
+ import { fetchPolicies } from '../../../lib/fetch-policies.js';
5
+ import { fetchAllowedFields } from '../../fetch-allowed-fields/fetch-allowed-fields.js';
6
+ import { injectCases } from '../../process-ast/lib/inject-cases.js';
3
7
  import { processAst } from '../../process-ast/process-ast.js';
4
8
  export async function validateItemAccess(options, context) {
5
9
  const primaryKeyField = context.schema.collections[options.collection]?.primary;
@@ -24,18 +28,62 @@ export async function validateItemAccess(options, context) {
24
28
  _in: options.primaryKeys,
25
29
  },
26
30
  };
31
+ let hasItemRules;
32
+ let permissionedFields;
33
+ // Inject the root fields after the permissions have been processed, as to not require access to all collection fields
34
+ if (options.returnAllowedRootFields) {
35
+ const allowedFields = await fetchAllowedFields({ accountability: options.accountability, action: options.action, collection: options.collection }, context);
36
+ const schemaFields = Object.keys(context.schema.collections[options.collection].fields);
37
+ const hasWildcard = allowedFields.includes('*');
38
+ permissionedFields = hasWildcard ? schemaFields : allowedFields;
39
+ const policies = await fetchPolicies(options.accountability, context);
40
+ const permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
41
+ // Only inject cases if there are item-level permission rules
42
+ hasItemRules = permissions.some((p) => p.permissions && Object.keys(p.permissions).length > 0);
43
+ if (hasItemRules) {
44
+ // Create children only for fields that exist in schema and are allowed by permissions
45
+ ast.children = permissionedFields.map((field) => ({
46
+ type: 'field',
47
+ name: field,
48
+ fieldKey: field,
49
+ whenCase: [],
50
+ alias: false,
51
+ }));
52
+ injectCases(ast, permissions);
53
+ }
54
+ }
27
55
  const items = await fetchPermittedAstRootFields(ast, {
28
56
  schema: context.schema,
29
57
  accountability: options.accountability,
30
58
  knex: context.knex,
31
59
  action: options.action,
32
60
  });
33
- if (items && items.length === options.primaryKeys.length) {
34
- const { fields } = options;
35
- if (fields) {
36
- return items.every((item) => fields.every((field) => toBoolean(item[field])));
61
+ const hasAccess = items && items.length === options.primaryKeys.length;
62
+ if (!hasAccess) {
63
+ if (options.returnAllowedRootFields) {
64
+ return { accessAllowed: false, allowedRootFields: [] };
65
+ }
66
+ return { accessAllowed: false };
67
+ }
68
+ let accessAllowed = true;
69
+ // If specific fields were requested, verify they are all accessible
70
+ if (options.fields) {
71
+ accessAllowed = items.every((item) => options.fields.every((field) => toBoolean(item[field])));
72
+ }
73
+ // If returnAllowedRootFields, return intersection of allowed fields across all items
74
+ if (options.returnAllowedRootFields) {
75
+ // If there are no item-level rules, return the permissioned fields directly
76
+ if (!hasItemRules) {
77
+ return {
78
+ accessAllowed,
79
+ allowedRootFields: permissionedFields,
80
+ };
37
81
  }
38
- return true;
82
+ const allowedRootFields = items.length > 0 ? Object.keys(items[0]).filter((field) => items.every((item) => item[field] === 1)) : [];
83
+ return {
84
+ accessAllowed,
85
+ allowedRootFields,
86
+ };
39
87
  }
40
- return false;
88
+ return { accessAllowed };
41
89
  }
@@ -1,7 +1,7 @@
1
1
  import { ForbiddenError } from '@directus/errors';
2
+ import { createCollectionForbiddenError } from '../process-ast/utils/validate-path/create-error.js';
2
3
  import { validateCollectionAccess } from './lib/validate-collection-access.js';
3
4
  import { validateItemAccess } from './lib/validate-item-access.js';
4
- import { createCollectionForbiddenError } from '../process-ast/utils/validate-path/create-error.js';
5
5
  /**
6
6
  * Validate if the current user has access to perform action against the given collection and
7
7
  * optional primary keys. This is done by reading the item from the database using the access
@@ -20,7 +20,8 @@ export async function validateAccess(options, context) {
20
20
  // from the database. If no keys are passed, we can simply check if the collection+action combo
21
21
  // exists within permissions
22
22
  if (options.primaryKeys) {
23
- access = await validateItemAccess(options, context);
23
+ const result = await validateItemAccess(options, context);
24
+ access = result.accessAllowed;
24
25
  }
25
26
  else {
26
27
  access = await validateCollectionAccess(options, context);
@@ -1,8 +1,8 @@
1
+ import { createRequire } from 'node:module';
1
2
  import { useEnv } from '@directus/env';
2
3
  import { merge } from 'lodash-es';
3
4
  import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible';
4
5
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
5
- import { createRequire } from 'node:module';
6
6
  const require = createRequire(import.meta.url);
7
7
  export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides) {
8
8
  const env = useEnv();
@@ -1,7 +1,7 @@
1
+ import os from 'node:os';
1
2
  import { useEnv } from '@directus/env';
2
3
  import { ipInNetworks } from '@directus/utils/node';
3
4
  import { matches } from 'ip-matching';
4
- import os from 'node:os';
5
5
  import { useLogger } from '../logger/index.js';
6
6
  export function isDeniedIp(ip) {
7
7
  const env = useEnv();
@@ -1,8 +1,8 @@
1
+ import { version } from 'directus/version';
1
2
  import { random } from 'lodash-es';
2
3
  import getDatabase from '../database/index.js';
3
4
  import { sendReport } from '../telemetry/index.js';
4
5
  import { scheduleSynchronizedJob } from '../utils/schedule.js';
5
- import { version } from 'directus/version';
6
6
  /**
7
7
  * Schedule the project status job
8
8
  */
@@ -1,8 +1,8 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { toBoolean } from '@directus/utils';
3
3
  import { getCache } from '../cache.js';
4
- import { scheduleSynchronizedJob } from '../utils/schedule.js';
5
4
  import { track } from '../telemetry/index.js';
5
+ import { scheduleSynchronizedJob } from '../utils/schedule.js';
6
6
  /**
7
7
  * Exported to be able to test the anonymous callback function
8
8
  */
@@ -1,6 +1,6 @@
1
1
  import { RESUMABLE_UPLOADS } from '../constants.js';
2
- import { getSchema } from '../utils/get-schema.js';
3
2
  import { createTusServer } from '../services/tus/index.js';
3
+ import { getSchema } from '../utils/get-schema.js';
4
4
  import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';
5
5
  /**
6
6
  * Schedule the tus cleanup
package/dist/server.js CHANGED
@@ -1,19 +1,19 @@
1
+ import * as http from 'http';
2
+ import * as https from 'https';
3
+ import url from 'url';
1
4
  import { useEnv } from '@directus/env';
2
5
  import { toBoolean } from '@directus/utils';
3
6
  import { getNodeEnv } from '@directus/utils/node';
4
7
  import { createTerminus } from '@godaddy/terminus';
5
- import * as http from 'http';
6
- import * as https from 'https';
7
8
  import { once } from 'lodash-es';
8
9
  import qs from 'qs';
9
- import url from 'url';
10
10
  import createApp from './app.js';
11
11
  import getDatabase from './database/index.js';
12
12
  import emitter from './emitter.js';
13
13
  import { useLogger } from './logger/index.js';
14
+ import { getAddress } from './utils/get-address.js';
14
15
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
15
16
  import { getIPFromReq } from './utils/get-ip-from-req.js';
16
- import { getAddress } from './utils/get-address.js';
17
17
  import { createLogsController, createSubscriptionController, createWebSocketController, getLogsController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
18
18
  import { startWebSocketHandlers } from './websocket/handlers/index.js';
19
19
  export let SERVER_ONLINE = true;
@@ -1,7 +1,7 @@
1
+ import type { Readable } from 'node:stream';
1
2
  import type { AbstractServiceOptions, Accountability, Range, SchemaOverview, Stat, TransformationSet } from '@directus/types';
2
3
  import archiver from 'archiver';
3
4
  import type { Knex } from 'knex';
4
- import type { Readable } from 'node:stream';
5
5
  import { FilesService } from './files.js';
6
6
  export declare class AssetsService {
7
7
  knex: Knex;
@@ -9,6 +9,7 @@ export declare class AssetsService {
9
9
  schema: SchemaOverview;
10
10
  sudoFilesService: FilesService;
11
11
  constructor(options: AbstractServiceOptions);
12
+ private sanitizeFields;
12
13
  private zip;
13
14
  zipFiles(files: string[]): Promise<{
14
15
  archive: archiver.Archiver;
@@ -1,22 +1,22 @@
1
+ import path from 'path';
1
2
  import { useEnv } from '@directus/env';
2
3
  import { ForbiddenError, IllegalAssetTransformationError, InvalidPayloadError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
3
4
  import archiver from 'archiver';
4
5
  import { clamp } from 'lodash-es';
5
6
  import { contentType, extension } from 'mime-types';
6
7
  import hash from 'object-hash';
7
- import path from 'path';
8
8
  import sharp from 'sharp';
9
9
  import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
10
10
  import getDatabase from '../database/index.js';
11
11
  import { useLogger } from '../logger/index.js';
12
- import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
12
+ import { validateItemAccess } from '../permissions/modules/validate-access/lib/validate-item-access.js';
13
13
  import { getStorage } from '../storage/index.js';
14
14
  import { getMilliseconds } from '../utils/get-milliseconds.js';
15
15
  import { isValidUuid } from '../utils/is-valid-uuid.js';
16
16
  import * as TransformationUtils from '../utils/transformations.js';
17
17
  import { NameDeduper } from './assets/name-deduper.js';
18
- import { FilesService } from './files.js';
19
18
  import { getSharpInstance } from './files/lib/get-sharp-instance.js';
19
+ import { FilesService } from './files.js';
20
20
  import { FoldersService } from './folders.js';
21
21
  const env = useEnv();
22
22
  const logger = useLogger();
@@ -31,6 +31,20 @@ export class AssetsService {
31
31
  this.schema = options.schema;
32
32
  this.sudoFilesService = new FilesService({ ...options, accountability: null });
33
33
  }
34
+ sanitizeFields(file, allowedFields) {
35
+ if (allowedFields.includes('*')) {
36
+ return file;
37
+ }
38
+ const bypassFields = ['type', 'filesize'];
39
+ const fieldsToKeep = new Set([...allowedFields, ...bypassFields]);
40
+ const filteredFile = {};
41
+ for (const field of fieldsToKeep) {
42
+ if (field in file) {
43
+ filteredFile[field] = file[field];
44
+ }
45
+ }
46
+ return filteredFile;
47
+ }
34
48
  zip(options) {
35
49
  if (options.files.length === 0) {
36
50
  throw new InvalidPayloadError({ reason: 'No files found in the selected folders tree' });
@@ -135,13 +149,22 @@ export class AssetsService {
135
149
  */
136
150
  if (!isValidUuid(id))
137
151
  throw new ForbiddenError();
138
- if (systemPublicKeys.includes(id) === false && this.accountability) {
139
- await validateAccess({
152
+ let allowedFields = ['*'];
153
+ if (!systemPublicKeys.includes(id) && this.accountability && this.accountability.admin !== true) {
154
+ // Use validateItemAccess to check access and get allowed fields
155
+ const { allowedRootFields, accessAllowed } = await validateItemAccess({
140
156
  accountability: this.accountability,
141
157
  action: 'read',
142
158
  collection: 'directus_files',
143
159
  primaryKeys: [id],
160
+ returnAllowedRootFields: true,
144
161
  }, { knex: this.knex, schema: this.schema });
162
+ if (!accessAllowed) {
163
+ throw new ForbiddenError({
164
+ reason: `You don't have permission to perform "read" for collection "directus_files" or it does not exist.`,
165
+ });
166
+ }
167
+ allowedFields = allowedRootFields;
145
168
  }
146
169
  const file = (await this.sudoFilesService.readOne(id, { limit: 1 }));
147
170
  const exists = await storage.location(file.storage).exists(file.filename_disk);
@@ -195,7 +218,7 @@ export class AssetsService {
195
218
  const assetStream = () => storage.location(file.storage).read(assetFilename, { range });
196
219
  return {
197
220
  stream: deferStream ? assetStream : await assetStream(),
198
- file,
221
+ file: this.sanitizeFields(file, allowedFields),
199
222
  stat: await storage.location(file.storage).stat(assetFilename),
200
223
  };
201
224
  }
@@ -260,13 +283,17 @@ export class AssetsService {
260
283
  return {
261
284
  stream: deferStream ? assetStream : await assetStream(),
262
285
  stat: await storage.location(file.storage).stat(assetFilename),
263
- file,
286
+ file: this.sanitizeFields(file, allowedFields),
264
287
  };
265
288
  }
266
289
  else {
267
290
  const assetStream = () => storage.location(file.storage).read(file.filename_disk, { range, version });
268
291
  const stat = await storage.location(file.storage).stat(file.filename_disk);
269
- return { stream: deferStream ? assetStream : await assetStream(), file, stat };
292
+ return {
293
+ stream: deferStream ? assetStream : await assetStream(),
294
+ file: this.sanitizeFields(file, allowedFields),
295
+ stat,
296
+ };
270
297
  }
271
298
  }
272
299
  }
@@ -1,16 +1,16 @@
1
+ import { performance } from 'perf_hooks';
1
2
  import { Action } from '@directus/constants';
2
3
  import { useEnv } from '@directus/env';
3
4
  import { InvalidCredentialsError, InvalidOtpError, ServiceUnavailableError, UserSuspendedError, } from '@directus/errors';
4
5
  import jwt from 'jsonwebtoken';
5
6
  import { clone, cloneDeep } from 'lodash-es';
6
- import { performance } from 'perf_hooks';
7
7
  import { getAuthProvider } from '../auth.js';
8
8
  import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
9
9
  import getDatabase from '../database/index.js';
10
10
  import emitter from '../emitter.js';
11
11
  import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
12
12
  import { fetchGlobalAccess } from '../permissions/modules/fetch-global-access/fetch-global-access.js';
13
- import { RateLimiterRes, createRateLimiter } from '../rate-limiter.js';
13
+ import { createRateLimiter, RateLimiterRes } from '../rate-limiter.js';
14
14
  import { getMilliseconds } from '../utils/get-milliseconds.js';
15
15
  import { getSecret } from '../utils/get-secret.js';
16
16
  import { stall } from '../utils/stall.js';
@@ -14,10 +14,10 @@ import { validateAccess } from '../permissions/modules/validate-access/validate-
14
14
  import { getSchema } from '../utils/get-schema.js';
15
15
  import { shouldClearCache } from '../utils/should-clear-cache.js';
16
16
  import { transaction } from '../utils/transaction.js';
17
- import { FieldsService } from './fields.js';
18
17
  import { buildCollectionAndFieldRelations } from './fields/build-collection-and-field-relations.js';
19
18
  import { getCollectionMetaUpdates } from './fields/get-collection-meta-updates.js';
20
19
  import { getCollectionRelationList } from './fields/get-collection-relation-list.js';
20
+ import { FieldsService } from './fields.js';
21
21
  import { ItemsService } from './items.js';
22
22
  export class CollectionsService {
23
23
  knex;
@@ -1,4 +1,4 @@
1
- import type { ApiOutput, ExtensionSettings, ExtensionManager } from '@directus/types';
1
+ import type { ApiOutput, ExtensionManager, ExtensionSettings } from '@directus/types';
2
2
  import type { AbstractServiceOptions, Accountability, DeepPartial, SchemaOverview } from '@directus/types';
3
3
  import type { Knex } from 'knex';
4
4
  import { ItemsService } from './items.js';
@@ -1,4 +1,4 @@
1
- import type { File } from '@directus/types';
2
1
  import type { Readable } from 'node:stream';
2
+ import type { File } from '@directus/types';
3
3
  export type Metadata = Partial<Pick<File, 'height' | 'width' | 'description' | 'title' | 'tags' | 'metadata'>>;
4
4
  export declare function getMetadata(stream: Readable, allowList?: string | string[]): Promise<Metadata>;
@@ -1,8 +1,8 @@
1
+ import { pipeline } from 'node:stream/promises';
1
2
  import { useEnv } from '@directus/env';
2
3
  import exif, {} from 'exif-reader';
3
4
  import { parse as parseIcc } from 'icc';
4
5
  import { pick } from 'lodash-es';
5
- import { pipeline } from 'node:stream/promises';
6
6
  import { useLogger } from '../../../logger/index.js';
7
7
  import { getSharpInstance } from '../lib/get-sharp-instance.js';
8
8
  import { parseIptc, parseXmp } from './parse-image-metadata.js';
@@ -1,5 +1,5 @@
1
- import type { AbstractServiceOptions, BusboyFileStream, File, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
2
1
  import type { Readable } from 'node:stream';
2
+ import type { AbstractServiceOptions, BusboyFileStream, File, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
3
3
  import { ItemsService } from './items.js';
4
4
  export declare class FilesService extends ItemsService<File> {
5
5
  constructor(options: AbstractServiceOptions);
@@ -1,3 +1,7 @@
1
+ import { PassThrough as PassThroughStream, Transform as TransformStream } from 'node:stream';
2
+ import zlib from 'node:zlib';
3
+ import path from 'path';
4
+ import url from 'url';
1
5
  import { useEnv } from '@directus/env';
2
6
  import { ContentTooLargeError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
3
7
  import formatTitle from '@directus/format-title';
@@ -5,10 +9,6 @@ import { toArray } from '@directus/utils';
5
9
  import encodeURL from 'encodeurl';
6
10
  import { clone, cloneDeep } from 'lodash-es';
7
11
  import { extension } from 'mime-types';
8
- import { PassThrough as PassThroughStream, Transform as TransformStream } from 'node:stream';
9
- import zlib from 'node:zlib';
10
- import path from 'path';
11
- import url from 'url';
12
12
  import { RESUMABLE_UPLOADS } from '../constants.js';
13
13
  import emitter from '../emitter.js';
14
14
  import { useLogger } from '../logger/index.js';
@@ -1,4 +1,4 @@
1
- import type { AbstractServiceOptions, Accountability, GraphQLParams, GQLScope, Item, Query, SchemaOverview, PrimaryKey } from '@directus/types';
1
+ import type { AbstractServiceOptions, Accountability, GQLScope, GraphQLParams, Item, PrimaryKey, Query, SchemaOverview } from '@directus/types';
2
2
  import type { FormattedExecutionResult, GraphQLSchema } from 'graphql';
3
3
  import type { Knex } from 'knex';
4
4
  export declare class GraphQLService {
@@ -1,5 +1,5 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate } from 'graphql';
2
+ import { execute, NoSchemaIntrospectionCustomRule, specifiedRules, validate } from 'graphql';
3
3
  import getDatabase from '../../database/index.js';
4
4
  import { getService } from '../../utils/get-service.js';
5
5
  import { formatError } from './errors/format.js';
@@ -1,7 +1,7 @@
1
1
  import { getService } from '../../../utils/get-service.js';
2
2
  import { formatError } from '../errors/format.js';
3
- import { replaceFragmentsInSelections } from '../utils/replace-fragments.js';
4
3
  import { getQuery } from '../schema/parse-query.js';
4
+ import { replaceFragmentsInSelections } from '../utils/replace-fragments.js';
5
5
  export async function resolveMutation(gql, args, info) {
6
6
  const action = info.fieldName.split('_')[0];
7
7
  let collection = info.fieldName.substring(action.length + 1);
@@ -1,6 +1,6 @@
1
+ import type { GQLScope } from '@directus/types';
1
2
  import type { SchemaComposer } from 'graphql-compose';
2
3
  import { ObjectTypeComposer } from 'graphql-compose';
3
- import type { GQLScope } from '@directus/types';
4
4
  import { type InconsistentFields, type Schema } from './index.js';
5
5
  /**
6
6
  * Construct an object of types for every collection, using the permitted fields per action type
@@ -9,8 +9,8 @@ import { GraphQLDate } from '../types/date.js';
9
9
  import { GraphQLGeoJSON } from '../types/geojson.js';
10
10
  import { GraphQLHash } from '../types/hash.js';
11
11
  import { GraphQLStringOrFloat } from '../types/string-or-float.js';
12
- import { SYSTEM_DENY_LIST } from './index.js';
13
12
  import { getTypes } from './get-types.js';
13
+ import { SYSTEM_DENY_LIST } from './index.js';
14
14
  /**
15
15
  * Create readable types and attach resolvers for each. Also prepares full filter argument structures
16
16
  */
@@ -1,5 +1,5 @@
1
- import type { GraphQLService } from './index.js';
2
1
  import type { GraphQLResolveInfo } from 'graphql';
2
+ import type { GraphQLService } from './index.js';
3
3
  export declare function bindPubSub(): void;
4
4
  export declare function createSubscriptionGenerator(gql: GraphQLService, event: string): (_x: unknown, _y: unknown, _z: unknown, request: GraphQLResolveInfo) => AsyncGenerator<{
5
5
  [event]: {
@@ -1,4 +1,4 @@
1
- import { GraphQLString, GraphQLScalarType } from 'graphql';
1
+ import { GraphQLScalarType, GraphQLString } from 'graphql';
2
2
  export const GraphQLDate = new GraphQLScalarType({
3
3
  ...GraphQLString,
4
4
  name: 'Date',
@@ -1,4 +1,4 @@
1
- import { GraphQLString, GraphQLScalarType } from 'graphql';
1
+ import { GraphQLScalarType, GraphQLString } from 'graphql';
2
2
  export const GraphQLHash = new GraphQLScalarType({
3
3
  ...GraphQLString,
4
4
  name: 'Hash',
@@ -1,4 +1,4 @@
1
- import { GraphQLError, Token, locatedError } from 'graphql';
1
+ import { GraphQLError, locatedError, Token } from 'graphql';
2
2
  export function addPathToValidationError(validationError) {
3
3
  const token = validationError.nodes?.[0]?.loc?.startToken;
4
4
  if (!token)
@@ -1,6 +1,6 @@
1
+ import type { Readable } from 'node:stream';
1
2
  import type { AbstractServiceOptions, Accountability, DirectusError, ExportFormat, File, Query, SchemaOverview } from '@directus/types';
2
3
  import type { Knex } from 'knex';
3
- import type { Readable } from 'node:stream';
4
4
  import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../types/index.js';
5
5
  export declare function createErrorTracker(): {
6
6
  addCapturedError: (err: any, rowNumber: number) => void;
@@ -1,3 +1,5 @@
1
+ import { createReadStream, createWriteStream } from 'node:fs';
2
+ import { appendFile } from 'node:fs/promises';
1
3
  import { useEnv } from '@directus/env';
2
4
  import { createError, ErrorCode, ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
3
5
  import { isSystemCollection } from '@directus/system-data';
@@ -9,8 +11,6 @@ import { dump as toYAML } from 'js-yaml';
9
11
  import { parse as toXML } from 'js2xmlparser';
10
12
  import { Parser as CSVParser, transforms as CSVTransforms } from 'json2csv';
11
13
  import { set } from 'lodash-es';
12
- import { createReadStream, createWriteStream } from 'node:fs';
13
- import { appendFile } from 'node:fs/promises';
14
14
  import Papa from 'papaparse';
15
15
  import StreamArray from 'stream-json/streamers/StreamArray.js';
16
16
  import { parseFields } from '../database/get-ast-from-query/lib/parse-fields.js';
@@ -35,5 +35,4 @@ export * from './translations.js';
35
35
  export * from './users.js';
36
36
  export * from './utils.js';
37
37
  export * from './versions.js';
38
- export * from './webhooks.js';
39
38
  export * from './websocket.js';
@@ -35,5 +35,4 @@ export * from './translations.js';
35
35
  export * from './users.js';
36
36
  export * from './utils.js';
37
37
  export * from './versions.js';
38
- export * from './webhooks.js';
39
38
  export * from './websocket.js';
@@ -1,10 +1,10 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
1
3
  import { useEnv } from '@directus/env';
2
4
  import { InvalidPayloadError } from '@directus/errors';
3
5
  import { isObject } from '@directus/utils';
4
6
  import fse from 'fs-extra';
5
7
  import { Liquid } from 'liquidjs';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
8
  import getDatabase from '../../database/index.js';
9
9
  import emitter from '../../emitter.js';
10
10
  import { useLogger } from '../../logger/index.js';
@@ -1,8 +1,8 @@
1
1
  import { useEnv } from '@directus/env';
2
+ import { EmailLimitExceededError } from '@directus/errors';
3
+ import { toBoolean } from '@directus/utils';
2
4
  import { RateLimiterQueue } from 'rate-limiter-flexible';
3
5
  import { createRateLimiter } from '../../rate-limiter.js';
4
- import { toBoolean } from '@directus/utils';
5
- import { EmailLimitExceededError } from '@directus/errors';
6
6
  let emailRateLimiterQueue;
7
7
  const env = useEnv();
8
8
  if (toBoolean(env['RATE_LIMITER_EMAIL_ENABLED']) === true) {
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
2
3
  import { UserIntegrityCheckFlag } from '@directus/types';
3
4
  import { parseJSON, toArray } from '@directus/utils';
@@ -5,12 +6,11 @@ import { format, isValid, parseISO } from 'date-fns';
5
6
  import { unflatten } from 'flat';
6
7
  import Joi from 'joi';
7
8
  import { clone, cloneDeep, isNil, isObject, isPlainObject, pick } from 'lodash-es';
8
- import { randomUUID } from 'node:crypto';
9
9
  import { parse as wktToGeoJSON } from 'wellknown';
10
10
  import { getHelpers } from '../database/helpers/index.js';
11
11
  import getDatabase from '../database/index.js';
12
- import { generateHash } from '../utils/generate-hash.js';
13
12
  import { decrypt, encrypt } from '../utils/encrypt.js';
13
+ import { generateHash } from '../utils/generate-hash.js';
14
14
  import { getSecret } from '../utils/get-secret.js';
15
15
  /**
16
16
  * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
@@ -1,5 +1,5 @@
1
- import getDatabase from '../database/index.js';
2
1
  import { ForbiddenError } from '@directus/errors';
2
+ import getDatabase from '../database/index.js';
3
3
  import { applyDiff } from '../utils/apply-diff.js';
4
4
  import { getSnapshotDiff } from '../utils/get-snapshot-diff.js';
5
5
  import { getSnapshot } from '../utils/get-snapshot.js';
@@ -1,11 +1,11 @@
1
+ import { Readable } from 'node:stream';
2
+ import { performance } from 'perf_hooks';
1
3
  import { useEnv } from '@directus/env';
2
4
  import { toArray, toBoolean } from '@directus/utils';
3
5
  import { version } from 'directus/version';
4
6
  import { merge } from 'lodash-es';
5
- import { Readable } from 'node:stream';
6
- import { performance } from 'perf_hooks';
7
7
  import { getCache } from '../cache.js';
8
- import { RESUMABLE_UPLOADS } from '../constants.js';
8
+ import { FILE_UPLOADS, RESUMABLE_UPLOADS } from '../constants.js';
9
9
  import getDatabase, { hasDatabaseConnection } from '../database/index.js';
10
10
  import { useLogger } from '../logger/index.js';
11
11
  import getMailer from '../mailer.js';
@@ -57,9 +57,10 @@ export class ServerService {
57
57
  ],
58
58
  });
59
59
  info['project'] = projectInfo;
60
- info['mcp_enabled'] = toBoolean(env['MCP_ENABLED'] ?? true);
61
60
  info['setupCompleted'] = setupComplete;
62
61
  if (this.accountability?.user) {
62
+ info['mcp_enabled'] = toBoolean(env['MCP_ENABLED'] ?? true);
63
+ info['ai_enabled'] = toBoolean(env['AI_ENABLED'] ?? true);
63
64
  if (env['RATE_LIMITER_ENABLED']) {
64
65
  info['rateLimit'] = {
65
66
  points: env['RATE_LIMITER_POINTS'],
@@ -112,8 +113,15 @@ export class ServerService {
112
113
  else {
113
114
  info['websocket'] = false;
114
115
  }
116
+ if (FILE_UPLOADS.MAX_CONCURRENCY && FILE_UPLOADS.MAX_CONCURRENCY !== Infinity) {
117
+ info['uploads'] = {
118
+ maxConcurrency: FILE_UPLOADS.MAX_CONCURRENCY,
119
+ };
120
+ }
115
121
  if (RESUMABLE_UPLOADS.ENABLED) {
116
122
  info['uploads'] = {
123
+ ...info['uploads'],
124
+ tus: true,
117
125
  chunkSize: RESUMABLE_UPLOADS.CHUNK_SIZE,
118
126
  };
119
127
  }
@@ -1,6 +1,6 @@
1
- import { ItemsService } from './items.js';
2
- import { sendReport } from '../telemetry/index.js';
3
1
  import { version } from 'directus/version';
2
+ import { sendReport } from '../telemetry/index.js';
3
+ import { ItemsService } from './items.js';
4
4
  export class SettingsService extends ItemsService {
5
5
  constructor(options) {
6
6
  super('directus_settings', options);
@@ -1,8 +1,8 @@
1
1
  import { InvalidPayloadError } from '@directus/errors';
2
2
  import { authenticator } from 'otplib';
3
+ import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
3
4
  import getDatabase from '../database/index.js';
4
5
  import { ItemsService } from './items.js';
5
- import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
6
6
  export class TFAService {
7
7
  knex;
8
8
  itemsService;
@@ -1,5 +1,5 @@
1
- import getDatabase from '../database/index.js';
2
1
  import { InvalidPayloadError } from '@directus/errors';
2
+ import getDatabase from '../database/index.js';
3
3
  import { ItemsService } from './items.js';
4
4
  export class TranslationsService extends ItemsService {
5
5
  constructor(options) {