@directus/api 11.0.1 → 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 (283) 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/emitter.d.ts +3 -2
  47. package/dist/emitter.js +12 -4
  48. package/dist/env.js +21 -17
  49. package/dist/errors/codes.d.ts +29 -0
  50. package/dist/errors/codes.js +30 -0
  51. package/dist/errors/contains-null-values.d.ts +7 -0
  52. package/dist/errors/contains-null-values.js +4 -0
  53. package/dist/errors/content-too-large.d.ts +1 -0
  54. package/dist/errors/content-too-large.js +3 -0
  55. package/dist/errors/forbidden.d.ts +1 -0
  56. package/dist/errors/forbidden.js +3 -0
  57. package/dist/errors/hit-rate-limit.d.ts +6 -0
  58. package/dist/errors/hit-rate-limit.js +8 -0
  59. package/dist/errors/illegal-asset-transformation.d.ts +4 -0
  60. package/dist/errors/illegal-asset-transformation.js +3 -0
  61. package/dist/errors/index.d.ts +28 -0
  62. package/dist/errors/index.js +28 -0
  63. package/dist/errors/invalid-credentials.d.ts +1 -0
  64. package/dist/errors/invalid-credentials.js +3 -0
  65. package/dist/errors/invalid-foreign-key.d.ts +6 -0
  66. package/dist/errors/invalid-foreign-key.js +14 -0
  67. package/dist/errors/invalid-ip.d.ts +1 -0
  68. package/dist/errors/invalid-ip.js +3 -0
  69. package/dist/errors/invalid-otp.d.ts +1 -0
  70. package/dist/errors/invalid-otp.js +3 -0
  71. package/dist/errors/invalid-payload.d.ts +5 -0
  72. package/dist/errors/invalid-payload.js +4 -0
  73. package/dist/errors/invalid-provider-config.d.ts +5 -0
  74. package/dist/errors/invalid-provider-config.js +3 -0
  75. package/dist/errors/invalid-provider.d.ts +1 -0
  76. package/dist/errors/invalid-provider.js +3 -0
  77. package/dist/errors/invalid-query.d.ts +5 -0
  78. package/dist/errors/invalid-query.js +4 -0
  79. package/dist/errors/invalid-token.d.ts +1 -0
  80. package/dist/errors/invalid-token.js +3 -0
  81. package/dist/errors/method-not-allowed.d.ts +6 -0
  82. package/dist/errors/method-not-allowed.js +6 -0
  83. package/dist/errors/not-null-violation.d.ts +6 -0
  84. package/dist/errors/not-null-violation.js +14 -0
  85. package/dist/errors/range-not-satisfiable.d.ts +7 -0
  86. package/dist/errors/range-not-satisfiable.js +7 -0
  87. package/dist/errors/record-not-unique.d.ts +6 -0
  88. package/dist/errors/record-not-unique.js +14 -0
  89. package/dist/errors/route-not-found.d.ts +5 -0
  90. package/dist/errors/route-not-found.js +4 -0
  91. package/dist/errors/service-unavailable.d.ts +7 -0
  92. package/dist/errors/service-unavailable.js +4 -0
  93. package/dist/errors/token-expired.d.ts +1 -0
  94. package/dist/errors/token-expired.js +3 -0
  95. package/dist/errors/unexpected-response.d.ts +1 -0
  96. package/dist/errors/unexpected-response.js +3 -0
  97. package/dist/errors/unprocessable-content.d.ts +5 -0
  98. package/dist/errors/unprocessable-content.js +4 -0
  99. package/dist/errors/unsupported-media-type.d.ts +6 -0
  100. package/dist/errors/unsupported-media-type.js +4 -0
  101. package/dist/errors/user-suspended.d.ts +1 -0
  102. package/dist/errors/user-suspended.js +3 -0
  103. package/dist/errors/value-out-of-range.d.ts +6 -0
  104. package/dist/errors/value-out-of-range.js +14 -0
  105. package/dist/errors/value-too-long.d.ts +6 -0
  106. package/dist/errors/value-too-long.js +14 -0
  107. package/dist/extensions.js +0 -4
  108. package/dist/flows.js +6 -8
  109. package/dist/index.d.ts +0 -2
  110. package/dist/index.js +0 -2
  111. package/dist/messenger.d.ts +3 -3
  112. package/dist/messenger.js +18 -9
  113. package/dist/middleware/authenticate.js +2 -38
  114. package/dist/middleware/check-ip.js +2 -2
  115. package/dist/middleware/collection-exists.js +2 -2
  116. package/dist/middleware/error-handler.js +7 -7
  117. package/dist/middleware/graphql.js +11 -9
  118. package/dist/middleware/rate-limiter-global.d.ts +2 -2
  119. package/dist/middleware/rate-limiter-global.js +2 -3
  120. package/dist/middleware/rate-limiter-ip.d.ts +2 -2
  121. package/dist/middleware/rate-limiter-ip.js +2 -3
  122. package/dist/middleware/validate-batch.js +3 -4
  123. package/dist/rate-limiter.js +2 -9
  124. package/dist/server.js +10 -0
  125. package/dist/services/activity.js +3 -2
  126. package/dist/services/assets.js +9 -10
  127. package/dist/services/authentication.js +12 -11
  128. package/dist/services/authorization.d.ts +1 -1
  129. package/dist/services/authorization.js +16 -16
  130. package/dist/services/collections.js +17 -16
  131. package/dist/services/fields.js +16 -14
  132. package/dist/services/files.js +7 -6
  133. package/dist/services/graphql/errors/execution.d.ts +6 -0
  134. package/dist/services/graphql/errors/execution.js +2 -0
  135. package/dist/services/graphql/errors/index.d.ts +2 -0
  136. package/dist/services/graphql/errors/index.js +2 -0
  137. package/dist/services/graphql/errors/validation.d.ts +6 -0
  138. package/dist/services/graphql/errors/validation.js +2 -0
  139. package/dist/services/graphql/index.d.ts +2 -8
  140. package/dist/services/graphql/index.js +125 -66
  141. package/dist/services/graphql/subscription.d.ts +16 -0
  142. package/dist/services/graphql/subscription.js +77 -0
  143. package/dist/services/graphql/utils/process-error.js +3 -3
  144. package/dist/services/import-export.js +7 -7
  145. package/dist/services/index.d.ts +1 -0
  146. package/dist/services/index.js +1 -0
  147. package/dist/services/items.js +14 -13
  148. package/dist/services/mail/index.js +3 -3
  149. package/dist/services/meta.js +3 -3
  150. package/dist/services/payload.js +11 -7
  151. package/dist/services/relations.js +32 -22
  152. package/dist/services/revisions.js +3 -3
  153. package/dist/services/roles.js +10 -9
  154. package/dist/services/schema.js +5 -5
  155. package/dist/services/server.js +24 -0
  156. package/dist/services/shares.js +4 -4
  157. package/dist/services/tfa.js +6 -6
  158. package/dist/services/translations.d.ts +2 -2
  159. package/dist/services/translations.js +4 -4
  160. package/dist/services/users.js +26 -29
  161. package/dist/services/utils.js +4 -4
  162. package/dist/services/websocket.d.ts +14 -0
  163. package/dist/services/websocket.js +26 -0
  164. package/dist/synchronization.js +3 -3
  165. package/dist/types/items.d.ts +2 -2
  166. package/dist/utils/apply-diff.js +13 -4
  167. package/dist/utils/apply-query.js +22 -13
  168. package/dist/utils/get-accountability-for-role.js +1 -2
  169. package/dist/utils/get-accountability-for-token.d.ts +2 -0
  170. package/dist/utils/get-accountability-for-token.js +50 -0
  171. package/dist/utils/get-column-path.js +5 -3
  172. package/dist/utils/get-column.js +3 -3
  173. package/dist/utils/get-service.d.ts +7 -0
  174. package/dist/utils/get-service.js +49 -0
  175. package/dist/utils/jwt.js +5 -5
  176. package/dist/utils/redact.d.ts +4 -0
  177. package/dist/utils/redact.js +15 -1
  178. package/dist/utils/to-boolean.d.ts +4 -0
  179. package/dist/utils/to-boolean.js +6 -0
  180. package/dist/utils/validate-diff.js +23 -9
  181. package/dist/utils/validate-keys.js +3 -3
  182. package/dist/utils/validate-query.d.ts +2 -0
  183. package/dist/utils/validate-query.js +27 -21
  184. package/dist/utils/validate-snapshot.js +11 -5
  185. package/dist/websocket/authenticate.d.ts +6 -0
  186. package/dist/websocket/authenticate.js +59 -0
  187. package/dist/websocket/controllers/base.d.ts +42 -0
  188. package/dist/websocket/controllers/base.js +279 -0
  189. package/dist/websocket/controllers/graphql.d.ts +12 -0
  190. package/dist/websocket/controllers/graphql.js +102 -0
  191. package/dist/websocket/controllers/hooks.d.ts +1 -0
  192. package/dist/websocket/controllers/hooks.js +122 -0
  193. package/dist/websocket/controllers/index.d.ts +10 -0
  194. package/dist/websocket/controllers/index.js +31 -0
  195. package/dist/websocket/controllers/rest.d.ts +9 -0
  196. package/dist/websocket/controllers/rest.js +47 -0
  197. package/dist/websocket/errors.d.ts +16 -0
  198. package/dist/websocket/errors.js +55 -0
  199. package/dist/websocket/handlers/heartbeat.d.ts +11 -0
  200. package/dist/websocket/handlers/heartbeat.js +72 -0
  201. package/dist/websocket/handlers/index.d.ts +4 -0
  202. package/dist/websocket/handlers/index.js +11 -0
  203. package/dist/websocket/handlers/items.d.ts +6 -0
  204. package/dist/websocket/handlers/items.js +103 -0
  205. package/dist/websocket/handlers/subscribe.d.ts +43 -0
  206. package/dist/websocket/handlers/subscribe.js +278 -0
  207. package/dist/websocket/messages.d.ts +311 -0
  208. package/dist/websocket/messages.js +96 -0
  209. package/dist/websocket/types.d.ts +34 -0
  210. package/dist/websocket/types.js +1 -0
  211. package/dist/websocket/utils/get-expires-at-for-token.d.ts +1 -0
  212. package/dist/websocket/utils/get-expires-at-for-token.js +8 -0
  213. package/dist/websocket/utils/message.d.ts +4 -0
  214. package/dist/websocket/utils/message.js +27 -0
  215. package/dist/websocket/utils/wait-for-message.d.ts +4 -0
  216. package/dist/websocket/utils/wait-for-message.js +45 -0
  217. package/package.json +21 -16
  218. package/dist/exceptions/content-too-large.d.ts +0 -4
  219. package/dist/exceptions/content-too-large.js +0 -6
  220. package/dist/exceptions/database/contains-null-values.d.ts +0 -9
  221. package/dist/exceptions/database/contains-null-values.js +0 -6
  222. package/dist/exceptions/database/invalid-foreign-key.d.ts +0 -10
  223. package/dist/exceptions/database/invalid-foreign-key.js +0 -11
  224. package/dist/exceptions/database/not-null-violation.d.ts +0 -9
  225. package/dist/exceptions/database/not-null-violation.js +0 -6
  226. package/dist/exceptions/database/record-not-unique.d.ts +0 -10
  227. package/dist/exceptions/database/record-not-unique.js +0 -11
  228. package/dist/exceptions/database/value-out-of-range.d.ts +0 -10
  229. package/dist/exceptions/database/value-out-of-range.js +0 -11
  230. package/dist/exceptions/database/value-too-long.d.ts +0 -9
  231. package/dist/exceptions/database/value-too-long.js +0 -11
  232. package/dist/exceptions/forbidden.d.ts +0 -6
  233. package/dist/exceptions/forbidden.js +0 -13
  234. package/dist/exceptions/graphql-validation.d.ts +0 -4
  235. package/dist/exceptions/graphql-validation.js +0 -6
  236. package/dist/exceptions/hit-rate-limit.d.ts +0 -9
  237. package/dist/exceptions/hit-rate-limit.js +0 -6
  238. package/dist/exceptions/illegal-asset-transformation.d.ts +0 -4
  239. package/dist/exceptions/illegal-asset-transformation.js +0 -6
  240. package/dist/exceptions/index.d.ts +0 -21
  241. package/dist/exceptions/index.js +0 -21
  242. package/dist/exceptions/invalid-config.d.ts +0 -4
  243. package/dist/exceptions/invalid-config.js +0 -6
  244. package/dist/exceptions/invalid-credentials.d.ts +0 -4
  245. package/dist/exceptions/invalid-credentials.js +0 -6
  246. package/dist/exceptions/invalid-ip.d.ts +0 -4
  247. package/dist/exceptions/invalid-ip.js +0 -6
  248. package/dist/exceptions/invalid-otp.d.ts +0 -4
  249. package/dist/exceptions/invalid-otp.js +0 -6
  250. package/dist/exceptions/invalid-payload.d.ts +0 -4
  251. package/dist/exceptions/invalid-payload.js +0 -6
  252. package/dist/exceptions/invalid-provider.d.ts +0 -4
  253. package/dist/exceptions/invalid-provider.js +0 -6
  254. package/dist/exceptions/invalid-query.d.ts +0 -4
  255. package/dist/exceptions/invalid-query.js +0 -6
  256. package/dist/exceptions/invalid-token.d.ts +0 -4
  257. package/dist/exceptions/invalid-token.js +0 -6
  258. package/dist/exceptions/method-not-allowed.d.ts +0 -8
  259. package/dist/exceptions/method-not-allowed.js +0 -6
  260. package/dist/exceptions/range-not-satisfiable.d.ts +0 -5
  261. package/dist/exceptions/range-not-satisfiable.js +0 -9
  262. package/dist/exceptions/route-not-found.d.ts +0 -4
  263. package/dist/exceptions/route-not-found.js +0 -6
  264. package/dist/exceptions/service-unavailable.d.ts +0 -9
  265. package/dist/exceptions/service-unavailable.js +0 -6
  266. package/dist/exceptions/token-expired.d.ts +0 -4
  267. package/dist/exceptions/token-expired.js +0 -6
  268. package/dist/exceptions/unexpected-response.d.ts +0 -4
  269. package/dist/exceptions/unexpected-response.js +0 -6
  270. package/dist/exceptions/unprocessable-entity.d.ts +0 -4
  271. package/dist/exceptions/unprocessable-entity.js +0 -6
  272. package/dist/exceptions/unsupported-media-type.d.ts +0 -4
  273. package/dist/exceptions/unsupported-media-type.js +0 -6
  274. package/dist/exceptions/user-suspended.d.ts +0 -4
  275. package/dist/exceptions/user-suspended.js +0 -6
  276. /package/dist/{exceptions/database → database/errors}/dialects/mssql.d.ts +0 -0
  277. /package/dist/{exceptions/database → database/errors}/dialects/mysql.d.ts +0 -0
  278. /package/dist/{exceptions/database → database/errors}/dialects/oracle.d.ts +0 -0
  279. /package/dist/{exceptions/database → database/errors}/dialects/postgres.d.ts +0 -0
  280. /package/dist/{exceptions/database → database/errors}/dialects/sqlite.d.ts +0 -0
  281. /package/dist/{exceptions/database → database/errors}/dialects/types.d.ts +0 -0
  282. /package/dist/{exceptions/database → database/errors}/dialects/types.js +0 -0
  283. /package/dist/{exceptions/database → database/errors}/translate.d.ts +0 -0
