@directus/api 32.1.1 → 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 (336) hide show
  1. package/dist/ai/chat/constants/system-prompt.d.ts +1 -0
  2. package/dist/ai/chat/constants/system-prompt.js +51 -0
  3. package/dist/ai/chat/controllers/chat.post.d.ts +2 -0
  4. package/dist/ai/chat/controllers/chat.post.js +47 -0
  5. package/dist/ai/chat/lib/create-ui-stream.d.ts +15 -0
  6. package/dist/ai/chat/lib/create-ui-stream.js +42 -0
  7. package/dist/ai/chat/middleware/load-settings.d.ts +2 -0
  8. package/dist/ai/chat/middleware/load-settings.js +18 -0
  9. package/dist/ai/chat/models/chat-request.d.ts +34 -0
  10. package/dist/ai/chat/models/chat-request.js +26 -0
  11. package/dist/ai/chat/models/providers.d.ts +9 -0
  12. package/dist/ai/chat/models/providers.js +9 -0
  13. package/dist/ai/chat/router.d.ts +1 -0
  14. package/dist/ai/chat/router.js +5 -0
  15. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.d.ts +9 -0
  16. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +38 -0
  17. package/dist/ai/chat/utils/fix-error-tool-calls.d.ts +12 -0
  18. package/dist/ai/chat/utils/fix-error-tool-calls.js +30 -0
  19. package/dist/ai/chat/utils/parse-json-schema-7.d.ts +13 -0
  20. package/dist/ai/chat/utils/parse-json-schema-7.js +75 -0
  21. package/dist/{mcp → ai/mcp}/server.d.ts +13 -16
  22. package/dist/{mcp → ai/mcp}/server.js +4 -13
  23. package/dist/ai/mcp/types.d.ts +15 -0
  24. package/dist/{mcp/tools/assets.js → ai/tools/assets/index.js} +8 -5
  25. package/dist/{mcp/tools/collections.js → ai/tools/collections/index.js} +7 -4
  26. package/dist/{mcp/tools/fields.js → ai/tools/fields/index.js} +12 -9
  27. package/dist/{mcp/tools/files.js → ai/tools/files/index.js} +11 -5
  28. package/dist/{mcp/tools/flows.js → ai/tools/flows/index.js} +11 -5
  29. package/dist/{mcp/tools/folders.js → ai/tools/folders/index.js} +12 -5
  30. package/dist/ai/tools/index.d.ts +15 -0
  31. package/dist/ai/tools/index.js +29 -0
  32. package/dist/{mcp/tools/items.js → ai/tools/items/index.js} +13 -6
  33. package/dist/{mcp/tools/prompts/items.md → ai/tools/items/prompt.md} +19 -15
  34. package/dist/{mcp/tools/operations.d.ts → ai/tools/operations/index.d.ts} +46 -0
  35. package/dist/{mcp/tools/operations.js → ai/tools/operations/index.js} +12 -5
  36. package/dist/{mcp/tools/relations.js → ai/tools/relations/index.js} +7 -4
  37. package/dist/{mcp/tools/schema.d.ts → ai/tools/schema/index.d.ts} +1 -1
  38. package/dist/{mcp/tools/schema.js → ai/tools/schema/index.js} +9 -6
  39. package/dist/{mcp/tools/system.js → ai/tools/system/index.js} +7 -4
  40. package/dist/{mcp/tools/trigger-flow.js → ai/tools/trigger-flow/index.js} +8 -5
  41. package/dist/{mcp → ai/tools}/types.d.ts +1 -17
  42. package/dist/ai/tools/utils.d.ts +9 -0
  43. package/dist/ai/tools/utils.js +17 -0
  44. package/dist/app.js +11 -6
  45. package/dist/auth/drivers/ldap.js +2 -2
  46. package/dist/auth/drivers/local.js +1 -1
  47. package/dist/auth/drivers/oauth2.d.ts +1 -2
  48. package/dist/auth/drivers/oauth2.js +22 -17
  49. package/dist/auth/drivers/openid.d.ts +1 -2
  50. package/dist/auth/drivers/openid.js +18 -13
  51. package/dist/auth/drivers/saml.js +6 -3
  52. package/dist/auth/utils/generate-callback-url.d.ts +11 -0
  53. package/dist/auth/utils/generate-callback-url.js +40 -0
  54. package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
  55. package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
  56. package/dist/cache.js +2 -2
  57. package/dist/cli/commands/bootstrap/index.js +2 -2
  58. package/dist/cli/commands/database/install.js +1 -1
  59. package/dist/cli/commands/database/migrate.js +1 -1
  60. package/dist/cli/commands/init/index.js +2 -2
  61. package/dist/cli/commands/roles/create.js +4 -4
  62. package/dist/cli/commands/schema/apply.js +3 -3
  63. package/dist/cli/commands/schema/snapshot.js +1 -1
  64. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  65. package/dist/cli/utils/create-db-connection.js +1 -1
  66. package/dist/cli/utils/create-env/index.js +1 -1
  67. package/dist/constants.d.ts +7 -3
  68. package/dist/constants.js +7 -3
  69. package/dist/controllers/access.js +1 -1
  70. package/dist/controllers/assets.js +40 -3
  71. package/dist/controllers/extensions.js +1 -1
  72. package/dist/controllers/fields.js +2 -2
  73. package/dist/controllers/files.js +1 -1
  74. package/dist/controllers/items.js +1 -1
  75. package/dist/controllers/mcp.js +1 -1
  76. package/dist/controllers/not-found.js +1 -1
  77. package/dist/controllers/relations.js +1 -1
  78. package/dist/database/errors/dialects/mysql.d.ts +1 -1
  79. package/dist/database/errors/dialects/postgres.d.ts +1 -1
  80. package/dist/database/errors/dialects/sqlite.d.ts +1 -1
  81. package/dist/database/errors/translate.d.ts +1 -1
  82. package/dist/database/errors/translate.js +1 -1
  83. package/dist/database/helpers/date/dialects/mssql.js +1 -1
  84. package/dist/database/helpers/date/dialects/mysql.js +1 -1
  85. package/dist/database/helpers/date/types.js +1 -1
  86. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
  87. package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
  88. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
  89. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  90. package/dist/database/helpers/schema/dialects/mysql.js +16 -3
  91. package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
  92. package/dist/database/helpers/schema/types.d.ts +13 -0
  93. package/dist/database/helpers/schema/types.js +24 -0
  94. package/dist/database/index.js +4 -4
  95. package/dist/database/migrations/20220429A-add-flows.js +1 -1
  96. package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
  97. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
  98. package/dist/database/migrations/20240204A-marketplace.js +9 -7
  99. package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
  100. package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
  101. package/dist/database/migrations/20240806A-permissions-policies.js +3 -3
  102. package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
  103. package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
  104. package/dist/database/migrations/20251103A-add-ai-settings.d.ts +3 -0
  105. package/dist/database/migrations/20251103A-add-ai-settings.js +14 -0
  106. package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
  107. package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
  108. package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
  109. package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
  110. package/dist/database/migrations/run.js +3 -3
  111. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  112. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
  113. package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
  114. package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
  115. package/dist/database/run-ast/run-ast.js +1 -1
  116. package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
  117. package/dist/database/run-ast/utils/get-column.js +1 -1
  118. package/dist/database/seeds/run.js +3 -3
  119. package/dist/extensions/lib/get-extensions-path.js +1 -1
  120. package/dist/extensions/lib/get-extensions-settings.js +1 -1
  121. package/dist/extensions/lib/get-extensions.js +1 -1
  122. package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
  123. package/dist/extensions/lib/installation/manager.js +8 -12
  124. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
  125. package/dist/extensions/lib/sync/status.d.ts +11 -0
  126. package/dist/extensions/lib/sync/status.js +34 -0
  127. package/dist/extensions/lib/sync/sync.d.ts +6 -0
  128. package/dist/extensions/lib/sync/sync.js +90 -0
  129. package/dist/extensions/lib/sync/tracker.d.ts +18 -0
  130. package/dist/extensions/lib/sync/tracker.js +71 -0
  131. package/dist/extensions/lib/sync/utils.d.ts +24 -0
  132. package/dist/extensions/lib/sync/utils.js +62 -0
  133. package/dist/extensions/manager.d.ts +9 -5
  134. package/dist/extensions/manager.js +36 -19
  135. package/dist/flows.d.ts +1 -1
  136. package/dist/logger/index.js +1 -1
  137. package/dist/logger/logs-stream.d.ts +1 -1
  138. package/dist/logger/logs-stream.js +1 -1
  139. package/dist/mailer.js +1 -1
  140. package/dist/metrics/lib/create-metrics.js +2 -2
  141. package/dist/middleware/authenticate.js +3 -3
  142. package/dist/middleware/collection-exists.js +1 -1
  143. package/dist/middleware/extract-token.js +1 -1
  144. package/dist/middleware/graphql.js +2 -2
  145. package/dist/middleware/respond.js +2 -2
  146. package/dist/middleware/validate-batch.js +1 -1
  147. package/dist/operations/exec/index.js +2 -1
  148. package/dist/operations/mail/index.js +1 -1
  149. package/dist/operations/mail/rate-limiter.js +2 -2
  150. package/dist/permissions/cache.js +5 -0
  151. package/dist/permissions/lib/fetch-policies.d.ts +1 -1
  152. package/dist/permissions/lib/fetch-roles-tree.d.ts +6 -3
  153. package/dist/permissions/lib/fetch-roles-tree.js +5 -27
  154. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
  155. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
  156. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +9 -7
  157. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +17 -9
  158. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
  159. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +1 -1
  160. package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
  161. package/dist/permissions/modules/process-ast/process-ast.js +1 -1
  162. package/dist/permissions/modules/process-payload/process-payload.js +1 -1
  163. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +13 -1
  164. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +54 -6
  165. package/dist/permissions/modules/validate-access/validate-access.js +3 -2
  166. package/dist/permissions/utils/fetch-raw-permissions.d.ts +1 -1
  167. package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
  168. package/dist/permissions/utils/fetch-share-info.js +1 -1
  169. package/dist/permissions/utils/filter-policies-by-ip.js +1 -1
  170. package/dist/permissions/utils/get-permissions-for-share.js +8 -8
  171. package/dist/permissions/utils/with-cache.d.ts +8 -6
  172. package/dist/permissions/utils/with-cache.js +12 -10
  173. package/dist/rate-limiter.js +1 -1
  174. package/dist/request/is-denied-ip.js +2 -2
  175. package/dist/schedules/project.js +1 -1
  176. package/dist/schedules/telemetry.js +1 -1
  177. package/dist/schedules/tus.js +1 -1
  178. package/dist/server.js +4 -4
  179. package/dist/services/assets/name-deduper.d.ts +7 -0
  180. package/dist/services/assets/name-deduper.js +23 -0
  181. package/dist/services/assets.d.ts +17 -3
  182. package/dist/services/assets.js +133 -13
  183. package/dist/services/authentication.js +6 -6
  184. package/dist/services/collections.js +1 -1
  185. package/dist/services/comments.js +2 -2
  186. package/dist/services/extensions.d.ts +1 -1
  187. package/dist/services/extensions.js +4 -0
  188. package/dist/services/files/utils/get-metadata.d.ts +1 -1
  189. package/dist/services/files/utils/get-metadata.js +1 -1
  190. package/dist/services/files.d.ts +1 -1
  191. package/dist/services/files.js +4 -4
  192. package/dist/services/folders.d.ts +27 -2
  193. package/dist/services/folders.js +75 -0
  194. package/dist/services/graphql/index.d.ts +1 -1
  195. package/dist/services/graphql/index.js +1 -1
  196. package/dist/services/graphql/resolvers/mutation.js +1 -1
  197. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  198. package/dist/services/graphql/schema/read.js +1 -1
  199. package/dist/services/graphql/subscription.d.ts +1 -1
  200. package/dist/services/graphql/types/date.js +1 -1
  201. package/dist/services/graphql/types/hash.js +1 -1
  202. package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
  203. package/dist/services/import-export.d.ts +2 -2
  204. package/dist/services/import-export.js +6 -7
  205. package/dist/services/index.d.ts +0 -1
  206. package/dist/services/index.js +0 -1
  207. package/dist/services/mail/index.js +2 -2
  208. package/dist/services/mail/rate-limiter.js +2 -2
  209. package/dist/services/notifications.js +2 -2
  210. package/dist/services/payload.js +21 -1
  211. package/dist/services/roles.js +2 -2
  212. package/dist/services/schema.js +1 -1
  213. package/dist/services/server.js +12 -4
  214. package/dist/services/settings.js +2 -2
  215. package/dist/services/tfa.js +1 -1
  216. package/dist/services/translations.js +1 -1
  217. package/dist/services/tus/data-store.d.ts +1 -3
  218. package/dist/services/tus/data-store.js +2 -5
  219. package/dist/services/tus/server.js +9 -9
  220. package/dist/services/users.js +4 -4
  221. package/dist/services/versions.js +1 -1
  222. package/dist/telemetry/lib/send-report.d.ts +1 -1
  223. package/dist/telemetry/lib/send-report.js +1 -1
  224. package/dist/telemetry/lib/track.js +1 -1
  225. package/dist/telemetry/utils/get-settings.d.ts +15 -0
  226. package/dist/telemetry/utils/get-settings.js +13 -1
  227. package/dist/test-utils/README.md +95 -24
  228. package/dist/test-utils/cache.d.ts +2 -2
  229. package/dist/test-utils/cache.js +2 -2
  230. package/dist/test-utils/knex.js +1 -1
  231. package/dist/test-utils/{fields-service.d.ts → services/fields-service.d.ts} +1 -1
  232. package/dist/test-utils/{fields-service.js → services/fields-service.js} +3 -2
  233. package/dist/test-utils/services/files-service.d.ts +28 -0
  234. package/dist/test-utils/services/files-service.js +34 -0
  235. package/dist/test-utils/services/folders-service.d.ts +28 -0
  236. package/dist/test-utils/services/folders-service.js +33 -0
  237. package/dist/types/collection.d.ts +1 -1
  238. package/dist/utils/async-handler.d.ts +1 -1
  239. package/dist/utils/calculate-field-depth.js +1 -1
  240. package/dist/utils/compress.js +1 -1
  241. package/dist/utils/deep-map-response.js +2 -2
  242. package/dist/utils/encrypt.d.ts +2 -0
  243. package/dist/utils/encrypt.js +64 -0
  244. package/dist/utils/get-accountability-for-role.js +2 -2
  245. package/dist/utils/get-accountability-for-token.js +4 -4
  246. package/dist/utils/get-cache-key.js +3 -3
  247. package/dist/utils/get-field-system-rows.js +1 -1
  248. package/dist/utils/get-ip-from-req.d.ts +1 -1
  249. package/dist/utils/get-ip-from-req.js +1 -1
  250. package/dist/utils/get-local-type.js +7 -3
  251. package/dist/utils/get-service.js +1 -3
  252. package/dist/utils/get-snapshot-diff.js +1 -1
  253. package/dist/utils/is-url-allowed.js +1 -1
  254. package/dist/utils/jwt.js +1 -1
  255. package/dist/utils/require-text.d.ts +1 -0
  256. package/dist/utils/require-text.js +4 -0
  257. package/dist/utils/require-yaml.js +2 -2
  258. package/dist/utils/sanitize-schema.d.ts +1 -1
  259. package/dist/utils/should-clear-cache.d.ts +1 -1
  260. package/dist/utils/should-skip-cache.js +2 -2
  261. package/dist/utils/validate-diff.js +1 -1
  262. package/dist/utils/validate-snapshot.js +3 -3
  263. package/dist/utils/validate-storage.js +2 -2
  264. package/dist/utils/verify-session-jwt.js +1 -1
  265. package/dist/utils/versioning/deep-map-with-schema.js +2 -2
  266. package/dist/websocket/controllers/base.d.ts +2 -2
  267. package/dist/websocket/controllers/base.js +3 -3
  268. package/dist/websocket/controllers/graphql.d.ts +1 -1
  269. package/dist/websocket/controllers/graphql.js +1 -1
  270. package/dist/websocket/controllers/logs.d.ts +1 -1
  271. package/dist/websocket/controllers/rest.d.ts +1 -1
  272. package/dist/websocket/controllers/rest.js +2 -2
  273. package/dist/websocket/handlers/heartbeat.js +1 -1
  274. package/dist/websocket/handlers/items.js +2 -2
  275. package/dist/websocket/handlers/subscribe.js +1 -1
  276. package/dist/websocket/types.d.ts +1 -1
  277. package/dist/websocket/utils/wait-for-message.js +1 -1
  278. package/package.json +34 -28
  279. package/dist/controllers/webhooks.d.ts +0 -2
  280. package/dist/controllers/webhooks.js +0 -74
  281. package/dist/extensions/lib/sync-extensions.d.ts +0 -3
  282. package/dist/extensions/lib/sync-extensions.js +0 -70
  283. package/dist/extensions/lib/sync-status.d.ts +0 -10
  284. package/dist/extensions/lib/sync-status.js +0 -27
  285. package/dist/mcp/tools/index.d.ts +0 -15
  286. package/dist/mcp/tools/index.js +0 -29
  287. package/dist/mcp/tools/prompts/index.d.ts +0 -16
  288. package/dist/mcp/tools/prompts/index.js +0 -19
  289. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
  290. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
  291. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
  292. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
  293. package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
  294. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
  295. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
  296. package/dist/services/webhooks.d.ts +0 -14
  297. package/dist/services/webhooks.js +0 -32
  298. package/dist/utils/get-date-formatted.d.ts +0 -1
  299. package/dist/utils/get-date-formatted.js +0 -10
  300. package/dist/utils/ip-in-networks.d.ts +0 -6
  301. package/dist/utils/ip-in-networks.js +0 -13
  302. package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
  303. /package/dist/{mcp → ai/mcp}/index.d.ts +0 -0
  304. /package/dist/{mcp → ai/mcp}/index.js +0 -0
  305. /package/dist/{mcp → ai/mcp}/transport.d.ts +0 -0
  306. /package/dist/{mcp → ai/mcp}/transport.js +0 -0
  307. /package/dist/{mcp → ai/mcp}/types.js +0 -0
  308. /package/dist/{mcp/tools/assets.d.ts → ai/tools/assets/index.d.ts} +0 -0
  309. /package/dist/{mcp/tools/prompts/assets.md → ai/tools/assets/prompt.md} +0 -0
  310. /package/dist/{mcp/tools/collections.d.ts → ai/tools/collections/index.d.ts} +0 -0
  311. /package/dist/{mcp/tools/prompts/collections.md → ai/tools/collections/prompt.md} +0 -0
  312. /package/dist/{mcp/define.d.ts → ai/tools/define-tool.d.ts} +0 -0
  313. /package/dist/{mcp/define.js → ai/tools/define-tool.js} +0 -0
  314. /package/dist/{mcp/tools/fields.d.ts → ai/tools/fields/index.d.ts} +0 -0
  315. /package/dist/{mcp/tools/prompts/fields.md → ai/tools/fields/prompt.md} +0 -0
  316. /package/dist/{mcp/tools/files.d.ts → ai/tools/files/index.d.ts} +0 -0
  317. /package/dist/{mcp/tools/prompts/files.md → ai/tools/files/prompt.md} +0 -0
  318. /package/dist/{mcp/tools/flows.d.ts → ai/tools/flows/index.d.ts} +0 -0
  319. /package/dist/{mcp/tools/prompts/flows.md → ai/tools/flows/prompt.md} +0 -0
  320. /package/dist/{mcp/tools/folders.d.ts → ai/tools/folders/index.d.ts} +0 -0
  321. /package/dist/{mcp/tools/prompts/folders.md → ai/tools/folders/prompt.md} +0 -0
  322. /package/dist/{mcp/tools/items.d.ts → ai/tools/items/index.d.ts} +0 -0
  323. /package/dist/{mcp/tools/prompts/operations.md → ai/tools/operations/prompt.md} +0 -0
  324. /package/dist/{mcp/tools/relations.d.ts → ai/tools/relations/index.d.ts} +0 -0
  325. /package/dist/{mcp/tools/prompts/relations.md → ai/tools/relations/prompt.md} +0 -0
  326. /package/dist/{mcp/tools/prompts/schema.md → ai/tools/schema/prompt.md} +0 -0
  327. /package/dist/{mcp → ai/tools}/schema.d.ts +0 -0
  328. /package/dist/{mcp → ai/tools}/schema.js +0 -0
  329. /package/dist/{mcp/tools/system.d.ts → ai/tools/system/index.d.ts} +0 -0
  330. /package/dist/{mcp/tools/prompts/system-prompt-description.md → ai/tools/system/prompt-description.md} +0 -0
  331. /package/dist/{mcp/tools/prompts/system-prompt.md → ai/tools/system/prompt.md} +0 -0
  332. /package/dist/{mcp/tools/trigger-flow.d.ts → ai/tools/trigger-flow/index.d.ts} +0 -0
  333. /package/dist/{mcp/tools/prompts/trigger-flow.md → ai/tools/trigger-flow/prompt.md} +0 -0
  334. /package/dist/{permissions/modules/fetch-global-access → ai/tools}/types.js +0 -0
  335. /package/dist/test-utils/{items-service.d.ts → services/items-service.d.ts} +0 -0
  336. /package/dist/test-utils/{items-service.js → services/items-service.js} +0 -0
