@directus/api 15.0.0 → 17.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 (185) hide show
  1. package/dist/app.js +6 -4
  2. package/dist/auth/drivers/ldap.js +7 -4
  3. package/dist/auth/drivers/local.js +3 -2
  4. package/dist/auth/drivers/oauth2.js +9 -2
  5. package/dist/auth/drivers/openid.js +9 -2
  6. package/dist/auth/drivers/saml.js +6 -4
  7. package/dist/auth.js +7 -4
  8. package/dist/bus/index.d.ts +1 -0
  9. package/dist/bus/index.js +1 -0
  10. package/dist/bus/lib/use-bus.d.ts +9 -0
  11. package/dist/bus/lib/use-bus.js +21 -0
  12. package/dist/cache.js +9 -9
  13. package/dist/cli/commands/bootstrap/index.js +6 -2
  14. package/dist/cli/commands/count/index.js +2 -1
  15. package/dist/cli/commands/database/install.js +2 -1
  16. package/dist/cli/commands/database/migrate.js +2 -1
  17. package/dist/cli/commands/roles/create.js +2 -1
  18. package/dist/cli/commands/schema/apply.js +2 -1
  19. package/dist/cli/commands/schema/snapshot.js +6 -5
  20. package/dist/cli/commands/users/create.js +4 -3
  21. package/dist/cli/commands/users/passwd.js +5 -4
  22. package/dist/cli/load-extensions.js +4 -2
  23. package/dist/cli/utils/create-env/env-stub.liquid +1 -1
  24. package/dist/constants.d.ts +1 -1
  25. package/dist/constants.js +4 -1
  26. package/dist/controllers/assets.js +5 -3
  27. package/dist/controllers/auth.js +5 -4
  28. package/dist/controllers/extensions.js +18 -6
  29. package/dist/controllers/files.js +3 -3
  30. package/dist/controllers/permissions.js +11 -2
  31. package/dist/controllers/schema.js +3 -2
  32. package/dist/controllers/shares.js +3 -3
  33. package/dist/controllers/utils.js +13 -32
  34. package/dist/database/helpers/index.d.ts +1 -1
  35. package/dist/database/index.js +9 -2
  36. package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +3 -1
  37. package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -1
  38. package/dist/database/migrations/20210802A-replace-groups.js +2 -1
  39. package/dist/database/migrations/20230721A-require-shares-fields.js +2 -1
  40. package/dist/database/migrations/20231215A-add-focalpoints.d.ts +3 -0
  41. package/dist/database/migrations/20231215A-add-focalpoints.js +12 -0
  42. package/dist/database/migrations/run.js +2 -1
  43. package/dist/database/run-ast.js +5 -2
  44. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -7
  45. package/dist/database/system-data/fields/files.yaml +16 -0
  46. package/dist/emitter.js +3 -1
  47. package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
  48. package/dist/extensions/lib/get-extensions-path.js +2 -1
  49. package/dist/extensions/lib/get-extensions.d.ts +1 -1
  50. package/dist/extensions/lib/get-extensions.js +32 -8
  51. package/dist/extensions/lib/get-shared-deps-mapping.js +6 -4
  52. package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
  53. package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
  54. package/dist/extensions/lib/sync-extensions.js +6 -4
  55. package/dist/extensions/manager.js +43 -19
  56. package/dist/flows.js +13 -7
  57. package/dist/logger.d.ts +7 -7
  58. package/dist/logger.js +116 -92
  59. package/dist/mailer.js +4 -2
  60. package/dist/middleware/cache.js +4 -2
  61. package/dist/middleware/check-ip.js +25 -6
  62. package/dist/middleware/cors.js +2 -1
  63. package/dist/middleware/error-handler.js +5 -5
  64. package/dist/middleware/rate-limiter-global.js +4 -2
  65. package/dist/middleware/rate-limiter-ip.js +2 -1
  66. package/dist/middleware/respond.js +5 -3
  67. package/dist/operations/log/index.js +2 -1
  68. package/dist/rate-limiter.d.ts +2 -1
  69. package/dist/rate-limiter.js +5 -2
  70. package/dist/redis/index.d.ts +3 -2
  71. package/dist/redis/index.js +3 -2
  72. package/dist/redis/{create-redis.js → lib/create-redis.js} +2 -2
  73. package/dist/redis/utils/redis-config-available.d.ts +4 -0
  74. package/dist/redis/utils/redis-config-available.js +8 -0
  75. package/dist/request/request-interceptor.js +7 -5
  76. package/dist/request/response-interceptor.js +2 -2
  77. package/dist/request/validate-ip.d.ts +1 -1
  78. package/dist/request/validate-ip.js +23 -7
  79. package/dist/server.js +11 -7
  80. package/dist/services/activity.js +5 -4
  81. package/dist/services/assets.d.ts +2 -0
  82. package/dist/services/assets.js +9 -6
  83. package/dist/services/authentication.js +17 -9
  84. package/dist/services/authorization.d.ts +1 -1
  85. package/dist/services/authorization.js +15 -3
  86. package/dist/services/collections.js +5 -4
  87. package/dist/services/extensions.d.ts +15 -9
  88. package/dist/services/extensions.js +74 -39
  89. package/dist/services/fields.js +9 -4
  90. package/dist/services/files.d.ts +2 -2
  91. package/dist/services/files.js +22 -14
  92. package/dist/services/graphql/index.js +46 -3
  93. package/dist/services/graphql/subscription.js +2 -2
  94. package/dist/services/graphql/types/bigint.js +16 -5
  95. package/dist/services/graphql/utils/process-error.d.ts +4 -1
  96. package/dist/services/graphql/utils/process-error.js +10 -8
  97. package/dist/services/{import-export/index.d.ts → import-export.d.ts} +1 -1
  98. package/dist/services/{import-export/index.js → import-export.js} +14 -12
  99. package/dist/services/index.d.ts +1 -1
  100. package/dist/services/index.js +1 -1
  101. package/dist/services/items.js +12 -8
  102. package/dist/services/mail/index.js +4 -2
  103. package/dist/services/notifications.js +7 -3
  104. package/dist/services/permissions.d.ts +3 -2
  105. package/dist/services/permissions.js +76 -1
  106. package/dist/services/relations.js +19 -10
  107. package/dist/services/roles.js +83 -15
  108. package/dist/services/server.js +7 -5
  109. package/dist/services/shares.js +3 -2
  110. package/dist/services/specifications.js +2 -1
  111. package/dist/services/users.js +20 -9
  112. package/dist/services/versions.js +6 -5
  113. package/dist/services/webhooks.d.ts +2 -2
  114. package/dist/services/webhooks.js +2 -2
  115. package/dist/services/websocket.d.ts +1 -1
  116. package/dist/services/websocket.js +4 -3
  117. package/dist/storage/register-drivers.js +2 -1
  118. package/dist/storage/register-locations.js +2 -1
  119. package/dist/synchronization.js +3 -1
  120. package/dist/telemetry/lib/get-report.js +1 -1
  121. package/dist/telemetry/lib/init-telemetry.js +2 -2
  122. package/dist/telemetry/lib/send-report.js +1 -1
  123. package/dist/telemetry/lib/track.js +2 -3
  124. package/dist/telemetry/utils/get-user-count.js +1 -1
  125. package/dist/types/assets.d.ts +2 -0
  126. package/dist/types/items.d.ts +4 -12
  127. package/dist/types/items.js +0 -4
  128. package/dist/utils/apply-diff.js +2 -1
  129. package/dist/utils/apply-query.js +0 -11
  130. package/dist/utils/delete-from-require-cache.js +2 -1
  131. package/dist/utils/get-accountability-for-token.js +3 -2
  132. package/dist/utils/get-auth-providers.js +2 -1
  133. package/dist/utils/get-cache-headers.js +5 -2
  134. package/dist/utils/get-config-from-env.js +2 -1
  135. package/dist/utils/get-default-value.js +4 -3
  136. package/dist/utils/get-ip-from-req.js +4 -2
  137. package/dist/utils/get-permissions.js +5 -3
  138. package/dist/utils/get-schema.js +5 -2
  139. package/dist/utils/get-snapshot-diff.js +7 -9
  140. package/dist/utils/get-snapshot.js +4 -4
  141. package/dist/utils/ip-in-networks.d.ts +6 -0
  142. package/dist/utils/ip-in-networks.js +13 -0
  143. package/dist/utils/is-url-allowed.js +2 -1
  144. package/dist/utils/job-queue.d.ts +1 -0
  145. package/dist/utils/job-queue.js +3 -0
  146. package/dist/utils/sanitize-query.js +7 -2
  147. package/dist/utils/sanitize-schema.d.ts +1 -1
  148. package/dist/utils/should-clear-cache.js +2 -1
  149. package/dist/utils/should-skip-cache.js +2 -1
  150. package/dist/utils/transformations.js +95 -12
  151. package/dist/utils/validate-env.js +4 -2
  152. package/dist/utils/validate-query.js +7 -3
  153. package/dist/utils/validate-storage.js +4 -2
  154. package/dist/webhooks.js +4 -3
  155. package/dist/websocket/controllers/base.js +12 -6
  156. package/dist/websocket/controllers/graphql.js +4 -2
  157. package/dist/websocket/controllers/hooks.js +3 -2
  158. package/dist/websocket/controllers/index.js +4 -2
  159. package/dist/websocket/controllers/rest.js +4 -2
  160. package/dist/websocket/errors.js +2 -1
  161. package/dist/websocket/handlers/heartbeat.js +4 -3
  162. package/dist/websocket/handlers/subscribe.d.ts +2 -2
  163. package/dist/websocket/handlers/subscribe.js +5 -4
  164. package/package.json +57 -57
  165. package/dist/__utils__/items-utils.d.ts +0 -2
  166. package/dist/__utils__/items-utils.js +0 -31
  167. package/dist/__utils__/mock-env.d.ts +0 -18
  168. package/dist/__utils__/mock-env.js +0 -41
  169. package/dist/__utils__/schemas.d.ts +0 -13
  170. package/dist/__utils__/schemas.js +0 -301
  171. package/dist/__utils__/snapshots.d.ts +0 -5
  172. package/dist/__utils__/snapshots.js +0 -903
  173. package/dist/env.d.ts +0 -14
  174. package/dist/env.js +0 -511
  175. package/dist/messenger.d.ts +0 -24
  176. package/dist/messenger.js +0 -64
  177. package/dist/services/import-export/import-worker.d.ts +0 -9
  178. package/dist/services/import-export/import-worker.js +0 -9
  179. package/dist/utils/to-boolean.d.ts +0 -4
  180. package/dist/utils/to-boolean.js +0 -6
  181. package/dist/worker-pool.d.ts +0 -2
  182. package/dist/worker-pool.js +0 -19
  183. /package/dist/redis/{create-redis.d.ts → lib/create-redis.d.ts} +0 -0
  184. /package/dist/redis/{use-redis.d.ts → lib/use-redis.d.ts} +0 -0
  185. /package/dist/redis/{use-redis.js → lib/use-redis.js} +0 -0
