@directus/api 11.1.0 → 12.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 (246) hide show
  1. package/dist/app.js +3 -4
  2. package/dist/auth/auth.d.ts +4 -4
  3. package/dist/auth/auth.js +2 -2
  4. package/dist/auth/drivers/ldap.js +20 -17
  5. package/dist/auth/drivers/local.js +5 -5
  6. package/dist/auth/drivers/oauth2.js +16 -16
  7. package/dist/auth/drivers/openid.js +18 -17
  8. package/dist/auth/drivers/saml.js +6 -7
  9. package/dist/auth.js +3 -2
  10. package/dist/cache.js +3 -13
  11. package/dist/cli/utils/create-env/env-stub.liquid +5 -7
  12. package/dist/controllers/activity.js +7 -6
  13. package/dist/controllers/assets.js +25 -12
  14. package/dist/controllers/auth.js +8 -7
  15. package/dist/controllers/collections.js +4 -3
  16. package/dist/controllers/dashboards.js +5 -4
  17. package/dist/controllers/extensions.js +3 -3
  18. package/dist/controllers/fields.js +9 -8
  19. package/dist/controllers/files.js +11 -11
  20. package/dist/controllers/flows.js +5 -4
  21. package/dist/controllers/folders.js +5 -4
  22. package/dist/controllers/items.js +14 -13
  23. package/dist/controllers/not-found.js +2 -2
  24. package/dist/controllers/notifications.js +5 -4
  25. package/dist/controllers/operations.js +5 -4
  26. package/dist/controllers/panels.js +5 -4
  27. package/dist/controllers/permissions.js +5 -4
  28. package/dist/controllers/presets.js +5 -4
  29. package/dist/controllers/relations.js +6 -5
  30. package/dist/controllers/roles.js +5 -4
  31. package/dist/controllers/schema.js +8 -8
  32. package/dist/controllers/server.js +2 -2
  33. package/dist/controllers/settings.js +3 -2
  34. package/dist/controllers/shares.js +7 -6
  35. package/dist/controllers/translations.js +6 -5
  36. package/dist/controllers/users.js +22 -21
  37. package/dist/controllers/utils.js +10 -10
  38. package/dist/controllers/webhooks.js +5 -4
  39. package/dist/{exceptions/database → database/errors}/dialects/mssql.js +8 -18
  40. package/dist/{exceptions/database → database/errors}/dialects/mysql.js +9 -19
  41. package/dist/{exceptions/database → database/errors}/dialects/oracle.js +2 -2
  42. package/dist/{exceptions/database → database/errors}/dialects/postgres.js +7 -18
  43. package/dist/{exceptions/database → database/errors}/dialects/sqlite.js +7 -10
  44. package/dist/{exceptions/database → database/errors}/translate.js +1 -1
  45. package/dist/database/migrations/run.js +10 -1
  46. package/dist/env.js +6 -13
  47. package/dist/errors/codes.d.ts +29 -0
  48. package/dist/errors/codes.js +30 -0
  49. package/dist/errors/contains-null-values.d.ts +7 -0
  50. package/dist/errors/contains-null-values.js +4 -0
  51. package/dist/errors/content-too-large.d.ts +1 -0
  52. package/dist/errors/content-too-large.js +3 -0
  53. package/dist/errors/forbidden.d.ts +1 -0
  54. package/dist/errors/forbidden.js +3 -0
  55. package/dist/errors/hit-rate-limit.d.ts +6 -0
  56. package/dist/errors/hit-rate-limit.js +8 -0
  57. package/dist/errors/illegal-asset-transformation.d.ts +4 -0
  58. package/dist/errors/illegal-asset-transformation.js +3 -0
  59. package/dist/errors/index.d.ts +28 -0
  60. package/dist/errors/index.js +28 -0
  61. package/dist/errors/invalid-credentials.d.ts +1 -0
  62. package/dist/errors/invalid-credentials.js +3 -0
  63. package/dist/errors/invalid-foreign-key.d.ts +6 -0
  64. package/dist/errors/invalid-foreign-key.js +14 -0
  65. package/dist/errors/invalid-ip.d.ts +1 -0
  66. package/dist/errors/invalid-ip.js +3 -0
  67. package/dist/errors/invalid-otp.d.ts +1 -0
  68. package/dist/errors/invalid-otp.js +3 -0
  69. package/dist/errors/invalid-payload.d.ts +5 -0
  70. package/dist/errors/invalid-payload.js +4 -0
  71. package/dist/errors/invalid-provider-config.d.ts +5 -0
  72. package/dist/errors/invalid-provider-config.js +3 -0
  73. package/dist/errors/invalid-provider.d.ts +1 -0
  74. package/dist/errors/invalid-provider.js +3 -0
  75. package/dist/errors/invalid-query.d.ts +5 -0
  76. package/dist/errors/invalid-query.js +4 -0
  77. package/dist/errors/invalid-token.d.ts +1 -0
  78. package/dist/errors/invalid-token.js +3 -0
  79. package/dist/errors/method-not-allowed.d.ts +6 -0
  80. package/dist/errors/method-not-allowed.js +6 -0
  81. package/dist/errors/not-null-violation.d.ts +6 -0
  82. package/dist/errors/not-null-violation.js +14 -0
  83. package/dist/errors/range-not-satisfiable.d.ts +7 -0
  84. package/dist/errors/range-not-satisfiable.js +7 -0
  85. package/dist/errors/record-not-unique.d.ts +6 -0
  86. package/dist/errors/record-not-unique.js +14 -0
  87. package/dist/errors/route-not-found.d.ts +5 -0
  88. package/dist/errors/route-not-found.js +4 -0
  89. package/dist/errors/service-unavailable.d.ts +7 -0
  90. package/dist/errors/service-unavailable.js +4 -0
  91. package/dist/errors/token-expired.d.ts +1 -0
  92. package/dist/errors/token-expired.js +3 -0
  93. package/dist/errors/unexpected-response.d.ts +1 -0
  94. package/dist/errors/unexpected-response.js +3 -0
  95. package/dist/errors/unprocessable-content.d.ts +5 -0
  96. package/dist/errors/unprocessable-content.js +4 -0
  97. package/dist/errors/unsupported-media-type.d.ts +6 -0
  98. package/dist/errors/unsupported-media-type.js +4 -0
  99. package/dist/errors/user-suspended.d.ts +1 -0
  100. package/dist/errors/user-suspended.js +3 -0
  101. package/dist/errors/value-out-of-range.d.ts +6 -0
  102. package/dist/errors/value-out-of-range.js +14 -0
  103. package/dist/errors/value-too-long.d.ts +6 -0
  104. package/dist/errors/value-too-long.js +14 -0
  105. package/dist/extensions.js +0 -4
  106. package/dist/flows.js +6 -8
  107. package/dist/index.d.ts +0 -2
  108. package/dist/index.js +0 -2
  109. package/dist/messenger.js +4 -4
  110. package/dist/middleware/authenticate.js +1 -1
  111. package/dist/middleware/check-ip.js +2 -2
  112. package/dist/middleware/collection-exists.js +2 -2
  113. package/dist/middleware/error-handler.js +7 -7
  114. package/dist/middleware/graphql.js +11 -9
  115. package/dist/middleware/rate-limiter-global.d.ts +2 -2
  116. package/dist/middleware/rate-limiter-global.js +2 -3
  117. package/dist/middleware/rate-limiter-ip.d.ts +2 -2
  118. package/dist/middleware/rate-limiter-ip.js +2 -3
  119. package/dist/middleware/validate-batch.js +3 -4
  120. package/dist/rate-limiter.js +2 -9
  121. package/dist/services/activity.js +3 -2
  122. package/dist/services/assets.js +9 -10
  123. package/dist/services/authentication.js +12 -11
  124. package/dist/services/authorization.d.ts +1 -1
  125. package/dist/services/authorization.js +16 -16
  126. package/dist/services/collections.js +17 -16
  127. package/dist/services/fields.js +16 -14
  128. package/dist/services/files.js +7 -6
  129. package/dist/services/graphql/errors/execution.d.ts +6 -0
  130. package/dist/services/graphql/errors/execution.js +2 -0
  131. package/dist/services/graphql/errors/index.d.ts +2 -0
  132. package/dist/services/graphql/errors/index.js +2 -0
  133. package/dist/services/graphql/errors/validation.d.ts +6 -0
  134. package/dist/services/graphql/errors/validation.js +2 -0
  135. package/dist/services/graphql/index.d.ts +2 -2
  136. package/dist/services/graphql/index.js +30 -12
  137. package/dist/services/graphql/utils/process-error.js +3 -3
  138. package/dist/services/import-export.js +7 -7
  139. package/dist/services/index.d.ts +1 -0
  140. package/dist/services/index.js +1 -0
  141. package/dist/services/items.js +14 -13
  142. package/dist/services/mail/index.js +3 -3
  143. package/dist/services/meta.js +3 -3
  144. package/dist/services/payload.js +11 -7
  145. package/dist/services/relations.js +32 -22
  146. package/dist/services/revisions.js +3 -3
  147. package/dist/services/roles.js +10 -9
  148. package/dist/services/schema.js +5 -5
  149. package/dist/services/shares.js +4 -4
  150. package/dist/services/tfa.js +6 -6
  151. package/dist/services/translations.d.ts +2 -2
  152. package/dist/services/translations.js +4 -4
  153. package/dist/services/users.js +26 -29
  154. package/dist/services/utils.js +4 -4
  155. package/dist/synchronization.js +3 -3
  156. package/dist/types/items.d.ts +2 -2
  157. package/dist/utils/apply-diff.js +2 -2
  158. package/dist/utils/apply-query.js +17 -7
  159. package/dist/utils/get-accountability-for-role.js +1 -2
  160. package/dist/utils/get-accountability-for-token.js +3 -3
  161. package/dist/utils/get-column-path.js +5 -3
  162. package/dist/utils/get-column.js +3 -3
  163. package/dist/utils/get-service.d.ts +1 -1
  164. package/dist/utils/get-service.js +1 -1
  165. package/dist/utils/jwt.js +5 -5
  166. package/dist/utils/validate-diff.js +23 -9
  167. package/dist/utils/validate-keys.js +3 -3
  168. package/dist/utils/validate-query.d.ts +2 -0
  169. package/dist/utils/validate-query.js +27 -21
  170. package/dist/utils/validate-snapshot.js +11 -5
  171. package/dist/websocket/authenticate.js +12 -15
  172. package/dist/websocket/controllers/base.js +18 -15
  173. package/dist/websocket/controllers/graphql.js +2 -2
  174. package/dist/websocket/controllers/index.js +3 -7
  175. package/dist/websocket/controllers/rest.js +3 -3
  176. package/dist/websocket/{exceptions.d.ts → errors.d.ts} +5 -5
  177. package/dist/websocket/{exceptions.js → errors.js} +10 -10
  178. package/dist/websocket/handlers/items.js +5 -5
  179. package/dist/websocket/handlers/subscribe.js +8 -8
  180. package/package.json +15 -15
  181. package/dist/exceptions/content-too-large.d.ts +0 -4
  182. package/dist/exceptions/content-too-large.js +0 -6
  183. package/dist/exceptions/database/contains-null-values.d.ts +0 -9
  184. package/dist/exceptions/database/contains-null-values.js +0 -6
  185. package/dist/exceptions/database/invalid-foreign-key.d.ts +0 -10
  186. package/dist/exceptions/database/invalid-foreign-key.js +0 -11
  187. package/dist/exceptions/database/not-null-violation.d.ts +0 -9
  188. package/dist/exceptions/database/not-null-violation.js +0 -6
  189. package/dist/exceptions/database/record-not-unique.d.ts +0 -10
  190. package/dist/exceptions/database/record-not-unique.js +0 -11
  191. package/dist/exceptions/database/value-out-of-range.d.ts +0 -10
  192. package/dist/exceptions/database/value-out-of-range.js +0 -11
  193. package/dist/exceptions/database/value-too-long.d.ts +0 -9
  194. package/dist/exceptions/database/value-too-long.js +0 -11
  195. package/dist/exceptions/forbidden.d.ts +0 -6
  196. package/dist/exceptions/forbidden.js +0 -13
  197. package/dist/exceptions/graphql-validation.d.ts +0 -4
  198. package/dist/exceptions/graphql-validation.js +0 -6
  199. package/dist/exceptions/hit-rate-limit.d.ts +0 -9
  200. package/dist/exceptions/hit-rate-limit.js +0 -6
  201. package/dist/exceptions/illegal-asset-transformation.d.ts +0 -4
  202. package/dist/exceptions/illegal-asset-transformation.js +0 -6
  203. package/dist/exceptions/index.d.ts +0 -21
  204. package/dist/exceptions/index.js +0 -21
  205. package/dist/exceptions/invalid-config.d.ts +0 -4
  206. package/dist/exceptions/invalid-config.js +0 -6
  207. package/dist/exceptions/invalid-credentials.d.ts +0 -4
  208. package/dist/exceptions/invalid-credentials.js +0 -6
  209. package/dist/exceptions/invalid-ip.d.ts +0 -4
  210. package/dist/exceptions/invalid-ip.js +0 -6
  211. package/dist/exceptions/invalid-otp.d.ts +0 -4
  212. package/dist/exceptions/invalid-otp.js +0 -6
  213. package/dist/exceptions/invalid-payload.d.ts +0 -4
  214. package/dist/exceptions/invalid-payload.js +0 -6
  215. package/dist/exceptions/invalid-provider.d.ts +0 -4
  216. package/dist/exceptions/invalid-provider.js +0 -6
  217. package/dist/exceptions/invalid-query.d.ts +0 -4
  218. package/dist/exceptions/invalid-query.js +0 -6
  219. package/dist/exceptions/invalid-token.d.ts +0 -4
  220. package/dist/exceptions/invalid-token.js +0 -6
  221. package/dist/exceptions/method-not-allowed.d.ts +0 -8
  222. package/dist/exceptions/method-not-allowed.js +0 -6
  223. package/dist/exceptions/range-not-satisfiable.d.ts +0 -5
  224. package/dist/exceptions/range-not-satisfiable.js +0 -9
  225. package/dist/exceptions/route-not-found.d.ts +0 -4
  226. package/dist/exceptions/route-not-found.js +0 -6
  227. package/dist/exceptions/service-unavailable.d.ts +0 -9
  228. package/dist/exceptions/service-unavailable.js +0 -6
  229. package/dist/exceptions/token-expired.d.ts +0 -4
  230. package/dist/exceptions/token-expired.js +0 -6
  231. package/dist/exceptions/unexpected-response.d.ts +0 -4
  232. package/dist/exceptions/unexpected-response.js +0 -6
  233. package/dist/exceptions/unprocessable-entity.d.ts +0 -4
  234. package/dist/exceptions/unprocessable-entity.js +0 -6
  235. package/dist/exceptions/unsupported-media-type.d.ts +0 -4
  236. package/dist/exceptions/unsupported-media-type.js +0 -6
  237. package/dist/exceptions/user-suspended.d.ts +0 -4
  238. package/dist/exceptions/user-suspended.js +0 -6
  239. /package/dist/{exceptions/database → database/errors}/dialects/mssql.d.ts +0 -0
  240. /package/dist/{exceptions/database → database/errors}/dialects/mysql.d.ts +0 -0
  241. /package/dist/{exceptions/database → database/errors}/dialects/oracle.d.ts +0 -0
  242. /package/dist/{exceptions/database → database/errors}/dialects/postgres.d.ts +0 -0
  243. /package/dist/{exceptions/database → database/errors}/dialects/sqlite.d.ts +0 -0
  244. /package/dist/{exceptions/database → database/errors}/dialects/types.d.ts +0 -0
  245. /package/dist/{exceptions/database → database/errors}/dialects/types.js +0 -0
  246. /package/dist/{exceptions/database → database/errors}/translate.d.ts +0 -0