@@ -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,6 +1,6 @@
1
1
  import type { Accountability, Permission, PermissionsAction } from '@directus/types';
2
2
  import type { Context } from '../types.js';
3
- export declare const fetchRawPermissions: typeof _fetchRawPermissions;
3
+ export declare const fetchRawPermissions: (options: FetchRawPermissionsOptions, context: Context) => Promise<Permission[]>;
4
4
  export interface FetchRawPermissionsOptions {
5
5
  action?: PermissionsAction;
6
6
  policies: string[];
@@ -8,5 +8,5 @@ export interface ShareInfo {
8
8
  role: string;
9
9
  };
10
10
  }
11
- export declare const fetchShareInfo: typeof _fetchShareInfo;
11
+ export declare const fetchShareInfo: (shareId: string, context: AbstractServiceOptions) => Promise<ShareInfo>;
12
12
  export declare function _fetchShareInfo(shareId: string, context: AbstractServiceOptions): Promise<ShareInfo>;
@@ -1,5 +1,5 @@
1
1
  import { withCache } from './with-cache.js';
2
- export const fetchShareInfo = withCache('share-info', _fetchShareInfo);
2
+ export const fetchShareInfo = withCache('share-info', _fetchShareInfo, (shareId) => ({ shareId }));
3
3
  export async function _fetchShareInfo(shareId, context) {
4
4
  const { SharesService } = await import('../../services/shares.js');
5
5
  const sharesService = new SharesService(context);
@@ -1,4 +1,4 @@
1
- import { ipInNetworks } from '../../utils/ip-in-networks.js';
1
+ import { ipInNetworks } from '@directus/utils/node';
2
2
  export function filterPoliciesByIp(policies, ip) {
3
3
  return policies.filter(({ policy }) => {
4
4
  // Keep policies that don't have an ip address allow list configured
@@ -1,13 +1,13 @@
1
1
  import { schemaPermissions } from '@directus/system-data';
2
2
  import { set, uniq } from 'lodash-es';
3
- import { fetchAllowedFieldMap } from '../modules/fetch-allowed-field-map/fetch-allowed-field-map.js';
4
- import { fetchShareInfo } from './fetch-share-info.js';
5
- import { mergePermissions } from './merge-permissions.js';
3
+ import { reduceSchema } from '../../utils/reduce-schema.js';
6
4
  import { fetchPermissions } from '../lib/fetch-permissions.js';
7
5
  import { fetchPolicies } from '../lib/fetch-policies.js';
8
6
  import { fetchRolesTree } from '../lib/fetch-roles-tree.js';
9
- import { reduceSchema } from '../../utils/reduce-schema.js';
7
+ import { fetchAllowedFieldMap } from '../modules/fetch-allowed-field-map/fetch-allowed-field-map.js';
10
8
  import { fetchGlobalAccess } from '../modules/fetch-global-access/fetch-global-access.js';
9
+ import { fetchShareInfo } from './fetch-share-info.js';
10
+ import { mergePermissions } from './merge-permissions.js';
11
11
  export async function getPermissionsForShare(accountability, collections, context) {
12
12
  const defaults = {
13
13
  action: 'read',
@@ -22,7 +22,7 @@ export async function getPermissionsForShare(accountability, collections, contex
22
22
  const userAccountability = {
23
23
  user: user_created.id,
24
24
  role: user_created.role,
25
- roles: await fetchRolesTree(user_created.role, context.knex),
25
+ roles: await fetchRolesTree(user_created.role, { knex: context.knex }),
26
26
  admin: false,
27
27
  app: false,
28
28
  ip: accountability.ip,
@@ -31,14 +31,14 @@ export async function getPermissionsForShare(accountability, collections, contex
31
31
  const shareAccountability = {
32
32
  user: null,
33
33
  role: role,
34
- roles: await fetchRolesTree(role, context.knex),
34
+ roles: await fetchRolesTree(role, { knex: context.knex }),
35
35
  admin: false,
36
36
  app: false,
37
37
  ip: accountability.ip,
38
38
  };
39
39
  const [{ admin: shareIsAdmin }, { admin: userIsAdmin }, userPermissions, sharePermissions, shareFieldMap, userFieldMap,] = await Promise.all([
40
- fetchGlobalAccess(shareAccountability, context.knex),
41
- fetchGlobalAccess(userAccountability, context.knex),
40
+ fetchGlobalAccess(shareAccountability, { knex: context.knex }),
41
+ fetchGlobalAccess(userAccountability, { knex: context.knex }),
42
42
  getPermissionsForAccountability(userAccountability, context),
43
43
  getPermissionsForAccountability(shareAccountability, context),
44
44
  fetchAllowedFieldMap({
@@ -1,10 +1,12 @@
1
1
  /**
2
- * The `pick` parameter can be used to stabilize cache keys, by only using a subset of the available parameters and
3
- * ensuring key order.
2
+ * Wraps a function with caching capabilities.
4
3
  *
5
- * If the `pick` function is provided, we pass the picked result to the handler, in order for TypeScript to ensure that
6
- * the function only relies on the parameters that are used for generating the cache key.
4
+ * @param namespace - A unique namespace for the cache key.
5
+ * @param handler - The function to be wrapped.
6
+ * @param prepareArg - Optional function to prepare arguments for hashing.
7
+ * @returns A new function that caches the results of the original function.
7
8
  *
8
- * @NOTE only uses the first parameter for memoization
9
+ * @NOTE Ensure that the `namespace` is unique to avoid cache key collisions.
10
+ * @NOTE Ensure that the `prepareArg` function returns a JSON stringifiable representation of the arguments.
9
11
  */
10
- export declare function withCache<F extends (arg0: Arg0, ...args: any[]) => R, R, Arg0 = Parameters<F>[0]>(namespace: string, handler: F, prepareArg?: (arg0: Arg0) => Arg0): F;
12
+ export declare function withCache<F extends (...args: any) => any>(namespace: string, handler: F, prepareArg?: (...args: Parameters<F>) => Record<string, unknown>): (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
@@ -1,25 +1,27 @@
1
1
  import { getSimpleHash } from '@directus/utils';
2
2
  import { useCache } from '../cache.js';
3
3
  /**
4
- * The `pick` parameter can be used to stabilize cache keys, by only using a subset of the available parameters and
5
- * ensuring key order.
4
+ * Wraps a function with caching capabilities.
6
5
  *
7
- * If the `pick` function is provided, we pass the picked result to the handler, in order for TypeScript to ensure that
8
- * the function only relies on the parameters that are used for generating the cache key.
6
+ * @param namespace - A unique namespace for the cache key.
7
+ * @param handler - The function to be wrapped.
8
+ * @param prepareArg - Optional function to prepare arguments for hashing.
9
+ * @returns A new function that caches the results of the original function.
9
10
  *
10
- * @NOTE only uses the first parameter for memoization
11
+ * @NOTE Ensure that the `namespace` is unique to avoid cache key collisions.
12
+ * @NOTE Ensure that the `prepareArg` function returns a JSON stringifiable representation of the arguments.
11
13
  */
12
14
  export function withCache(namespace, handler, prepareArg) {
13
15
  const cache = useCache();
14
- return (async (arg0, ...args) => {
15
- arg0 = prepareArg ? prepareArg(arg0) : arg0;
16
- const key = namespace + '-' + getSimpleHash(JSON.stringify(arg0));
16
+ return async (...args) => {
17
+ const hashArgs = prepareArg ? prepareArg(...args) : args;
18
+ const key = namespace + '-' + getSimpleHash(JSON.stringify(hashArgs));
17
19
  const cached = await cache.get(key);
18
20
  if (cached !== undefined) {
19
21
  return cached;
20
22
  }
21
- const res = await handler(arg0, ...args);
23
+ const res = await handler(...args);
22
24
  cache.set(key, res);
23
25
  return res;
24
- });
26
+ };
25
27
  }
@@ -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,8 +1,8 @@
1
- import { useEnv } from '@directus/env';
2
1
  import os from 'node:os';
2
+ import { useEnv } from '@directus/env';
3
+ import { ipInNetworks } from '@directus/utils/node';
3
4
  import { matches } from 'ip-matching';
4
5
  import { useLogger } from '../logger/index.js';
5
- import { ipInNetworks } from '../utils/ip-in-networks.js';
6
6
  export function isDeniedIp(ip) {
7
7
  const env = useEnv();
8
8
  const logger = useLogger();
@@ -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;
@@ -0,0 +1,7 @@
1
+ export declare class NameDeduper {
2
+ private map;
3
+ add(name?: string | null, options?: {
4
+ group?: string | null;
5
+ fallback?: string;
6
+ }): string;
7
+ }
@@ -0,0 +1,23 @@
1
+ import sanitize from 'sanitize-filename';
2
+ const DEFAULT_GROUP = Symbol('undefined');
3
+ export class NameDeduper {
4
+ map = {};
5
+ add(name, options) {
6
+ name = sanitize(name ?? '') || options?.fallback;
7
+ if (!name) {
8
+ throw Error('Invalid "name" provided');
9
+ }
10
+ const groupKey = options?.group ?? DEFAULT_GROUP;
11
+ const match = this.map[groupKey]?.[name];
12
+ if (match) {
13
+ const dedupedName = `${name} (${match})`;
14
+ this.map[groupKey][name] += 1;
15
+ return dedupedName;
16
+ }
17
+ if (!this.map[groupKey]) {
18
+ this.map[groupKey] = {};
19
+ }
20
+ this.map[groupKey][name] = 1;
21
+ return name;
22
+ }
23
+ }
@@ -1,13 +1,27 @@
1
- import type { AbstractServiceOptions, Accountability, Range, Stat, SchemaOverview, TransformationSet } from '@directus/types';
2
- import type { Knex } from 'knex';
3
1
  import type { Readable } from 'node:stream';
2
+ import type { AbstractServiceOptions, Accountability, Range, SchemaOverview, Stat, TransformationSet } from '@directus/types';
3
+ import archiver from 'archiver';
4
+ import type { Knex } from 'knex';
4
5
  import { FilesService } from './files.js';
5
6
  export declare class AssetsService {
6
7
  knex: Knex;
7
8
  accountability: Accountability | null;
8
9
  schema: SchemaOverview;
9
- filesService: FilesService;
10
+ sudoFilesService: FilesService;
10
11
  constructor(options: AbstractServiceOptions);
12
+ private sanitizeFields;
13
+ private zip;
14
+ zipFiles(files: string[]): Promise<{
15
+ archive: archiver.Archiver;
16
+ complete: () => Promise<void>;
17
+ }>;
18
+ zipFolder(root: string): Promise<{
19
+ archive: archiver.Archiver;
20
+ complete: () => Promise<void>;
21
+ metadata: {
22
+ name: string | undefined;
23
+ };
24
+ }>;
11
25
  getAsset(id: string, transformation?: TransformationSet, range?: Range, deferStream?: false): Promise<{
12
26
  stream: Readable;
13
27
  file: any;
@@ -1,32 +1,139 @@
1
+ import path from 'path';
1
2
  import { useEnv } from '@directus/env';
2
- import { ForbiddenError, IllegalAssetTransformationError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
3
+ import { ForbiddenError, IllegalAssetTransformationError, InvalidPayloadError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
4
+ import archiver from 'archiver';
3
5
  import { clamp } from 'lodash-es';
4
- import { contentType } from 'mime-types';
6
+ import { contentType, extension } from 'mime-types';
5
7
  import hash from 'object-hash';
6
- import path from 'path';
7
8
  import sharp from 'sharp';
8
9
  import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
9
10
  import getDatabase from '../database/index.js';
10
11
  import { useLogger } from '../logger/index.js';
11
- import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
12
+ import { validateItemAccess } from '../permissions/modules/validate-access/lib/validate-item-access.js';
12
13
  import { getStorage } from '../storage/index.js';
13
14
  import { getMilliseconds } from '../utils/get-milliseconds.js';
14
15
  import { isValidUuid } from '../utils/is-valid-uuid.js';
15
16
  import * as TransformationUtils from '../utils/transformations.js';
16
- import { FilesService } from './files.js';
17
+ import { NameDeduper } from './assets/name-deduper.js';
17
18
  import { getSharpInstance } from './files/lib/get-sharp-instance.js';
19
+ import { FilesService } from './files.js';
20
+ import { FoldersService } from './folders.js';
18
21
  const env = useEnv();
19
22
  const logger = useLogger();
20
23
  export class AssetsService {
21
24
  knex;
22
25
  accountability;
23
26
  schema;
24
- filesService;
27
+ sudoFilesService;
25
28
  constructor(options) {
26
29
  this.knex = options.knex || getDatabase();
27
30
  this.accountability = options.accountability || null;
28
31
  this.schema = options.schema;
29
- this.filesService = new FilesService({ ...options, accountability: null });
32
+ this.sudoFilesService = new FilesService({ ...options, accountability: null });
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
+ }
48
+ zip(options) {
49
+ if (options.files.length === 0) {
50
+ throw new InvalidPayloadError({ reason: 'No files found in the selected folders tree' });
51
+ }
52
+ const archive = archiver('zip');
53
+ const complete = async () => {
54
+ const deduper = new NameDeduper();
55
+ const storage = await getStorage();
56
+ for (const { id, folder, filename_download } of options.files) {
57
+ const file = await this.sudoFilesService.readOne(id, {
58
+ fields: ['id', 'storage', 'filename_disk', 'filename_download', 'modified_on', 'type'],
59
+ });
60
+ const exists = await storage.location(file.storage).exists(file.filename_disk);
61
+ if (!exists)
62
+ throw new ForbiddenError();
63
+ const version = file.modified_on ? (new Date(file.modified_on).getTime() / 1000).toFixed() : undefined;
64
+ const assetStream = await storage.location(file.storage).read(file.filename_disk, { version });
65
+ const fileExtension = path.extname(file.filename_download) || (file.type && '.' + extension(file.type)) || '';
66
+ const dedupedFileName = deduper.add(filename_download, { group: folder, fallback: file.id + fileExtension });
67
+ const folderName = folder ? options.folders?.get(folder) : undefined;
68
+ archive.append(assetStream, { name: dedupedFileName, prefix: folderName });
69
+ }
70
+ // add any empty folders, does not override already filled folder
71
+ if (options.folders) {
72
+ for (const [, folder] of options.folders) {
73
+ archive.append('', { name: folder + '/' });
74
+ }
75
+ }
76
+ await archive.finalize();
77
+ };
78
+ return { archive, complete };
79
+ }
80
+ async zipFiles(files) {
81
+ const filesService = new FilesService({
82
+ schema: this.schema,
83
+ knex: this.knex,
84
+ accountability: this.accountability,
85
+ });
86
+ const filesToZip = await filesService.readByQuery({
87
+ filter: {
88
+ id: {
89
+ _in: files,
90
+ },
91
+ },
92
+ limit: -1,
93
+ });
94
+ return this.zip({
95
+ files: filesToZip.map((file) => ({
96
+ id: file['id'],
97
+ folder: file['folder'],
98
+ filename_download: file['filename_download'],
99
+ })),
100
+ });
101
+ }
102
+ async zipFolder(root) {
103
+ const foldersService = new FoldersService({
104
+ schema: this.schema,
105
+ knex: this.knex,
106
+ accountability: this.accountability,
107
+ });
108
+ const folderTree = await foldersService.buildTree(root);
109
+ const filesService = new FilesService({
110
+ schema: this.schema,
111
+ knex: this.knex,
112
+ accountability: this.accountability,
113
+ });
114
+ const filesToZip = await filesService.readByQuery({
115
+ filter: {
116
+ folder: {
117
+ _in: Array.from(folderTree.keys()),
118
+ },
119
+ },
120
+ limit: -1,
121
+ });
122
+ const { archive, complete } = this.zip({
123
+ folders: folderTree,
124
+ files: filesToZip.map((file) => ({
125
+ id: file['id'],
126
+ folder: file['folder'],
127
+ filename_download: file['filename_download'],
128
+ })),
129
+ });
130
+ return {
131
+ archive,
132
+ complete,
133
+ metadata: {
134
+ name: folderTree.get(root),
135
+ },
136
+ };
30
137
  }
31
138
  async getAsset(id, transformation, range, deferStream = false) {
32
139
  const storage = await getStorage();
@@ -42,15 +149,24 @@ export class AssetsService {
42
149
  */
43
150
  if (!isValidUuid(id))
44
151
  throw new ForbiddenError();
45
- if (systemPublicKeys.includes(id) === false && this.accountability) {
46
- 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({
47
156
  accountability: this.accountability,
48
157
  action: 'read',
49
158
  collection: 'directus_files',
50
159
  primaryKeys: [id],
160
+ returnAllowedRootFields: true,
51
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;
52
168
  }
53
- const file = (await this.filesService.readOne(id, { limit: 1 }));
169
+ const file = (await this.sudoFilesService.readOne(id, { limit: 1 }));
54
170
  const exists = await storage.location(file.storage).exists(file.filename_disk);
55
171
  if (!exists)
56
172
  throw new ForbiddenError();
@@ -102,7 +218,7 @@ export class AssetsService {
102
218
  const assetStream = () => storage.location(file.storage).read(assetFilename, { range });
103
219
  return {
104
220
  stream: deferStream ? assetStream : await assetStream(),
105
- file,
221
+ file: this.sanitizeFields(file, allowedFields),
106
222
  stat: await storage.location(file.storage).stat(assetFilename),
107
223
  };
108
224
  }
@@ -167,13 +283,17 @@ export class AssetsService {
167
283
  return {
168
284
  stream: deferStream ? assetStream : await assetStream(),
169
285
  stat: await storage.location(file.storage).stat(assetFilename),
170
- file,
286
+ file: this.sanitizeFields(file, allowedFields),
171
287
  };
172
288
  }
173
289
  else {
174
290
  const assetStream = () => storage.location(file.storage).read(file.filename_disk, { range, version });
175
291
  const stat = await storage.location(file.storage).stat(file.filename_disk);
176
- 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
+ };
177
297
  }
178
298
  }
179
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';
@@ -149,8 +149,8 @@ export class AuthenticationService {
149
149
  throw new InvalidOtpError();
150
150
  }
151
151
  }
152
- const roles = await fetchRolesTree(user.role, this.knex);
153
- const globalAccess = await fetchGlobalAccess({ roles, user: user.id, ip: this.accountability?.ip ?? null }, this.knex);
152
+ const roles = await fetchRolesTree(user.role, { knex: this.knex });
153
+ const globalAccess = await fetchGlobalAccess({ roles, user: user.id, ip: this.accountability?.ip ?? null }, { knex: this.knex });
154
154
  const tokenPayload = {
155
155
  id: user.id,
156
156
  role: user.role,
@@ -277,8 +277,8 @@ export class AuthenticationService {
277
277
  throw new InvalidCredentialsError();
278
278
  }
279
279
  }
280
- const roles = await fetchRolesTree(record.user_role, this.knex);
281
- const globalAccess = await fetchGlobalAccess({ user: record.user_id, roles, ip: this.accountability?.ip ?? null }, this.knex);
280
+ const roles = await fetchRolesTree(record.user_role, { knex: this.knex });
281
+ const globalAccess = await fetchGlobalAccess({ user: record.user_id, roles, ip: this.accountability?.ip ?? null }, { knex: this.knex });
282
282
  if (record.user_id) {
283
283
  const provider = getAuthProvider(record.user_provider);
284
284
  await provider.refresh({
@@ -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;