@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,6 @@
1
1
  import deepDiff from 'deep-diff';
2
2
  import { cloneDeep, merge, set } from 'lodash-es';
3
- import { clearSystemCache } from '../cache.js';
3
+ import { flushCaches } from '../cache.js';
4
4
  import { getHelpers } from '../database/helpers/index.js';
5
5
  import getDatabase from '../database/index.js';
6
6
  import emitter from '../emitter.js';
@@ -246,7 +246,7 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
246
246
  if (runPostColumnChange) {
247
247
  await helpers.schema.postColumnChange();
248
248
  }
249
- await clearSystemCache();
249
+ await flushCaches();
250
250
  if (nestedActionEvents.length > 0) {
251
251
  const updatedSchema = await getSchema({ database, bypassCache: true });
252
252
  for (const nestedActionEvent of nestedActionEvents) {
@@ -3,7 +3,7 @@ import { clone, isPlainObject } from 'lodash-es';
3
3
  import { customAlphabet } from 'nanoid/non-secure';
4
4
  import validate from 'uuid-validate';
5
5
  import { getHelpers } from '../database/helpers/index.js';
6
- import { InvalidQueryException } from '../exceptions/invalid-query.js';
6
+ import { InvalidQueryError } from '../errors/index.js';
7
7
  import { getColumnPath } from './get-column-path.js';
8
8
  import { getColumn } from './get-column.js';
9
9
  import { getRelationInfo } from './get-relation-info.js';
@@ -78,7 +78,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
78
78
  else if (relationType === 'a2o') {
79
79
  const pathScope = pathParts[0].split(':')[1];
80
80
  if (!pathScope) {
81
- throw new InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
81
+ throw new InvalidQueryError({
82
+ reason: `You have to provide a collection scope when sorting or filtering on a many-to-any item`,
83
+ });
82
84
  }
83
85
  rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
84
86
  joinClause
@@ -112,7 +114,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
112
114
  else if (relationType === 'a2o') {
113
115
  const pathScope = pathParts[0].split(':')[1];
114
116
  if (!pathScope) {
115
- throw new InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
117
+ throw new InvalidQueryError({
118
+ reason: `You have to provide a collection scope when sorting or filtering on a many-to-any item`,
119
+ });
116
120
  }
117
121
  parent = pathScope;
118
122
  }
@@ -287,7 +291,9 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
287
291
  }
288
292
  }
289
293
  if (filterPath.includes('_none') || filterPath.includes('_some')) {
290
- throw new InvalidQueryException(`"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`);
294
+ throw new InvalidQueryError({
295
+ reason: `"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`,
296
+ });
291
297
  }
292
298
  const { columnPath, targetCollection, addNestedPkField } = getColumnPath({
293
299
  path: filterPath,
@@ -313,7 +319,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
313
319
  }
314
320
  function validateFilterField(fields, key, collection = 'unknown') {
315
321
  if (fields[key] === undefined) {
316
- throw new InvalidQueryException(`Invalid filter key "${key}" on "${collection}"`);
322
+ throw new InvalidQueryError({ reason: `Invalid filter key "${key}" on "${collection}"` });
317
323
  }
318
324
  return fields[key];
319
325
  }
@@ -322,11 +328,15 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
322
328
  filterOperator = filterOperator.slice(1);
323
329
  }
324
330
  if (!getFilterOperatorsForType(type).includes(filterOperator)) {
325
- throw new InvalidQueryException(`"${type}" field type does not contain the "_${filterOperator}" filter operator`);
331
+ throw new InvalidQueryError({
332
+ reason: `"${type}" field type does not contain the "_${filterOperator}" filter operator`,
333
+ });
326
334
  }
327
335
  if (special.includes('conceal') &&
328
336
  !getFilterOperatorsForType('hash').includes(filterOperator)) {
329
- throw new InvalidQueryException(`Field with "conceal" special does not allow the "_${filterOperator}" filter operator`);
337
+ throw new InvalidQueryError({
338
+ reason: `Field with "conceal" special does not allow the "_${filterOperator}" filter operator`,
339
+ });
330
340
  }
331
341
  }