@@ -1,6 +1,5 @@
1
- import ms from 'ms';
2
1
  import env from '../env.js';
3
- import { HitRateLimitException } from '../exceptions/index.js';
2
+ import { HitRateLimitError } from '../errors/index.js';
4
3
  import logger from '../logger.js';
5
4
  import { createRateLimiter } from '../rate-limiter.js';
6
5
  import asyncHandler from '../utils/async-handler.js';
@@ -20,7 +19,7 @@ if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
20
19
  if (rateLimiterRes instanceof Error)
21
20
  throw rateLimiterRes;
22
21
  res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
23
- throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, {
22
+ throw new HitRateLimitError({
24
23
  limit: +env['RATE_LIMITER_GLOBAL_POINTS'],
25
24
  reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
26
25
  });
@@ -1,5 +1,5 @@
1
1
  import type { RequestHandler } from 'express';
2
- import type { RateLimiterMemcache, RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
2
+ import type { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
3
3
  declare let checkRateLimit: RequestHandler;
4
- export declare let rateLimiter: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory;
4
+ export declare let rateLimiter: RateLimiterRedis | RateLimiterMemory;
5
5
  export default checkRateLimit;
@@ -1,6 +1,5 @@
1
- import ms from 'ms';
2
1
  import env from '../env.js';
3
- import { HitRateLimitException } from '../exceptions/index.js';
2
+ import { HitRateLimitError } from '../errors/index.js';
4
3
  import { createRateLimiter } from '../rate-limiter.js';
5
4
  import asyncHandler from '../utils/async-handler.js';
6
5
  import { getIPFromReq } from '../utils/get-ip-from-req.js';
@@ -18,7 +17,7 @@ if (env['RATE_LIMITER_ENABLED'] === true) {
18
17
  if (rateLimiterRes instanceof Error)
19
18
  throw rateLimiterRes;
20
19
  res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
21
- throw new HitRateLimitException(`Too many requests, retry after ${ms(rateLimiterRes.msBeforeNext)}.`, {
20
+ throw new HitRateLimitError({
22
21
  limit: +env['RATE_LIMITER_POINTS'],
23
22
  reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
24
23
  });
@@ -1,6 +1,5 @@
1
- import { FailedValidationException } from '@directus/exceptions';
2
1
  import Joi from 'joi';
3
- import { InvalidPayloadException } from '../exceptions/index.js';
2
+ import { InvalidPayloadError } from '../errors/index.js';
4
3
  import asyncHandler from '../utils/async-handler.js';
5
4
  import { sanitizeQuery } from '../utils/sanitize-query.js';
6
5
  export const validateBatch = (scope) => asyncHandler(async (req, _res, next) => {
@@ -12,7 +11,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
12
11
  return next();
13
12
  }
14
13
  if (!req.body)
15
- throw new InvalidPayloadException('Payload in body is required');
14
+ throw new InvalidPayloadError({ reason: 'Payload in body is required' });
16
15
  if (['update', 'delete'].includes(scope) && Array.isArray(req.body)) {
17
16
  return next();
18
17
  }
@@ -36,7 +35,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
36
35
  }
37
36
  const { error } = batchSchema.validate(req.body);
38
37
  if (error) {
39
- throw new FailedValidationException(error.details[0]);
38
+ throw new InvalidPayloadError({ reason: error.details[0].message });
40
39
  }
41
40
  return next();
42
41
  });
@@ -1,5 +1,5 @@
1
1
  import { merge } from 'lodash-es';
2
- import { RateLimiterMemcache, RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
2
+ import { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
3
3
  import env from './env.js';
4
4
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
5
5
  import { createRequire } from 'node:module';
@@ -8,8 +8,6 @@ export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides
8
8
  switch (env['RATE_LIMITER_STORE']) {
9
9
  case 'redis':
10
10
  return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
11
- case 'memcache':
12
- return new RateLimiterMemcache(getConfig('memcache', configPrefix, configOverrides));
13
11
  case 'memory':
14
12
  default:
15
13
  return new RateLimiterMemory(getConfig('memory', configPrefix, configOverrides));
@@ -19,12 +17,7 @@ function getConfig(store = 'memory', configPrefix = 'RATE_LIMITER', overrides) {
19
17
  const config = getConfigFromEnv(`${configPrefix}_`, `${configPrefix}_${store}_`);
20
18
  if (store === 'redis') {
21
19
  const Redis = require('ioredis');
22
- delete config.redis;
23
- config.storeClient = new Redis(env[`${configPrefix}_REDIS`] || getConfigFromEnv(`${configPrefix}_REDIS_`));
24
- }
25
- if (store === 'memcache') {
26
- const Memcached = require('memcached');
27
- config.storeClient = new Memcached(env[`${configPrefix}_MEMCACHE`], getConfigFromEnv(`${configPrefix}_MEMCACHE_`));
20
+ config.storeClient = new Redis(env[`REDIS`] || getConfigFromEnv(`REDIS_`));
28
21
  }
29
22
  delete config.enabled;
30
23
  delete config.store;
@@ -1,8 +1,9 @@
1
1
  import { Action } from '@directus/constants';
2
+ import { isDirectusError } from '@directus/errors';
2
3
  import { uniq } from 'lodash-es';
3
4
  import validateUUID from 'uuid-validate';
4
5
  import env from '../env.js';
5
- import { ForbiddenException } from '../exceptions/forbidden.js';
6
+ import { ErrorCode } from '../errors/index.js';
6
7
  import logger from '../logger.js';
7
8
  import { getPermissions } from '../utils/get-permissions.js';
8
9
  import { Url } from '../utils/url.js';
@@ -80,7 +81,7 @@ ${comment}
80
81
  });
81
82
  }
82
83
  catch (err) {
83
- if (err instanceof ForbiddenException) {
84
+ if (isDirectusError(err, ErrorCode.Forbidden)) {
84
85
  logger.warn(`User ${userID} doesn't have proper permissions to receive notification for this item.`);
85
86
  }
86
87
  else {
@@ -7,10 +7,7 @@ import validateUUID from 'uuid-validate';
7
7
  import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
8
8
  import getDatabase from '../database/index.js';
9
9
  import env from '../env.js';
10
- import { ForbiddenException } from '../exceptions/forbidden.js';
11
- import { IllegalAssetTransformation } from '../exceptions/illegal-asset-transformation.js';
12
- import { RangeNotSatisfiableException } from '../exceptions/range-not-satisfiable.js';
13
- import { ServiceUnavailableException } from '../exceptions/service-unavailable.js';
10
+ import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '../errors/index.js';
14
11
  import logger from '../logger.js';
15
12
  import { getStorage } from '../storage/index.js';
16
13
  import { getMilliseconds } from '../utils/get-milliseconds.js';
@@ -39,23 +36,23 @@ export class AssetsService {
39
36
  */
40
37
  const isValidUUID = validateUUID(id, 4);
41
38
  if (isValidUUID === false)
42
- throw new ForbiddenException();
39
+ throw new ForbiddenError();
43
40
  if (systemPublicKeys.includes(id) === false && this.accountability?.admin !== true) {
44
41
  await this.authorizationService.checkAccess('read', 'directus_files', id);
45
42
  }
46
43
  const file = (await this.knex.select('*').from('directus_files').where({ id }).first());
47
44
  if (!file)
48
- throw new ForbiddenException();
45
+ throw new ForbiddenError();
49
46
  const exists = await storage.location(file.storage).exists(file.filename_disk);
50
47
  if (!exists)
51
- throw new ForbiddenException();
48
+ throw new ForbiddenError();
52
49
  if (range) {
53
50
  const missingRangeLimits = range.start === undefined && range.end === undefined;
54
51
  const endBeforeStart = range.start !== undefined && range.end !== undefined && range.end <= range.start;
55
52
  const startOverflow = range.start !== undefined && range.start >= file.filesize;
56
53
  const endUnderflow = range.end !== undefined && range.end <= 0;
57
54
  if (missingRangeLimits || endBeforeStart || startOverflow || endUnderflow) {
58
- throw new RangeNotSatisfiableException(range);
55
+ throw new RangeNotSatisfiableError({ range });
59
56
  }
60
57
  const lastByte = file.filesize - 1;
61
58
  if (range.end) {
@@ -106,12 +103,14 @@ export class AssetsService {
106
103
  !height ||
107
104
  width > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] ||
108
105
  height > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION']) {
109
- throw new IllegalAssetTransformation(`Image is too large to be transformed, or image size couldn't be determined.`);
106
+ logger.warn(`Image is too large to be transformed, or image size couldn't be determined.`);
107
+ throw new IllegalAssetTransformationError({ invalidTransformations: ['width', 'height'] });
110
108
  }
111
109
  const { queue, process } = sharp.counters();
112
110
  if (queue + process > env['ASSETS_TRANSFORM_MAX_CONCURRENT']) {
113
- throw new ServiceUnavailableException('Server too busy', {
111
+ throw new ServiceUnavailableError({
114
112
  service: 'files',
113
+ reason: 'Server too busy',
115
114
  });
116
115
  }
117
116
  const readStream = await storage.location(file.storage).read(file.filename_disk, range);
@@ -7,7 +7,8 @@ import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
7
7
  import getDatabase from '../database/index.js';
8
8
  import emitter from '../emitter.js';
9
9
  import env from '../env.js';
10
- import { InvalidCredentialsException, InvalidOTPException, InvalidProviderException, UserSuspendedException, } from '../exceptions/index.js';
10
+ import { InvalidCredentialsError, InvalidProviderError, UserSuspendedError } from '../errors/index.js';
11
+ import { InvalidOtpError } from '../errors/index.js';
11
12
  import { createRateLimiter } from '../rate-limiter.js';
12
13
  import { getMilliseconds } from '../utils/get-milliseconds.js';
13
14
  import { stall } from '../utils/stall.js';
@@ -76,16 +77,16 @@ export class AuthenticationService {
76
77
  emitStatus('fail');
77
78
  if (user?.status === 'suspended') {
78
79
  await stall(STALL_TIME, timeStart);
79
- throw new UserSuspendedException();
80
+ throw new UserSuspendedError();
80
81
  }
81
82
  else {
82
83
  await stall(STALL_TIME, timeStart);
83
- throw new InvalidCredentialsException();
84
+ throw new InvalidCredentialsError();
84
85
  }
85
86
  }
86
87
  else if (user.provider !== providerName) {
87
88
  await stall(STALL_TIME, timeStart);
88
- throw new InvalidProviderException();
89
+ throw new InvalidProviderError();
89
90
  }
90
91
  const settingsService = new SettingsService({
91
92
  knex: this.knex,
@@ -117,7 +118,7 @@ export class AuthenticationService {
117
118
  if (user.tfa_secret && !otp) {
118
119
  emitStatus('fail');
119
120
  await stall(STALL_TIME, timeStart);
120
- throw new InvalidOTPException(`"otp" is required`);
121
+ throw new InvalidOtpError();
121
122
  }
122
123
  if (user.tfa_secret && otp) {
123
124
  const tfaService = new TFAService({ knex: this.knex, schema: this.schema });
@@ -125,7 +126,7 @@ export class AuthenticationService {
125
126
  if (otpValid === false) {
126
127
  emitStatus('fail');
127
128
  await stall(STALL_TIME, timeStart);
128
- throw new InvalidOTPException(`"otp" is invalid`);
129
+ throw new InvalidOtpError();
129
130
  }
130
131
  }
131
132
  const tokenPayload = {
@@ -188,7 +189,7 @@ export class AuthenticationService {
188
189
  const STALL_TIME = env['LOGIN_STALL_TIME'];
189
190
  const timeStart = performance.now();
190
191
  if (!refreshToken) {
191
- throw new InvalidCredentialsException();
192
+ throw new InvalidCredentialsError();
192
193
  }
193
194
  const record = await this.knex
194
195
  .select({
@@ -230,17 +231,17 @@ export class AuthenticationService {
230
231
  })
231
232
  .first();
232
233
  if (!record || (!record.share_id && !record.user_id)) {
233
- throw new InvalidCredentialsException();
234
+ throw new InvalidCredentialsError();
234
235
  }
235
236
  if (record.user_id && record.user_status !== 'active') {
236
237
  await this.knex('directus_sessions').where({ token: refreshToken }).del();
237
238
  if (record.user_status === 'suspended') {
238
239
  await stall(STALL_TIME, timeStart);
239
- throw new UserSuspendedException();
240
+ throw new UserSuspendedError();
240
241
  }
241
242
  else {
242
243
  await stall(STALL_TIME, timeStart);
243
- throw new InvalidCredentialsException();
244
+ throw new InvalidCredentialsError();
244
245
  }
245
246
  }
246
247
  if (record.user_id) {
@@ -330,7 +331,7 @@ export class AuthenticationService {
330
331
  .where('id', userID)
331
332
  .first();
332
333
  if (!user) {
333
- throw new InvalidCredentialsException();
334
+ throw new InvalidCredentialsError();
334
335
  }
335
336
  const provider = getAuthProvider(user.provider);
336
337
  await provider.verify(clone(user), password);
@@ -1,6 +1,6 @@
1
1
  import type { Accountability, PermissionsAction, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { AbstractServiceOptions, AST, Item, PrimaryKey } from '../types/index.js';
3
+ import type { AST, AbstractServiceOptions, Item, PrimaryKey } from '../types/index.js';
4
4
  import { PayloadService } from './payload.js';
5
5
  export declare class AuthorizationService {
6
6
  knex: Knex;
@@ -1,9 +1,9 @@
1
- import { FailedValidationException } from '@directus/exceptions';
2
1
  import { validatePayload } from '@directus/utils';
2
+ import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
3
3
  import { cloneDeep, flatten, isArray, isNil, merge, reduce, uniq, uniqWith } from 'lodash-es';
4
4
  import { GENERATE_SPECIAL } from '../constants.js';
5
5
  import getDatabase from '../database/index.js';
6
- import { ForbiddenException } from '../exceptions/index.js';
6
+ import { ForbiddenError } from '../errors/forbidden.js';
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';
@@ -31,7 +31,7 @@ export class AuthorizationService {
31
31
  // If the permissions don't match the collections, you don't have permission to read all of them
32
32
  const uniqueCollectionsRequestedCount = uniq(collectionsRequested.map(({ collection }) => collection)).length;
33
33
  if (uniqueCollectionsRequestedCount !== permissionsForCollections.length) {
34
- throw new ForbiddenException();
34
+ throw new ForbiddenError();
35
35
  }
36
36
  validateFields(ast);
37
37
  validateFilterPermissions(ast, this.schema, action, this.accountability);
@@ -94,7 +94,7 @@ export class AuthorizationService {
94
94
  if (column === '*')
95
95
  continue;
96
96
  if (allowedFields.includes(column) === false)
97
- throw new ForbiddenException();
97
+ throw new ForbiddenError();
98
98
  }
99
99
  }
100
100
  }
@@ -107,7 +107,7 @@ export class AuthorizationService {
107
107
  continue;
108
108
  const fieldKey = stripFunction(childNode.name);
109
109
  if (allowedFields.includes(fieldKey) === false) {
110
- throw new ForbiddenException();
110
+ throw new ForbiddenError();
111
111
  }
112
112
  }
113
113
  }
@@ -189,7 +189,7 @@ export class AuthorizationService {
189
189
  });
190
190
  // Filter key not found in parent collection
191
191
  if (!relation)
192
- throw new ForbiddenException();
192
+ throw new ForbiddenError();
193
193
  const relatedCollectionName = relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
194
194
  // Add the `item` field to the required permissions
195
195
  (result[relatedCollectionName] || (result[relatedCollectionName] = new Set())).add(field);
@@ -211,7 +211,7 @@ export class AuthorizationService {
211
211
  });
212
212
  // Filter key not found in parent collection
213
213
  if (!relation)
214
- throw new ForbiddenException();
214
+ throw new ForbiddenError();
215
215
  parentCollection =
216
216
  relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
217
217
  (result[parentCollection] || (result[parentCollection] = new Set())).add(filterKey);
@@ -262,14 +262,14 @@ export class AuthorizationService {
262
262
  if (action !== 'read' && collection === rootCollection) {
263
263
  const actionPermission = accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === action);
264
264
  if (!actionPermission || !actionPermission.fields) {
265
- throw new ForbiddenException();
265
+ throw new ForbiddenError();
266
266
  }
267
267
  allowedFields = permission?.fields
268
268
  ? [...permission.fields, schema.collections[collection].primary]
269
269
  : [schema.collections[collection].primary];
270
270
  }
271
271
  else if (!permission || !permission.fields) {
272
- throw new ForbiddenException();
272
+ throw new ForbiddenError();
273
273
  }
274
274
  else {
275
275
  allowedFields = permission.fields;
@@ -288,7 +288,7 @@ export class AuthorizationService {
288
288
  originalFieldName = aliasMap[fieldName];
289
289
  }
290
290
  if (!allowedFields.includes(originalFieldName)) {
291
- throw new ForbiddenException();
291
+ throw new ForbiddenError();
292
292
  }
293
293
  }
294
294
  }
@@ -356,14 +356,14 @@ export class AuthorizationService {
356
356
  return permission.collection === collection && permission.action === action;
357
357
  });
358
358
  if (!permission)
359
- throw new ForbiddenException();
359
+ throw new ForbiddenError();
360
360
  // Check if you have permission to access the fields you're trying to access
361
361
  const allowedFields = permission.fields || [];
362
362
  if (allowedFields.includes('*') === false) {
363
363
  const keysInData = Object.keys(payload);
364
364
  const invalidKeys = keysInData.filter((fieldKey) => allowedFields.includes(fieldKey) === false);
365
365
  if (invalidKeys.length > 0) {
366
- throw new ForbiddenException();
366
+ throw new ForbiddenError();
367
367
  }
368
368
  }
369
369
  }
@@ -412,7 +412,7 @@ export class AuthorizationService {
412
412
  }
413
413
  }
414
414
  const validationErrors = [];
415
- validationErrors.push(...flatten(validatePayload(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new FailedValidationException(details)))));
415
+ validationErrors.push(...flatten(validatePayload(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))));
416
416
  if (validationErrors.length > 0)
417
417
  throw validationErrors;
418
418
  return payloadWithPresets;
@@ -431,14 +431,14 @@ export class AuthorizationService {
431
431
  if (Array.isArray(pk)) {
432
432
  const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
433
433
  if (!result)
434
- throw new ForbiddenException();
434
+ throw new ForbiddenError();
435
435
  if (result.length !== pk.length)
436
- throw new ForbiddenException();
436
+ throw new ForbiddenError();
437
437
  }
438
438
  else {
439
439
  const result = await itemsService.readOne(pk, query, { permissionsAction: action });
440
440
  if (!result)
441
- throw new ForbiddenException();
441
+ throw new ForbiddenError();
442
442
  }
443
443
  }
444
444
  }
@@ -8,7 +8,7 @@ import getDatabase, { getSchemaInspector } from '../database/index.js';
8
8
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
9
9
  import emitter from '../emitter.js';
10
10
  import env from '../env.js';
11
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
11
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
12
12
  import { FieldsService } from '../services/fields.js';
13
13
  import { ItemsService } from '../services/items.js';
14
14
  import { getSchema } from '../utils/get-schema.js';
@@ -36,12 +36,12 @@ export class CollectionsService {
36
36
  */
37
37
  async createOne(payload, opts) {
38
38
  if (this.accountability && this.accountability.admin !== true) {
39
- throw new ForbiddenException();
39
+ throw new ForbiddenError();
40
40
  }
41
41
  if (!payload.collection)
42
- throw new InvalidPayloadException(`"collection" is required`);
42
+ throw new InvalidPayloadError({ reason: `"collection" is required` });
43
43
  if (payload.collection.startsWith('directus_')) {
44
- throw new InvalidPayloadException(`Collections can't start with "directus_"`);
44
+ throw new InvalidPayloadError({ reason: `Collections can't start with "directus_"` });
45
45
  }
46
46
  const nestedActionEvents = [];
47
47
  try {
@@ -51,7 +51,7 @@ export class CollectionsService {
51
51
  ...Object.keys(this.schema.collections),
52
52
  ];
53
53
  if (existingCollections.includes(payload.collection)) {
54
- throw new InvalidPayloadException(`Collection "${payload.collection}" already exists.`);
54
+ throw new InvalidPayloadError({ reason: `Collection "${payload.collection}" already exists` });
55
55
  }
56
56
  // Create the collection/fields in a transaction so it'll be reverted in case of errors or
57
57
  // permission problems. This might not work reliably in MySQL, as it doesn't support DDL in
@@ -255,7 +255,7 @@ export class CollectionsService {
255
255
  async readOne(collectionKey) {
256
256
  const result = await this.readMany([collectionKey]);
257
257
  if (result.length === 0)
258
- throw new ForbiddenException();
258
+ throw new ForbiddenError();
259
259
  return result[0];
260
260
  }
261
261
  /**
@@ -270,7 +270,7 @@ export class CollectionsService {
270
270
  const collectionsYouHavePermissionToRead = permissions.map(({ collection }) => collection);
271
271
  for (const collectionKey of collectionKeys) {
272
272
  if (collectionsYouHavePermissionToRead.includes(collectionKey) === false) {
273
- throw new ForbiddenException();
273
+ throw new ForbiddenError();
274
274
  }
275
275
  }
276
276
  }
@@ -283,7 +283,7 @@ export class CollectionsService {
283
283
  */
284
284
  async updateOne(collectionKey, data, opts) {
285
285
  if (this.accountability && this.accountability.admin !== true) {
286
- throw new ForbiddenException();
286
+ throw new ForbiddenError();
287
287
  }
288
288
  const nestedActionEvents = [];
289
289
  try {
@@ -336,10 +336,10 @@ export class CollectionsService {
336
336
  */
337
337
  async updateBatch(data, opts) {
338
338
  if (this.accountability && this.accountability.admin !== true) {
339
- throw new ForbiddenException();
339
+ throw new ForbiddenError();
340
340
  }
341
341
  if (!Array.isArray(data)) {
342
- throw new InvalidPayloadException('Input should be an array of collection changes.');
342
+ throw new InvalidPayloadError({ reason: 'Input should be an array of collection changes' });
343
343
  }
344
344
  const collectionKey = 'collection';
345
345
  const collectionKeys = [];
@@ -352,8 +352,9 @@ export class CollectionsService {
352
352
  schema: this.schema,
353
353
  });
354
354
  for (const payload of data) {
355
- if (!payload[collectionKey])
356
- throw new InvalidPayloadException(`Collection in update misses collection key.`);
355
+ if (!payload[collectionKey]) {
356
+ throw new InvalidPayloadError({ reason: `Collection in update misses collection key` });
357
+ }
357
358
  await collectionItemsService.updateOne(payload[collectionKey], omit(payload, collectionKey), {
358
359
  autoPurgeCache: false,
359
360
  autoPurgeSystemCache: false,
@@ -385,7 +386,7 @@ export class CollectionsService {
385
386
  */
386
387
  async updateMany(collectionKeys, data, opts) {
387
388
  if (this.accountability && this.accountability.admin !== true) {
388
- throw new ForbiddenException();
389
+ throw new ForbiddenError();
389
390
  }
390
391
  const nestedActionEvents = [];
391
392
  try {
@@ -427,14 +428,14 @@ export class CollectionsService {
427
428
  */
428
429
  async deleteOne(collectionKey, opts) {
429
430
  if (this.accountability && this.accountability.admin !== true) {
430
- throw new ForbiddenException();
431
+ throw new ForbiddenError();
431
432
  }
432
433
  const nestedActionEvents = [];
433
434
  try {
434
435
  const collections = await this.readByQuery();
435
436
  const collectionToBeDeleted = collections.find((collection) => collection.collection === collectionKey);
436
437
  if (!!collectionToBeDeleted === false) {
437
- throw new ForbiddenException();
438
+ throw new ForbiddenError();
438
439
  }
439
440
  await this.knex.transaction(async (trx) => {
440
441
  if (collectionToBeDeleted.schema) {
@@ -531,7 +532,7 @@ export class CollectionsService {
531
532
  */
532
533
  async deleteMany(collectionKeys, opts) {
533
534
  if (this.accountability && this.accountability.admin !== true) {
534
- throw new ForbiddenException();
535
+ throw new ForbiddenError();
535
536
  }
536
537
  const nestedActionEvents = [];
537
538
  try {
@@ -4,12 +4,12 @@ import { addFieldFlag, toArray } from '@directus/utils';
4
4
  import { isEqual, isNil } from 'lodash-es';
5
5
  import { clearSystemCache, getCache } from '../cache.js';
6
6
  import { ALIAS_TYPES } from '../constants.js';
7
+ import { translateDatabaseError } from '../database/errors/translate.js';
7
8
  import { getHelpers } from '../database/helpers/index.js';
8
9
  import getDatabase, { getSchemaInspector } from '../database/index.js';
9
10
  import { systemFieldRows } from '../database/system-data/fields/index.js';
10
11
  import emitter from '../emitter.js';
11
- import { translateDatabaseError } from '../exceptions/database/translate.js';
12
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
12
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
13
13
  import { ItemsService } from '../services/items.js';
14
14
  import { PayloadService } from '../services/payload.js';
15
15
  import getDefaultValue from '../utils/get-default-value.js';
@@ -48,7 +48,7 @@ export class FieldsService {
48
48
  async readAll(collection) {
49
49
  let fields;
50
50
  if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
51
- throw new ForbiddenException();
51
+ throw new ForbiddenError();
52
52
  }
53
53
  const nonAuthorizedItemsService = new ItemsService('directus_fields', {
54
54
  knex: this.knex,
@@ -125,7 +125,7 @@ export class FieldsService {
125
125
  allowedFieldsInCollection[permission.collection] = permission.fields ?? [];
126
126
  });
127
127
  if (collection && collection in allowedFieldsInCollection === false) {
128
- throw new ForbiddenException();
128
+ throw new ForbiddenError();
129
129
  }
130
130
  return result.filter((field) => {
131
131
  if (field.collection in allowedFieldsInCollection === false)
@@ -151,17 +151,17 @@ export class FieldsService {
151
151
  async readOne(collection, field) {
152
152
  if (this.accountability && this.accountability.admin !== true) {
153
153
  if (this.hasReadAccess === false) {
154
- throw new ForbiddenException();
154
+ throw new ForbiddenError();
155
155
  }
156
156
  const permissions = this.accountability.permissions.find((permission) => {
157
157
  return permission.action === 'read' && permission.collection === collection;
158
158
  });
159
159
  if (!permissions || !permissions.fields)
160
- throw new ForbiddenException();
160
+ throw new ForbiddenError();
161
161
  if (permissions.fields.includes('*') === false) {
162
162
  const allowedFields = permissions.fields;
163
163
  if (allowedFields.includes(field) === false)
164
- throw new ForbiddenException();
164
+ throw new ForbiddenError();
165
165
  }
166
166
  }
167
167
  let column = undefined;
@@ -179,7 +179,7 @@ export class FieldsService {
179
179
  // Do nothing
180
180
  }
181
181
  if (!column && !fieldInfo)
182
- throw new ForbiddenException();
182
+ throw new ForbiddenError();
183
183
  const type = getLocalType(column, fieldInfo);
184
184
  const columnWithCastDefaultValue = column
185
185
  ? {
@@ -199,7 +199,7 @@ export class FieldsService {
199
199
  async createField(collection, field, table, // allows collection creation to
200
200
  opts) {
201
201
  if (this.accountability && this.accountability.admin !== true) {
202
- throw new ForbiddenException();
202
+ throw new ForbiddenError();
203
203
  }
204
204
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
205
205
  const nestedActionEvents = [];
@@ -208,7 +208,9 @@ export class FieldsService {
208
208
  isNil(await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()) === false;
209
209
  // Check if field already exists, either as a column, or as a row in directus_fields
210
210
  if (exists) {
211
- throw new InvalidPayloadException(`Field "${field.field}" already exists in collection "${collection}"`);
211
+ throw new InvalidPayloadError({
212
+ reason: `Field "${field.field}" already exists in collection "${collection}"`,
213
+ });
212
214
  }
213
215
  // Add flag for specific database type overrides
214
216
  const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
@@ -287,7 +289,7 @@ export class FieldsService {
287
289
  }
288
290
  async updateField(collection, field, opts) {
289
291
  if (this.accountability && this.accountability.admin !== true) {
290
- throw new ForbiddenException();
292
+ throw new ForbiddenError();
291
293
  }
292
294
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
293
295
  const nestedActionEvents = [];
@@ -307,7 +309,7 @@ export class FieldsService {
307
309
  (hookAdjustedField.type === 'alias' ||
308
310
  this.schema.collections[collection].fields[field.field]?.type === 'alias') &&
309
311
  hookAdjustedField.type !== (this.schema.collections[collection].fields[field.field]?.type ?? 'alias')) {
310
- throw new InvalidPayloadException('Alias type cannot be changed');
312
+ throw new InvalidPayloadError({ reason: 'Alias type cannot be changed' });
311
313
  }
312
314
  if (hookAdjustedField.schema) {
313
315
  const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
@@ -384,7 +386,7 @@ export class FieldsService {
384
386
  }
385
387
  async deleteField(collection, field, opts) {
386
388
  if (this.accountability && this.accountability.admin !== true) {
387
- throw new ForbiddenException();
389
+ throw new ForbiddenError();
388
390
  }
389
391
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
390
392
  const nestedActionEvents = [];
@@ -551,7 +553,7 @@ export class FieldsService {
551
553
  column = table[field.type](field.field);
552
554
  }
553
555
  else {
554
- throw new InvalidPayloadException(`Illegal type passed: "${field.type}"`);
556
+ throw new InvalidPayloadError({ reason: `Illegal type passed: "${field.type}"` });
555
557
  }
556
558
  if (field.schema?.default_value !== undefined) {
557
559
  if (typeof field.schema.default_value === 'string' &&