@@ -1,9 +1,9 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { parse as parseBytesConfiguration } from 'bytes';
2
3
  import { assign } from 'lodash-es';
3
4
  import { getCache, setCacheValue } from '../cache.js';
4
- import env from '../env.js';
5
- import logger from '../logger.js';
6
- import { ExportService } from '../services/import-export/index.js';
5
+ import { useLogger } from '../logger.js';
6
+ import { ExportService } from '../services/import-export.js';
7
7
  import { VersionsService } from '../services/versions.js';
8
8
  import asyncHandler from '../utils/async-handler.js';
9
9
  import { getCacheControlHeader } from '../utils/get-cache-headers.js';
@@ -12,6 +12,8 @@ import { getDateFormatted } from '../utils/get-date-formatted.js';
12
12
  import { getMilliseconds } from '../utils/get-milliseconds.js';
13
13
  import { stringByteSize } from '../utils/get-string-byte-size.js';
14
14
  export const respond = asyncHandler(async (req, res) => {
15
+ const env = useEnv();
16
+ const logger = useLogger();
15
17
  const { cache } = getCache();
16
18
  let exceedsMaxSize = false;
17
19
  if (env['CACHE_VALUE_MAX_SIZE'] !== false) {
@@ -1,9 +1,10 @@
1
1
  import { defineOperationApi } from '@directus/extensions';
2
2
  import { optionToString } from '@directus/utils';
3
- import logger from '../../logger.js';
3
+ import { useLogger } from '../../logger.js';
4
4
  export default defineOperationApi({
5
5
  id: 'log',
6
6
  handler: ({ message }) => {
7
+ const logger = useLogger();
7
8
  logger.info(optionToString(message));
8
9
  },
9
10
  });
@@ -1,4 +1,5 @@
1
1
  import type { IRateLimiterOptions, IRateLimiterStoreOptions, RateLimiterAbstract } from 'rate-limiter-flexible';
2
+ import { RateLimiterRes } from 'rate-limiter-flexible';
2
3
  type IRateLimiterOptionsOverrides = Partial<IRateLimiterOptions> | Partial<IRateLimiterStoreOptions>;
3
4
  export declare function createRateLimiter(configPrefix?: string, configOverrides?: IRateLimiterOptionsOverrides): RateLimiterAbstract;
4
- export {};
5
+ export { RateLimiterRes };
@@ -1,10 +1,11 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { merge } from 'lodash-es';
2
- import { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
3
- import env from './env.js';
3
+ import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible';
4
4
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
5
5
  import { createRequire } from 'node:module';
6
6
  const require = createRequire(import.meta.url);
7
7
  export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides) {
8
+ const env = useEnv();
8
9
  switch (env['RATE_LIMITER_STORE']) {
9
10
  case 'redis':
10
11
  return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
@@ -13,10 +14,12 @@ export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides
13
14
  return new RateLimiterMemory(getConfig('memory', configPrefix, configOverrides));
14
15
  }
15
16
  }
17
+ export { RateLimiterRes };
16
18
  function getConfig(store = 'memory', configPrefix = 'RATE_LIMITER', overrides) {
17
19
  const config = getConfigFromEnv(`${configPrefix}_`, `${configPrefix}_${store}_`);
18
20
  if (store === 'redis') {
19
21
  const Redis = require('ioredis');
22
+ const env = useEnv();
20
23
  config.storeClient = new Redis(env[`REDIS`] || getConfigFromEnv(`REDIS_`));
21
24
  }
22
25
  delete config.enabled;
@@ -1,2 +1,3 @@
1
- export { useRedis } from './use-redis.js';
2
- export { createRedis } from './create-redis.js';
1
+ export { createRedis } from './lib/create-redis.js';
2
+ export { useRedis } from './lib/use-redis.js';
3
+ export { redisConfigAvailable } from './utils/redis-config-available.js';
@@ -1,2 +1,3 @@
1
- export { useRedis } from './use-redis.js';
2
- export { createRedis } from './create-redis.js';
1
+ export { createRedis } from './lib/create-redis.js';
2
+ export { useRedis } from './lib/use-redis.js';
3
+ export { redisConfigAvailable } from './utils/redis-config-available.js';
@@ -1,6 +1,6 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import { Redis } from 'ioredis';
2
- import { useEnv } from '../env.js';
3
- import { getConfigFromEnv } from '../utils/get-config-from-env.js';
3
+ import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
4
4
  /**
5
5
  * Create a new Redis instance based on the global env configuration
6
6
  *
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Check if Redis configuration exists in the current project's environment configuration
3
+ */
4
+ export declare const redisConfigAvailable: () => boolean;
@@ -0,0 +1,8 @@
1
+ import { useEnv } from '@directus/env';
2
+ /**
3
+ * Check if Redis configuration exists in the current project's environment configuration
4
+ */
5
+ export const redisConfigAvailable = () => {
6
+ const env = useEnv();
7
+ return 'REDIS' in env || Object.keys(env).some((key) => key.startsWith('REDIS_'));
8
+ };
@@ -2,9 +2,10 @@ import axios from 'axios';
2
2
  import { lookup } from 'node:dns/promises';
3
3
  import { isIP } from 'node:net';
4
4
  import { URL } from 'node:url';
5
- import logger from '../logger.js';
6
- import { validateIP } from './validate-ip.js';
5
+ import { useLogger } from '../logger.js';
6
+ import { validateIp } from './validate-ip.js';
7
7
  export const requestInterceptor = async (config) => {
8
+ const logger = useLogger();
8
9
  const uri = axios.getUri(config);
9
10
  const { hostname } = new URL(uri);
10
11
  let ip;
@@ -13,14 +14,15 @@ export const requestInterceptor = async (config) => {
13
14
  const dns = await lookup(hostname);
14
15
  ip = dns.address;
15
16
  }
16
- catch (err) {
17
- logger.warn(err, `Couldn't lookup the DNS for url "${uri}"`);
17
+ catch (error) {
18
+ logger.warn(`Couldn't lookup the DNS for URL "${uri}"`);
19
+ logger.warn(error);
18
20
  throw new Error(`Requested URL "${uri}" resolves to a denied IP address`);
19
21
  }
20
22
  }
21
23
  else {
22
24
  ip = hostname;
23
25
  }
24
- await validateIP(ip, uri);
26
+ validateIp(ip, uri);
25
27
  return config;
26
28
  };
@@ -1,5 +1,5 @@
1
- import { validateIP } from './validate-ip.js';
1
+ import { validateIp } from './validate-ip.js';
2
2
  export const responseInterceptor = async (config) => {
3
- await validateIP(config.request.socket.remoteAddress, config.request.url);
3
+ validateIp(config.request.socket.remoteAddress, config.request.url);
4
4
  return config;
5
5
  };
@@ -1 +1 @@
1
- export declare const validateIP: (ip: string, url: string) => Promise<void>;
1
+ export declare function validateIp(ip: string, url: string): void;
@@ -1,19 +1,35 @@
1
+ import { useEnv } from '@directus/env';
1
2
  import os from 'node:os';
2
- import env from '../env.js';
3
- export const validateIP = async (ip, url) => {
4
- if (env['IMPORT_IP_DENY_LIST'].includes(ip)) {
5
- throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
3
+ import { useLogger } from '../logger.js';
4
+ import { ipInNetworks } from '../utils/ip-in-networks.js';
5
+ const deniedError = (url) => new Error(`Requested URL "${url}" resolves to a denied IP address`);
6
+ export function validateIp(ip, url) {
7
+ const env = useEnv();
8
+ const logger = useLogger();
9
+ const ipDenyList = env['IMPORT_IP_DENY_LIST'];
10
+ if (ipDenyList.length === 0)
11
+ return;
12
+ let denied;
13
+ try {
14
+ denied = ipInNetworks(ip, ipDenyList);
6
15
  }
7
- if (env['IMPORT_IP_DENY_LIST'].includes('0.0.0.0')) {
16
+ catch (error) {
17
+ logger.warn(`Invalid "IMPORT_IP_DENY_LIST" configuration`);
18
+ logger.warn(error);
19
+ throw deniedError(url);
20
+ }
21
+ if (denied)
22
+ throw deniedError(url);
23
+ if (ipDenyList.includes('0.0.0.0')) {
8
24
  const networkInterfaces = os.networkInterfaces();
9
25
  for (const networkInfo of Object.values(networkInterfaces)) {
10
26
  if (!networkInfo)
11
27
  continue;
12
28
  for (const info of networkInfo) {
13
29
  if (info.address === ip) {
14
- throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
30
+ throw deniedError(url);
15
31
  }
16
32
  }
17
33
  }
18
34
  }
19
- };
35
+ }
package/dist/server.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { toBoolean } from '@directus/utils';
3
+ import { getNodeEnv } from '@directus/utils/node';
1
4
  import { createTerminus } from '@godaddy/terminus';
2
5
  import * as http from 'http';
3
6
  import * as https from 'https';
@@ -7,13 +10,14 @@ import url from 'url';
7
10
  import createApp from './app.js';
8
11
  import getDatabase from './database/index.js';
9
12
  import emitter from './emitter.js';
10
- import env from './env.js';
11
- import logger from './logger.js';
13
+ import { useLogger } from './logger.js';
12
14
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
13
- import { toBoolean } from './utils/to-boolean.js';
15
+ import { getIPFromReq } from './utils/get-ip-from-req.js';
14
16
  import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
15
17
  import { startWebSocketHandlers } from './websocket/handlers/index.js';
16
18
  export let SERVER_ONLINE = true;
19
+ const env = useEnv();
20
+ const logger = useLogger();
17
21
  export async function createServer() {
18
22
  const server = http.createServer(await createApp());
19
23
  Object.assign(server, getConfigFromEnv('SERVER_'));
@@ -57,7 +61,7 @@ export async function createServer() {
57
61
  size: metrics.out,
58
62
  headers: res.getHeaders(),
59
63
  },
60
- ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress,
64
+ ip: getIPFromReq(req),
61
65
  duration: elapsedMilliseconds.toFixed(),
62
66
  };
63
67
  emitter.emitAction('response', info, {
@@ -86,7 +90,7 @@ export async function createServer() {
86
90
  createTerminus(server, terminusOptions);
87
91
  return server;
88
92
  async function beforeShutdown() {
89
- if (env['NODE_ENV'] !== 'development') {
93
+ if (getNodeEnv() !== 'development') {
90
94
  logger.info('Shutting down...');
91
95
  }
92
96
  SERVER_ONLINE = false;
@@ -104,7 +108,7 @@ export async function createServer() {
104
108
  schema: null,
105
109
  accountability: null,
106
110
  });
107
- if (env['NODE_ENV'] !== 'development') {
111
+ if (getNodeEnv() !== 'development') {
108
112
  logger.info('Directus shut down OK. Bye bye!');
109
113
  }
110
114
  }
@@ -112,7 +116,7 @@ export async function createServer() {
112
116
  export async function startServer() {
113
117
  const server = await createServer();
114
118
  const host = env['HOST'];
115
- const port = env['PORT'];
119
+ const port = parseInt(env['PORT']);
116
120
  server
117
121
  .listen(port, host, () => {
118
122
  logger.info(`Server started at http://${host}:${port}`);
@@ -1,10 +1,9 @@
1
1
  import { Action } from '@directus/constants';
2
- import { isDirectusError } from '@directus/errors';
2
+ import { useEnv } from '@directus/env';
3
+ import { ErrorCode, isDirectusError } from '@directus/errors';
3
4
  import { uniq } from 'lodash-es';
4
5
  import validateUUID from 'uuid-validate';
5
- import env from '../env.js';
6
- import { ErrorCode } from '@directus/errors';
7
- import logger from '../logger.js';
6
+ import { useLogger } from '../logger.js';
8
7
  import { getPermissions } from '../utils/get-permissions.js';
9
8
  import { Url } from '../utils/url.js';
10
9
  import { userName } from '../utils/user-name.js';
@@ -12,6 +11,8 @@ import { AuthorizationService } from './authorization.js';
12
11
  import { ItemsService } from './items.js';
13
12
  import { NotificationsService } from './notifications.js';
14
13
  import { UsersService } from './users.js';
14
+ const env = useEnv();
15
+ const logger = useLogger();
15
16
  export class ActivityService extends ItemsService {
16
17
  notificationsService;
17
18
  usersService;
@@ -5,10 +5,12 @@ import type { Knex } from 'knex';
5
5
  import type { Readable } from 'node:stream';
6
6
  import type { AbstractServiceOptions, TransformationSet } from '../types/index.js';
7
7
  import { AuthorizationService } from './authorization.js';
8
+ import { FilesService } from './files.js';
8
9
  export declare class AssetsService {
9
10
  knex: Knex;
10
11
  accountability: Accountability | null;
11
12
  authorizationService: AuthorizationService;
13
+ filesService: FilesService;
12
14
  constructor(options: AbstractServiceOptions);
13
15
  getAsset(id: string, transformation?: TransformationSet, range?: Range): Promise<{
14
16
  stream: Readable;
@@ -1,3 +1,5 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
1
3
  import { clamp } from 'lodash-es';
2
4
  import { contentType } from 'mime-types';
3
5
  import hash from 'object-hash';
@@ -6,20 +8,23 @@ import sharp from 'sharp';
6
8
  import validateUUID from 'uuid-validate';
7
9
  import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
8
10
  import getDatabase from '../database/index.js';
9
- import env from '../env.js';
10
- import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
11
- import logger from '../logger.js';
11
+ import { useLogger } from '../logger.js';
12
12
  import { getStorage } from '../storage/index.js';
13
13
  import { getMilliseconds } from '../utils/get-milliseconds.js';
14
14
  import * as TransformationUtils from '../utils/transformations.js';
15
15
  import { AuthorizationService } from './authorization.js';
16
+ import { FilesService } from './files.js';
17
+ const env = useEnv();
18
+ const logger = useLogger();
16
19
  export class AssetsService {
17
20
  knex;
18
21
  accountability;
19
22
  authorizationService;
23
+ filesService;
20
24
  constructor(options) {
21
25
  this.knex = options.knex || getDatabase();
22
26
  this.accountability = options.accountability || null;
27
+ this.filesService = new FilesService({ ...options, accountability: null });
23
28
  this.authorizationService = new AuthorizationService(options);
24
29
  }
25
30
  async getAsset(id, transformation, range) {
@@ -40,9 +45,7 @@ export class AssetsService {
40
45
  if (systemPublicKeys.includes(id) === false && this.accountability?.admin !== true) {
41
46
  await this.authorizationService.checkAccess('read', 'directus_files', id);
42
47
  }
43
- const file = (await this.knex.select('*').from('directus_files').where({ id }).first());
44
- if (!file)
45
- throw new ForbiddenError();
48
+ const file = (await this.filesService.readOne(id, { limit: 1 }));
46
49
  const exists = await storage.location(file.storage).exists(file.filename_disk);
47
50
  if (!exists)
48
51
  throw new ForbiddenError();
@@ -1,4 +1,6 @@
1
1
  import { Action } from '@directus/constants';
2
+ import { useEnv } from '@directus/env';
3
+ import { InvalidCredentialsError, InvalidOtpError, InvalidProviderError, ServiceUnavailableError, UserSuspendedError, } from '@directus/errors';
2
4
  import jwt from 'jsonwebtoken';
3
5
  import { clone, cloneDeep } from 'lodash-es';
4
6
  import { performance } from 'perf_hooks';
@@ -6,15 +8,13 @@ import { getAuthProvider } from '../auth.js';
6
8
  import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
7
9
  import getDatabase from '../database/index.js';
8
10
  import emitter from '../emitter.js';
9
- import env from '../env.js';
10
- import { InvalidCredentialsError, InvalidProviderError, UserSuspendedError } from '@directus/errors';
11
- import { InvalidOtpError } from '@directus/errors';
12
- import { createRateLimiter } from '../rate-limiter.js';
11
+ import { RateLimiterRes, createRateLimiter } from '../rate-limiter.js';
13
12
  import { getMilliseconds } from '../utils/get-milliseconds.js';
14
13
  import { stall } from '../utils/stall.js';
15
14
  import { ActivityService } from './activity.js';
16
15
  import { SettingsService } from './settings.js';
17
16
  import { TFAService } from './tfa.js';
17
+ const env = useEnv();
18
18
  const loginAttemptsLimiter = createRateLimiter('RATE_LIMITER', { duration: 0 });
19
19
  export class AuthenticationService {
20
20
  knex;
@@ -100,11 +100,19 @@ export class AuthenticationService {
100
100
  try {
101
101
  await loginAttemptsLimiter.consume(user.id);
102
102
  }
103
- catch {
104
- await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
105
- user.status = 'suspended';
106
- // This means that new attempts after the user has been re-activated will be accepted
107
- await loginAttemptsLimiter.set(user.id, 0, 0);
103
+ catch (error) {
104
+ if (error instanceof RateLimiterRes && error.remainingPoints === 0) {
105
+ await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
106
+ user.status = 'suspended';
107
+ // This means that new attempts after the user has been re-activated will be accepted
108
+ await loginAttemptsLimiter.set(user.id, 0, 0);
109
+ }
110
+ else {
111
+ throw new ServiceUnavailableError({
112
+ service: 'authentication',
113
+ reason: 'Rate limiter unreachable',
114
+ });
115
+ }
108
116
  }
109
117
  }
110
118
  try {
@@ -13,5 +13,5 @@ export declare class AuthorizationService {
13
13
  * Checks if the provided payload matches the configured permissions, and adds the presets to the payload.
14
14
  */
15
15
  validatePayload(action: PermissionsAction, collection: string, data: Partial<Item>): Partial<Item>;
16
- checkAccess(action: PermissionsAction, collection: string, pk: PrimaryKey | PrimaryKey[]): Promise<void>;
16
+ checkAccess(action: PermissionsAction, collection: string, pk?: PrimaryKey | PrimaryKey[]): Promise<void>;
17
17
  }
@@ -1,9 +1,9 @@
1
+ import { ForbiddenError } from '@directus/errors';
1
2
  import { validatePayload } from '@directus/utils';
2
3
  import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
3
4
  import { cloneDeep, flatten, isArray, isNil, merge, reduce, uniq, uniqWith } from 'lodash-es';
4
5
  import { GENERATE_SPECIAL } from '../constants.js';
5
6
  import getDatabase from '../database/index.js';
6
- import { ForbiddenError } from '@directus/errors';
7
7
  import { getRelationInfo } from '../utils/get-relation-info.js';
8
8
  import { stripFunction } from '../utils/strip-function.js';
9
9
  import { ItemsService } from './items.js';
@@ -430,15 +430,27 @@ export class AuthorizationService {
430
430
  };
431
431
  if (Array.isArray(pk)) {
432
432
  const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
433
- if (!result)
433
+ // for the unexpected case that the result is not an array (for example due to filter hook)
434
+ if (!isArray(result))
434
435
  throw new ForbiddenError();
435
436
  if (result.length !== pk.length)
436
437
  throw new ForbiddenError();
437
438
  }
438
- else {
439
+ else if (pk) {
439
440
  const result = await itemsService.readOne(pk, query, { permissionsAction: action });
440
441
  if (!result)
441
442
  throw new ForbiddenError();
442
443
  }
444
+ else {
445
+ query.limit = 1;
446
+ const result = await itemsService.readByQuery(query, { permissionsAction: action });
447
+ // for the unexpected case that the result is not an array (for example due to filter hook)
448
+ if (!isArray(result))
449
+ throw new ForbiddenError();
450
+ // for create action, an empty array is expected - for other actions, the first item is expected to be available
451
+ const access = action === 'create' ? result.length === 0 : !!result[0];
452
+ if (!access)
453
+ throw new ForbiddenError();
454
+ }
443
455
  }
444
456
  }
@@ -1,3 +1,5 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
1
3
  import { createInspector } from '@directus/schema';
2
4
  import { addFieldFlag } from '@directus/utils';
3
5
  import { chunk, groupBy, merge, omit } from 'lodash-es';
@@ -7,12 +9,10 @@ import { getHelpers } from '../database/helpers/index.js';
7
9
  import getDatabase, { getSchemaInspector } from '../database/index.js';
8
10
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
9
11
  import emitter from '../emitter.js';
10
- import env from '../env.js';
11
- import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
12
- import { FieldsService } from './fields.js';
13
- import { ItemsService } from './items.js';
14
12
  import { getSchema } from '../utils/get-schema.js';
15
13
  import { shouldClearCache } from '../utils/should-clear-cache.js';
14
+ import { FieldsService } from './fields.js';
15
+ import { ItemsService } from './items.js';
16
16
  export class CollectionsService {
17
17
  knex;
18
18
  helpers;
@@ -206,6 +206,7 @@ export class CollectionsService {
206
206
  * Read all collections. Currently doesn't support any query.
207
207
  */
208
208
  async readByQuery() {
209
+ const env = useEnv();
209
210
  const collectionItemsService = new ItemsService('directus_collections', {
210
211
  knex: this.knex,
211
212
  schema: this.schema,
@@ -1,28 +1,34 @@
1
1
  import type { ApiOutput, ExtensionSettings } from '@directus/extensions';
2
- import type { SchemaInspector } from '@directus/schema';
3
2
  import type { Accountability, DeepPartial, SchemaOverview } from '@directus/types';
4
- import type Keyv from 'keyv';
5
3
  import type { Knex } from 'knex';
6
- import type { Helpers } from '../database/helpers/index.js';
7
4
  import type { ExtensionManager } from '../extensions/manager.js';
8
5
  import type { AbstractServiceOptions } from '../types/index.js';
9
6
  import { ItemsService } from './items.js';
10
- import { PermissionsService } from './permissions.js';
7
+ export declare class ExtensionReadError extends Error {
8
+ originalError: unknown;
9
+ constructor(originalError: unknown);
10
+ }
11
11
  export declare class ExtensionsService {
12
12
  knex: Knex;
13
- permissionsService: PermissionsService;
14
- schemaInspector: SchemaInspector;
15
13
  accountability: Accountability | null;
16
14
  schema: SchemaOverview;
17
15
  extensionsItemService: ItemsService<ExtensionSettings>;
18
- systemCache: Keyv<any>;
19
- helpers: Helpers;
20
16
  extensionsManager: ExtensionManager;
21
17
  constructor(options: AbstractServiceOptions);
22
18
  readAll(): Promise<ApiOutput[]>;
23
19
  readOne(bundle: string | null, name: string): Promise<ApiOutput>;
24
- updateOne(bundle: string | null, name: string, data: DeepPartial<ApiOutput>): Promise<void>;
20
+ updateOne(bundle: string | null, name: string, data: DeepPartial<ApiOutput>): Promise<ApiOutput>;
25
21
  private getKey;
22
+ /**
23
+ * Sync a bundles enabled status
24
+ * - If the extension or extensions parent is not a bundle changes are skipped
25
+ * - If a bundles status is toggled, all children are set to that status
26
+ * - If an entries status is toggled, then if the:
27
+ * - Parent bundle is non-partial throws UnprocessableContentError
28
+ * - Entry status change resulted in all children being disabled then the parent bundle is disabled
29
+ * - Entry status change resulted in at least one child being enabled then the parent bundle is enabled
30
+ */
31
+ private checkBundleAndSyncStatus;
26
32
  /**
27
33
  * Combine the settings stored in the database with the information available from the installed
28
34
  * extensions into the standardized extensions api output