332
342
  function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
@@ -1,4 +1,3 @@
1
- import { InvalidConfigException } from '../exceptions/index.js';
2
1
  import { getPermissions } from './get-permissions.js';
3
2
  export async function getAccountabilityForRole(role, context) {
4
3
  let generatedAccountability = context.accountability;
@@ -27,7 +26,7 @@ export async function getAccountabilityForRole(role, context) {
27
26
  .where({ id: role })
28
27
  .first();
29
28
  if (!roleInfo) {
30
- throw new InvalidConfigException(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
29
+ throw new Error(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
31
30
  }
32
31
  generatedAccountability = {
33
32
  role,
@@ -1,7 +1,7 @@
1
1
  import getDatabase from '../database/index.js';
2
- import isDirectusJWT from './is-directus-jwt.js';
3
- import { InvalidCredentialsException } from '../index.js';
4
2
  import env from '../env.js';
3
+ import { InvalidCredentialsError } from '../errors/index.js';
4
+ import isDirectusJWT from './is-directus-jwt.js';
5
5
  import { verifyAccessJWT } from './jwt.js';
6
6
  export async function getAccountabilityForToken(token, accountability) {
7
7
  if (!accountability) {
@@ -38,7 +38,7 @@ export async function getAccountabilityForToken(token, accountability) {
38
38
  })
39
39
  .first();
40
40
  if (!user) {
41
- throw new InvalidCredentialsException();
41
+ throw new InvalidCredentialsError();
42
42
  }
43
43
  accountability.user = user.id;
44
44
  accountability.role = user.role;
@@ -1,4 +1,4 @@
1
- import { InvalidQueryException } from '../exceptions/index.js';
1
+ import { InvalidQueryError } from '../errors/index.js';
2
2
  import { getRelationInfo } from './get-relation-info.js';
3
3
  /**
4
4
  * Converts a Directus field list path to the correct SQL names based on the constructed alias map.
@@ -15,7 +15,7 @@ export function getColumnPath({ path, collection, aliasMap, relations, schema })
15
15
  const pathRoot = pathParts[0].split(':')[0];
16
16
  const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
17
17
  if (!relation) {
18
- throw new InvalidQueryException(`"${parentCollection}.${pathRoot}" is not a relational field`);
18
+ throw new InvalidQueryError({ reason: `"${parentCollection}.${pathRoot}" is not a relational field` });
19
19
  }
20
20
  const alias = parentFields ? aliasMap[`${parentFields}.${pathParts[0]}`]?.alias : aliasMap[pathParts[0]]?.alias;
21
21
  const remainingParts = pathParts.slice(1);
@@ -23,7 +23,9 @@ export function getColumnPath({ path, collection, aliasMap, relations, schema })
23
23
  if (relationType === 'a2o') {
24
24
  const pathScope = pathParts[0].split(':')[1];
25
25
  if (!pathScope) {
26
- throw new InvalidQueryException(`You have to provide a collection scope when sorting on a many-to-any item`);
26
+ throw new InvalidQueryError({
27
+ reason: `You have to provide a collection scope when sorting on a many-to-any item`,
28
+ });
27
29
  }
28
30
  parent = pathScope;
29
31
  }
@@ -1,7 +1,7 @@
1
1
  import { REGEX_BETWEEN_PARENS } from '@directus/constants';
2
2
  import { getFunctionsForType } from '@directus/utils';
3
3
  import { getFunctions } from '../database/helpers/index.js';
4
- import { InvalidQueryException } from '../exceptions/index.js';
4
+ import { InvalidQueryError } from '../errors/index.js';
5
5
  import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
6
6
  /**
7
7
  * Return column prefixed by table. If column includes functions (like `year(date_created)`), the
@@ -25,7 +25,7 @@ export function getColumn(knex, table, column, alias = applyFunctionToColumnName
25
25
  const type = schema?.collections[collectionName]?.fields?.[columnName]?.type ?? 'unknown';
26
26
  const allowedFunctions = getFunctionsForType(type);
27
27
  if (allowedFunctions.includes(functionName) === false) {
28
- throw new InvalidQueryException(`Invalid function specified "${functionName}"`);
28
+ throw new InvalidQueryError({ reason: `Invalid function specified "${functionName}"` });
29
29
  }
30
30
  const result = fn[functionName](table, columnName, {
31
31
  type,
@@ -38,7 +38,7 @@ export function getColumn(knex, table, column, alias = applyFunctionToColumnName
38
38
  return result;
39
39
  }
40
40
  else {
41
- throw new InvalidQueryException(`Invalid function specified "${functionName}"`);
41
+ throw new InvalidQueryError({ reason: `Invalid function specified "${functionName}"` });
42
42
  }
43
43
  }
44
44
  if (alias && column !== alias) {
@@ -1,4 +1,4 @@
1
- import { ItemsService } from '../index.js';
1
+ import { ItemsService } from '../services/index.js';
2
2
  import type { AbstractServiceOptions } from '../types/services.js';
3
3
  /**
4
4
  * Select the correct service for the given collection. This allows the individual services to run
@@ -1,4 +1,4 @@
1
- import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../index.js';
1
+ import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../services/index.js';
2
2
  /**
3
3
  * Select the correct service for the given collection. This allows the individual services to run
4
4
  * their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
package/dist/utils/jwt.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import jwt from 'jsonwebtoken';
2
- import { InvalidTokenException, ServiceUnavailableException, TokenExpiredException } from '../exceptions/index.js';
2
+ import { InvalidTokenError, ServiceUnavailableError, TokenExpiredError } from '../errors/index.js';
3
3
  export function verifyJWT(token, secret) {
4
4
  let payload;
5
5
  try {
@@ -9,13 +9,13 @@ export function verifyJWT(token, secret) {
9
9
  }
10
10
  catch (err) {
11
11
  if (err instanceof jwt.TokenExpiredError) {
12
- throw new TokenExpiredException();
12
+ throw new TokenExpiredError();
13
13
  }
14
14
  else if (err instanceof jwt.JsonWebTokenError) {
15
- throw new InvalidTokenException('Token invalid.');
15
+ throw new InvalidTokenError();
16
16
  }
17
17
  else {
18
- throw new ServiceUnavailableException(`Couldn't verify token.`, { service: 'jwt' });
18
+ throw new ServiceUnavailableError({ service: 'jwt', reason: `Couldn't verify token.` });
19
19
  }
20
20
  }
21
21
  return payload;
@@ -23,7 +23,7 @@ export function verifyJWT(token, secret) {
23
23
  export function verifyAccessJWT(token, secret) {
24
24
  const { id, role, app_access, admin_access, share, share_scope } = verifyJWT(token, secret);
25
25
  if (role === undefined || app_access === undefined || admin_access === undefined) {
26
- throw new InvalidTokenException('Invalid token payload.');
26
+ throw new InvalidTokenError();
27
27
  }
28
28
  return { id, role, app_access, admin_access, share, share_scope };
29
29
  }
@@ -1,5 +1,5 @@
1
1
  import Joi from 'joi';
2
- import { InvalidPayloadException } from '../exceptions/invalid-payload.js';
2
+ import { InvalidPayloadError } from '../errors/index.js';
3
3
  import { DiffKind } from '../types/snapshot.js';
4
4
  const deepDiffSchema = Joi.object({
5
5
  kind: Joi.string()
@@ -49,7 +49,7 @@ const applyJoiSchema = Joi.object({
49
49
  export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
50
50
  const { error } = applyJoiSchema.validate(applyDiff);
51
51
  if (error)
52
- throw new InvalidPayloadException(error.message);
52
+ throw new InvalidPayloadError({ reason: error.message });
53
53
  // No changes to apply
54
54
  if (applyDiff.diff.collections.length === 0 &&
55
55
  applyDiff.diff.fields.length === 0 &&
@@ -64,13 +64,17 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
64
64
  if (diffCollection.diff[0]?.kind === DiffKind.NEW) {
65
65
  const existingCollection = currentSnapshotWithHash.collections.find((c) => c.collection === diffCollection.collection);
66
66
  if (existingCollection) {
67
- throw new InvalidPayloadException(`Provided diff is trying to create collection "${collection}" but it already exists. Please generate a new diff and try again.`);
67
+ throw new InvalidPayloadError({
68
+ reason: `Provided diff is trying to create collection "${collection}" but it already exists. Please generate a new diff and try again`,
69
+ });
68
70
  }
69
71
  }
70
72
  else if (diffCollection.diff[0]?.kind === DiffKind.DELETE) {
71
73
  const existingCollection = currentSnapshotWithHash.collections.find((c) => c.collection === diffCollection.collection);
72
74
  if (!existingCollection) {
73
- throw new InvalidPayloadException(`Provided diff is trying to delete collection "${collection}" but it does not exist. Please generate a new diff and try again.`);
75
+ throw new InvalidPayloadError({
76
+ reason: `Provided diff is trying to delete collection "${collection}" but it does not exist. Please generate a new diff and try again`,
77
+ });
74
78
  }
75
79
  }
76
80
  }
@@ -79,13 +83,17 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
79
83
  if (diffField.diff[0]?.kind === DiffKind.NEW) {
80
84
  const existingField = currentSnapshotWithHash.fields.find((f) => f.collection === diffField.collection && f.field === diffField.field);
81
85
  if (existingField) {
82
- throw new InvalidPayloadException(`Provided diff is trying to create field "${field}" but it already exists. Please generate a new diff and try again.`);
86
+ throw new InvalidPayloadError({
87
+ reason: `Provided diff is trying to create field "${field}" but it already exists. Please generate a new diff and try again`,
88
+ });
83
89
  }
84
90
  }
85
91
  else if (diffField.diff[0]?.kind === DiffKind.DELETE) {
86
92
  const existingField = currentSnapshotWithHash.fields.find((f) => f.collection === diffField.collection && f.field === diffField.field);
87
93
  if (!existingField) {
88
- throw new InvalidPayloadException(`Provided diff is trying to delete field "${field}" but it does not exist. Please generate a new diff and try again.`);
94
+ throw new InvalidPayloadError({
95
+ reason: `Provided diff is trying to delete field "${field}" but it does not exist. Please generate a new diff and try again`,
96
+ });
89
97
  }
90
98
  }
91
99
  }
@@ -96,15 +104,21 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
96
104
  if (diffRelation.diff[0]?.kind === DiffKind.NEW) {
97
105
  const existingRelation = currentSnapshotWithHash.relations.find((r) => r.collection === diffRelation.collection && r.field === diffRelation.field);
98
106
  if (existingRelation) {
99
- throw new InvalidPayloadException(`Provided diff is trying to create relation "${relation}" but it already exists. Please generate a new diff and try again.`);
107
+ throw new InvalidPayloadError({
108
+ reason: `Provided diff is trying to create relation "${relation}" but it already exists. Please generate a new diff and try again`,
109
+ });
100
110
  }
101
111
  }
102
112
  else if (diffRelation.diff[0]?.kind === DiffKind.DELETE) {
103
113
  const existingRelation = currentSnapshotWithHash.relations.find((r) => r.collection === diffRelation.collection && r.field === diffRelation.field);
104
114
  if (!existingRelation) {
105
- throw new InvalidPayloadException(`Provided diff is trying to delete relation "${relation}" but it does not exist. Please generate a new diff and try again.`);
115
+ throw new InvalidPayloadError({
116
+ reason: `Provided diff is trying to delete relation "${relation}" but it does not exist. Please generate a new diff and try again`,
117
+ });
106
118
  }
107
119
  }
108
120
  }
109
- throw new InvalidPayloadException(`Provided hash does not match the current instance's schema hash, indicating the schema has changed after this diff was generated. Please generate a new diff and try again.`);
121
+ throw new InvalidPayloadError({
122
+ reason: `Provided hash does not match the current instance's schema hash, indicating the schema has changed after this diff was generated. Please generate a new diff and try again`,
123
+ });
110
124
  }
@@ -1,5 +1,5 @@
1
+ import { ForbiddenError } from '../errors/index.js';
1
2
  import validateUUID from 'uuid-validate';
2
- import { ForbiddenException } from '../exceptions/forbidden.js';
3
3
  /**
4
4
  * Validate keys based on its type
5
5
  */
@@ -12,10 +12,10 @@ export function validateKeys(schema, collection, keyField, keys) {
12
12
  else {
13
13
  const primaryKeyFieldType = schema.collections[collection]?.fields[keyField]?.type;
14
14
  if (primaryKeyFieldType === 'uuid' && !validateUUID(String(keys))) {
15
- throw new ForbiddenException();
15
+ throw new ForbiddenError();
16
16
  }
17
17
  else if (primaryKeyFieldType === 'integer' && !Number.isInteger(Number(keys))) {
18
- throw new ForbiddenException();
18
+ throw new ForbiddenError();
19
19
  }
20
20
  }
21
21
  }
@@ -1,2 +1,4 @@
1
1
  import type { Query } from '@directus/types';
2
2
  export declare function validateQuery(query: Query): Query;
3
+ export declare function validateBoolean(value: any, key: string): boolean;
4
+ export declare function validateGeometry(value: any, key: string): boolean;
@@ -2,7 +2,7 @@ import Joi from 'joi';
2
2
  import { isPlainObject, uniq } from 'lodash-es';
3
3
  import { stringify } from 'wellknown';
4
4
  import env from '../env.js';
5
- import { InvalidQueryException } from '../exceptions/invalid-query.js';
5
+ import { InvalidQueryError } from '../errors/index.js';
6
6
  import { calculateFieldDepth } from './calculate-field-depth.js';
7
7
  const querySchema = Joi.object({
8
8
  fields: Joi.array().items(Joi.string()),
@@ -31,13 +31,13 @@ export function validateQuery(query) {
31
31
  }
32
32
  validateRelationalDepth(query);
33
33
  if (error) {
34
- throw new InvalidQueryException(error.message);
34
+ throw new InvalidQueryError({ reason: error.message });
35
35
  }
36
36
  return query;
37
37
  }
38
38
  function validateFilter(filter) {
39
39
  if (!filter)
40
- throw new InvalidQueryException('Invalid filter object');
40
+ throw new InvalidQueryError({ reason: 'Invalid filter object' });
41
41
  for (const [key, nested] of Object.entries(filter)) {
42
42
  if (key === '_and' || key === '_or') {
43
43
  nested.forEach(validateFilter);
@@ -73,8 +73,12 @@ function validateFilter(filter) {
73
73
  case '_ncontains':
74
74
  case '_starts_with':
75
75
  case '_nstarts_with':
76
+ case '_istarts_with':
77
+ case '_nistarts_with':
76
78
  case '_ends_with':
77
79
  case '_nends_with':
80
+ case '_iends_with':
81
+ case '_niends_with':
78
82
  case '_gt':
79
83
  case '_gte':
80
84
  case '_lt':
@@ -100,54 +104,56 @@ function validateFilterPrimitive(value, key) {
100
104
  return true;
101
105
  if ((typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value instanceof Date) ===
102
106
  false) {
103
- throw new InvalidQueryException(`The filter value for "${key}" has to be a string, number, or boolean`);
107
+ throw new InvalidQueryError({ reason: `The filter value for "${key}" has to be a string, number, or boolean` });
104
108
  }
105
109
  if (typeof value === 'number' && (Number.isNaN(value) || value > Number.MAX_SAFE_INTEGER)) {
106
- throw new InvalidQueryException(`The filter value for "${key}" is not a valid number`);
110
+ throw new InvalidQueryError({ reason: `The filter value for "${key}" is not a valid number` });
107
111
  }
108
112
  if (typeof value === 'string' && value.length === 0) {
109
- throw new InvalidQueryException(`You can't filter for an empty string in "${key}". Use "_empty" or "_nempty" instead`);
113
+ throw new InvalidQueryError({
114
+ reason: `You can't filter for an empty string in "${key}". Use "_empty" or "_nempty" instead`,
115
+ });
110
116
  }
111
117
  return true;
112
118
  }
113
119
  function validateList(value, key) {
114
120
  if (Array.isArray(value) === false || value.length === 0) {
115
- throw new InvalidQueryException(`"${key}" has to be an array of values`);
121
+ throw new InvalidQueryError({ reason: `"${key}" has to be an array of values` });
116
122
  }
117
123
  return true;
118
124
  }
119
- function validateBoolean(value, key) {
120
- if (value === null)
125
+ export function validateBoolean(value, key) {
126
+ if (value === null || value === '')
121
127
  return true;
122
128
  if (typeof value !== 'boolean') {
123
- throw new InvalidQueryException(`"${key}" has to be a boolean`);
129
+ throw new InvalidQueryError({ reason: `"${key}" has to be a boolean` });
124
130
  }
125
131
  return true;
126
132
  }
127
- function validateGeometry(value, key) {
128
- if (value === null)
133
+ export function validateGeometry(value, key) {
134
+ if (value === null || value === '')
129
135
  return true;
130
136
  try {
131
137
  stringify(value);
132
138
  }
133
139
  catch {
134
- throw new InvalidQueryException(`"${key}" has to be a valid GeoJSON object`);
140
+ throw new InvalidQueryError({ reason: `"${key}" has to be a valid GeoJSON object` });
135
141
  }
136
142
  return true;
137
143
  }
138
144
  function validateAlias(alias) {
139
145
  if (isPlainObject(alias) === false) {
140
- throw new InvalidQueryException(`"alias" has to be an object`);
146
+ throw new InvalidQueryError({ reason: `"alias" has to be an object` });
141
147
  }
142
148
  for (const [key, value] of Object.entries(alias)) {
143
149
  if (typeof key !== 'string') {
144
- throw new InvalidQueryException(`"alias" key has to be a string. "${typeof key}" given.`);
150
+ throw new InvalidQueryError({ reason: `"alias" key has to be a string. "${typeof key}" given` });
145
151
  }
146
152
  if (typeof value !== 'string') {
147
- throw new InvalidQueryException(`"alias" value has to be a string. "${typeof key}" given.`);
153
+ throw new InvalidQueryError({ reason: `"alias" value has to be a string. "${typeof key}" given` });
148
154
  }
149
155
  if (key.includes('.') || value.includes('.')) {
150
- throw new InvalidQueryException(`"alias" key/value can't contain a period character \`.\``);
156
+ throw new InvalidQueryError({ reason: `"alias" key/value can't contain a period character \`.\`` });
151
157
  }
152
158
  }
153
159
  }
@@ -175,26 +181,26 @@ function validateRelationalDepth(query) {
175
181
  fields = uniq(fields);
176
182
  for (const field of fields) {
177
183
  if (field.split('.').length > maxRelationalDepth) {
178
- throw new InvalidQueryException('Max relational depth exceeded.');
184
+ throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
179
185
  }
180
186
  }
181
187
  if (query.filter) {
182
188
  const filterRelationalDepth = calculateFieldDepth(query.filter);
183
189
  if (filterRelationalDepth > maxRelationalDepth) {
184
- throw new InvalidQueryException('Max relational depth exceeded.');
190
+ throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
185
191
  }
186
192
  }
187
193
  if (query.sort) {
188
194
  for (const sort of query.sort) {
189
195
  if (sort.split('.').length > maxRelationalDepth) {
190
- throw new InvalidQueryException('Max relational depth exceeded.');
196
+ throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
191
197
  }
192
198
  }
193
199
  }
194
200
  if (query.deep) {
195
201
  const deepRelationalDepth = calculateFieldDepth(query.deep, ['_sort']);
196
202
  if (deepRelationalDepth > maxRelationalDepth) {
197
- throw new InvalidQueryException('Max relational depth exceeded.');
203
+ throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
198
204
  }
199
205
  }
200
206
  }
@@ -2,7 +2,7 @@ import { TYPES } from '@directus/constants';
2
2
  import Joi from 'joi';
3
3
  import { ALIAS_TYPES } from '../constants.js';
4
4
  import { getDatabaseClient } from '../database/index.js';
5
- import { InvalidPayloadException } from '../exceptions/invalid-payload.js';
5
+ import { InvalidPayloadError } from '../errors/index.js';
6
6
  import { DatabaseClients } from '../types/index.js';
7
7
  import { version as currentDirectusVersion } from './package.js';
8
8
  const snapshotJoiSchema = Joi.object({
@@ -47,18 +47,24 @@ const snapshotJoiSchema = Joi.object({
47
47
  export function validateSnapshot(snapshot, force = false) {
48
48
  const { error } = snapshotJoiSchema.validate(snapshot);
49
49
  if (error)
50
- throw new InvalidPayloadException(error.message);
50
+ throw new InvalidPayloadError({ reason: error.message });
51
51
  // Bypass checks when "force" option is enabled
52
52
  if (force)
53
53
  return;
54
54
  if (snapshot.directus !== currentDirectusVersion) {
55
- throw new InvalidPayloadException(`Provided snapshot's directus version ${snapshot.directus} does not match the current instance's version ${currentDirectusVersion}. You can bypass this check by passing the "force" query parameter.`);
55
+ throw new InvalidPayloadError({
56
+ reason: `Provided snapshot's directus version ${snapshot.directus} does not match the current instance's version ${currentDirectusVersion}. You can bypass this check by passing the "force" query parameter`,
57
+ });
56
58
  }
57
59
  if (!snapshot.vendor) {
58
- throw new InvalidPayloadException('Provided snapshot does not contain the "vendor" property. You can bypass this check by passing the "force" query parameter.');
60
+ throw new InvalidPayloadError({
61
+ reason: 'Provided snapshot does not contain the "vendor" property. You can bypass this check by passing the "force" query parameter',
62
+ });
59
63
  }
60
64
  const currentVendor = getDatabaseClient();
61
65
  if (snapshot.vendor !== currentVendor) {
62
- throw new InvalidPayloadException(`Provided snapshot's vendor ${snapshot.vendor} does not match the current instance's vendor ${currentVendor}. You can bypass this check by passing the "force" query parameter.`);
66
+ throw new InvalidPayloadError({
67
+ reason: `Provided snapshot's vendor ${snapshot.vendor} does not match the current instance's vendor ${currentVendor}. You can bypass this check by passing the "force" query parameter`,
68
+ });
63
69
  }
64
70
  }
@@ -1,11 +1,9 @@
1
1
  import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
2
- import getDatabase from '../database/index.js';
3
- import { InvalidCredentialsException } from '../exceptions/index.js';
4
2
  import { AuthenticationService } from '../services/index.js';
5
- import { getAccountabilityForRole } from '../utils/get-accountability-for-role.js';
6
3
  import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
4
+ import { getPermissions } from '../utils/get-permissions.js';
7
5
  import { getSchema } from '../utils/get-schema.js';
8
- import { WebSocketException } from './exceptions.js';
6
+ import { WebSocketError } from './errors.js';
9
7
  import { getExpiresAtForToken } from './utils/get-expires-at-for-token.js';
10
8
  export async function authenticateConnection(message) {
11
9
  let access_token, refresh_token;
@@ -32,20 +30,19 @@ export async function authenticateConnection(message) {
32
30
  return { accountability, expires_at, refresh_token };
33
31
  }
34
32
  catch (error) {
35
- if (error instanceof InvalidCredentialsException && error.message === 'Token expired.') {
36
- throw new WebSocketException('auth', 'TOKEN_EXPIRED', 'Token expired.', message['uid']);
37
- }
38
- throw new WebSocketException('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
33
+ throw new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
39
34
  }
40
35
  }
41
36
  export async function refreshAccountability(accountability) {
42
- const result = await getAccountabilityForRole(accountability?.role || null, {
43
- accountability: accountability || null,
44
- schema: await getSchema(),
45
- database: getDatabase(),
46
- });
47
- result.user = accountability?.user || null;
48
- return result;
37
+ accountability = accountability ?? {
38
+ role: null,
39
+ user: null,
40
+ admin: false,
41
+ app: false,
42
+ };
43
+ const schema = await getSchema();
44
+ const permissions = await getPermissions(accountability, schema);
45
+ return { ...accountability, permissions };
49
46
  }
50
47
  export function authenticationSuccess(uid, refresh_token) {
51
48
  const message = {