@@ -1,12 +1,9 @@
1
1
  import { isEqual } from 'lodash-es';
2
2
  import getDatabase from '../database/index.js';
3
3
  import emitter from '../emitter.js';
4
- import env from '../env.js';
5
- import { InvalidCredentialsException } from '../exceptions/index.js';
6
4
  import asyncHandler from '../utils/async-handler.js';
5
+ import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
7
6
  import { getIPFromReq } from '../utils/get-ip-from-req.js';
8
- import isDirectusJWT from '../utils/is-directus-jwt.js';
9
- import { verifyAccessJWT } from '../utils/jwt.js';
10
7
  /**
11
8
  * Verify the passed JWT and assign the user ID and role to `req`
12
9
  */
@@ -36,40 +33,7 @@ export const handler = async (req, _res, next) => {
36
33
  req.accountability = customAccountability;
37
34
  return next();
38
35
  }
39
- req.accountability = defaultAccountability;
40
- if (req.token) {
41
- if (isDirectusJWT(req.token)) {
42
- const payload = verifyAccessJWT(req.token, env['SECRET']);
43
- req.accountability.role = payload.role;
44
- req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;
45
- req.accountability.app = payload.app_access === true || payload.app_access == 1;
46
- if (payload.share)
47
- req.accountability.share = payload.share;
48
- if (payload.share_scope)
49
- req.accountability.share_scope = payload.share_scope;
50
- if (payload.id)
51
- req.accountability.user = payload.id;
52
- }
53
- else {
54
- // Try finding the user with the provided token
55
- const user = await database
56
- .select('directus_users.id', 'directus_users.role', 'directus_roles.admin_access', 'directus_roles.app_access')
57
- .from('directus_users')
58
- .leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
59
- .where({
60
- 'directus_users.token': req.token,
61
- status: 'active',
62
- })
63
- .first();
64
- if (!user) {
65
- throw new InvalidCredentialsException();
66
- }
67
- req.accountability.user = user.id;
68
- req.accountability.role = user.role;
69
- req.accountability.admin = user.admin_access === true || user.admin_access == 1;
70
- req.accountability.app = user.app_access === true || user.app_access == 1;
71
- }
72
- }
36
+ req.accountability = await getAccountabilityForToken(req.token, defaultAccountability);
73
37
  return next();
74
38
  };
75
39
  export default asyncHandler(handler);
@@ -1,5 +1,5 @@
1
1
  import getDatabase from '../database/index.js';
2
- import { InvalidIPException } from '../exceptions/index.js';
2
+ import { InvalidIpError } from '../errors/index.js';
3
3
  import asyncHandler from '../utils/async-handler.js';
4
4
  export const checkIP = asyncHandler(async (req, _res, next) => {
5
5
  const database = getDatabase();
@@ -13,6 +13,6 @@ export const checkIP = asyncHandler(async (req, _res, next) => {
13
13
  const role = await query.first();
14
14
  const ipAllowlist = (role?.ip_access || '').split(',').filter((ip) => ip);
15
15
  if (ipAllowlist.length > 0 && ipAllowlist.includes(req.accountability.ip) === false)
16
- throw new InvalidIPException();
16
+ throw new InvalidIpError();
17
17
  return next();
18
18
  });
@@ -2,13 +2,13 @@
2
2
  * Check if requested collection exists, and save it to req.collection
3
3
  */
4
4
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
5
- import { ForbiddenException } from '../exceptions/index.js';
5
+ import { ForbiddenError } from '../errors/index.js';
6
6
  import asyncHandler from '../utils/async-handler.js';
7
7
  const collectionExists = asyncHandler(async (req, _res, next) => {
8
8
  if (!req.params['collection'])
9
9
  return next();
10
10
  if (req.params['collection'] in req.schema.collections === false) {
11
- throw new ForbiddenException();
11
+ throw new ForbiddenError();
12
12
  }
13
13
  req.collection = req.params['collection'];
14
14
  if (req.collection.startsWith('directus_')) {
@@ -1,9 +1,9 @@
1
- import { BaseException } from '@directus/exceptions';
1
+ import { isDirectusError } from '@directus/errors';
2
2
  import { toArray } from '@directus/utils';
3
3
  import getDatabase from '../database/index.js';
4
4
  import emitter from '../emitter.js';
5
5
  import env from '../env.js';
6
- import { MethodNotAllowedException } from '../exceptions/index.js';
6
+ import { ErrorCode, MethodNotAllowedError } from '../errors/index.js';
7
7
  import logger from '../logger.js';
8
8
  // Note: keep all 4 parameters here. That's how Express recognizes it's the error handler, even if
9
9
  // we don't use next
@@ -12,7 +12,7 @@ const errorHandler = (err, req, res, _next) => {
12
12
  errors: [],
13
13
  };
14
14
  const errors = toArray(err);
15
- if (errors.some((err) => err instanceof BaseException === false)) {
15
+ if (errors.some((err) => isDirectusError(err) === false)) {
16
16
  res.status(500);
17
17
  }
18
18
  else {
@@ -33,18 +33,18 @@ const errorHandler = (err, req, res, _next) => {
33
33
  stack: err.stack,
34
34
  };
35
35
  }
36
- if (err instanceof BaseException) {
36
+ if (isDirectusError(err)) {
37
37
  logger.debug(err);
38
38
  res.status(err.status);
39
39
  payload.errors.push({
40
40
  message: err.message,
41
41
  extensions: {
42
42
  code: err.code,
43
- ...err.extensions,
43
+ ...(err.extensions ?? {}),
44
44
  },
45
45
  });
46
- if (err instanceof MethodNotAllowedException) {
47
- res.header('Allow', err.extensions['allow'].join(', '));
46
+ if (isDirectusError(err, ErrorCode.MethodNotAllowed)) {
47
+ res.header('Allow', err.extensions.allowed.join(', '));
48
48
  }
49
49
  }
50
50
  else {
@@ -1,10 +1,11 @@
1
1
  import { parseJSON } from '@directus/utils';
2
2
  import { getOperationAST, parse, Source } from 'graphql';
3
- import { InvalidPayloadException, InvalidQueryException, MethodNotAllowedException } from '../exceptions/index.js';
3
+ import { InvalidPayloadError, InvalidQueryError, MethodNotAllowedError } from '../errors/index.js';
4
+ import { GraphQLValidationError } from '../services/graphql/errors/validation.js';
4
5
  import asyncHandler from '../utils/async-handler.js';
5
6
  export const parseGraphQL = asyncHandler(async (req, res, next) => {
6
7
  if (req.method !== 'GET' && req.method !== 'POST') {
7
- throw new MethodNotAllowedException('GraphQL only supports GET and POST requests.', { allow: ['GET', 'POST'] });
8
+ throw new MethodNotAllowedError({ allowed: ['GET', 'POST'], current: req.method });
8
9
  }
9
10
  let query = null;
10
11
  let variables = null;
@@ -17,7 +18,7 @@ export const parseGraphQL = asyncHandler(async (req, res, next) => {
17
18
  variables = parseJSON(req.query['variables']);
18
19
  }
19
20
  catch {
20
- throw new InvalidQueryException(`Variables are invalid JSON.`);
21
+ throw new InvalidQueryError({ reason: `Variables are invalid JSON` });
21
22
  }
22
23
  }
23
24
  else {
@@ -31,21 +32,22 @@ export const parseGraphQL = asyncHandler(async (req, res, next) => {
31
32
  operationName = req.body.operationName || null;
32
33
  }
33
34
  if (query === null) {
34
- throw new InvalidPayloadException('Must provide query string.');
35
+ throw new InvalidPayloadError({ reason: 'Must provide query string' });
35
36
  }
36
37
  try {
37
38
  document = parse(new Source(query));
38
39
  }
39
40
  catch (err) {
40
- throw new InvalidPayloadException(`GraphQL schema validation error.`, {
41
- graphqlErrors: [err],
41
+ throw new GraphQLValidationError({
42
+ errors: [err],
42
43
  });
43
44
  }
44
45
  const operationAST = getOperationAST(document, operationName);
45
- // You can only do `query` through GET
46
+ // Mutations can't happen through GET requests
46
47
  if (req.method === 'GET' && operationAST?.operation !== 'query') {
47
- throw new MethodNotAllowedException(`Can only perform a ${operationAST?.operation} from a POST request.`, {
48
- allow: ['POST'],
48
+ throw new MethodNotAllowedError({
49
+ allowed: ['POST'],
50
+ current: 'GET',
49
51
  });
50
52
  }
51
53
  // Prevent caching responses when mutations are made
@@ -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 rateLimiterGlobal: RateLimiterRedis | RateLimiterMemcache | RateLimiterMemory;
4
+ export declare let rateLimiterGlobal: 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 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;
package/dist/server.js CHANGED
@@ -10,6 +10,9 @@ import emitter from './emitter.js';
10
10
  import env from './env.js';
11
11
  import logger from './logger.js';
12
12
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
13
+ import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
14
+ import { startWebSocketHandlers } from './websocket/handlers/index.js';
15
+ import { toBoolean } from './utils/to-boolean.js';
13
16
  export let SERVER_ONLINE = true;
14
17
  export async function createServer() {
15
18
  const server = http.createServer(await createApp());
@@ -66,6 +69,11 @@ export async function createServer() {
66
69
  res.once('finish', complete.bind(null, true));
67
70
  res.once('close', complete.bind(null, false));
68
71
  });
72
+ if (toBoolean(env['WEBSOCKETS_ENABLED']) === true) {
73
+ createSubscriptionController(server);
74
+ createWebSocketController(server);
75
+ startWebSocketHandlers();
76
+ }
69
77
  const terminusOptions = {
70
78
  timeout: env['SERVER_SHUTDOWN_TIMEOUT'] >= 0 && env['SERVER_SHUTDOWN_TIMEOUT'] < Infinity
71
79
  ? env['SERVER_SHUTDOWN_TIMEOUT']
@@ -84,6 +92,8 @@ export async function createServer() {
84
92
  SERVER_ONLINE = false;
85
93
  }
86
94
  async function onSignal() {
95
+ getSubscriptionController()?.terminate();
96
+ getWebSocketController()?.terminate();
87
97
  const database = getDatabase();
88
98
  await database.destroy();
89
99
  logger.info('Database connections destroyed');
@@ -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
  }