@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
@@ -7,7 +7,7 @@ import { v4 as uuid } from 'uuid';
7
7
  import { parse as wktToGeoJSON } from 'wellknown';
8
8
  import { getHelpers } from '../database/helpers/index.js';
9
9
  import getDatabase from '../database/index.js';
10
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
10
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
11
11
  import { generateHash } from '../utils/generate-hash.js';
12
12
  import { ItemsService } from './items.js';
13
13
  /**
@@ -262,14 +262,14 @@ export class PayloadService {
262
262
  if (dateColumn.type === 'date') {
263
263
  const parsedDate = parseISO(value);
264
264
  if (!isValid(parsedDate)) {
265
- throw new InvalidPayloadException(`Invalid Date format in field "${dateColumn.field}"`);
265
+ throw new InvalidPayloadError({ reason: `Invalid Date format in field "${dateColumn.field}"` });
266
266
  }
267
267
  payload[name] = parsedDate;
268
268
  }
269
269
  if (dateColumn.type === 'dateTime') {
270
270
  const parsedDate = parseISO(value);
271
271
  if (!isValid(parsedDate)) {
272
- throw new InvalidPayloadException(`Invalid DateTime format in field "${dateColumn.field}"`);
272
+ throw new InvalidPayloadError({ reason: `Invalid DateTime format in field "${dateColumn.field}"` });
273
273
  }
274
274
  payload[name] = parsedDate;
275
275
  }
@@ -318,11 +318,15 @@ export class PayloadService {
318
318
  continue;
319
319
  const relatedCollection = payload[relation.meta.one_collection_field];
320
320
  if (!relatedCollection) {
321
- throw new InvalidPayloadException(`Can't update nested record "${relation.collection}.${relation.field}" without field "${relation.collection}.${relation.meta.one_collection_field}" being set`);
321
+ throw new InvalidPayloadError({
322
+ reason: `Can't update nested record "${relation.collection}.${relation.field}" without field "${relation.collection}.${relation.meta.one_collection_field}" being set`,
323
+ });
322
324
  }
323
325
  const allowedCollections = relation.meta.one_allowed_collections;
324
326
  if (allowedCollections.includes(relatedCollection) === false) {
325
- throw new InvalidPayloadException(`"${relation.collection}.${relation.field}" can't be linked to collection "${relatedCollection}"`);
327
+ throw new InvalidPayloadError({
328
+ reason: `"${relation.collection}.${relation.field}" can't be linked to collection "${relatedCollection}"`,
329
+ });
326
330
  }
327
331
  const itemsService = new ItemsService(relatedCollection, {
328
332
  accountability: this.accountability,
@@ -474,7 +478,7 @@ export class PayloadService {
474
478
  .where({ [relatedPrimaryKeyField]: record })
475
479
  .first();
476
480
  if (!!existingRecord === false) {
477
- throw new ForbiddenException();
481
+ throw new ForbiddenError();
478
482
  }
479
483
  // If the related item is already associated to the current item, and there's no
480
484
  // other updates (which is indicated by the fact that this is just the PK, we can
@@ -542,7 +546,7 @@ export class PayloadService {
542
546
  const alterations = field;
543
547
  const { error } = nestedUpdateSchema.validate(alterations);
544
548
  if (error)
545
- throw new InvalidPayloadException(`Invalid one-to-many update structure: ${error.message}`);
549
+ throw new InvalidPayloadError({ reason: `Invalid one-to-many update structure: ${error.message}` });
546
550
  if (alterations.create) {
547
551
  const sortField = relation.meta.sort_field;
548
552
  let createPayload;
@@ -5,7 +5,7 @@ import { getHelpers } from '../database/helpers/index.js';
5
5
  import getDatabase, { getSchemaInspector } from '../database/index.js';
6
6
  import { systemRelationRows } from '../database/system-data/relations/index.js';
7
7
  import emitter from '../emitter.js';
8
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
8
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
9
9
  import { getDefaultIndexName } from '../utils/get-default-index-name.js';
10
10
  import { getSchema } from '../utils/get-schema.js';
11
11
  import { ItemsService } from './items.js';
@@ -37,7 +37,7 @@ export class RelationsService {
37
37
  }
38
38
  async readAll(collection, opts) {
39
39
  if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
40
- throw new ForbiddenException();
40
+ throw new ForbiddenError();
41
41
  }
42
42
  const metaReadQuery = {
43
43
  limit: -1,
@@ -64,17 +64,17 @@ export class RelationsService {
64
64
  async readOne(collection, field) {
65
65
  if (this.accountability && this.accountability.admin !== true) {
66
66
  if (this.hasReadAccess === false) {
67
- throw new ForbiddenException();
67
+ throw new ForbiddenError();
68
68
  }
69
69
  const permissions = this.accountability.permissions?.find((permission) => {
70
70
  return permission.action === 'read' && permission.collection === collection;
71
71
  });
72
72
  if (!permissions || !permissions.fields)
73
- throw new ForbiddenException();
73
+ throw new ForbiddenError();
74
74
  if (permissions.fields.includes('*') === false) {
75
75
  const allowedFields = permissions.fields;
76
76
  if (allowedFields.includes(field) === false)
77
- throw new ForbiddenException();
77
+ throw new ForbiddenError();
78
78
  }
79
79
  }
80
80
  const metaRow = await this.relationsItemService.readByQuery({
@@ -98,7 +98,7 @@ export class RelationsService {
98
98
  const stitched = this.stitchRelations(metaRow, schemaRow ? [schemaRow] : []);
99
99
  const results = await this.filterForbidden(stitched);
100
100
  if (results.length === 0) {
101
- throw new ForbiddenException();
101
+ throw new ForbiddenError();
102
102
  }
103
103
  return results[0];
104
104
  }
@@ -107,30 +107,36 @@ export class RelationsService {
107
107
  */
108
108
  async createOne(relation, opts) {
109
109
  if (this.accountability && this.accountability.admin !== true) {
110
- throw new ForbiddenException();
110
+ throw new ForbiddenError();
111
111
  }
112
112
  if (!relation.collection) {
113
- throw new InvalidPayloadException('"collection" is required');
113
+ throw new InvalidPayloadError({ reason: '"collection" is required' });
114
114
  }
115
115
  if (!relation.field) {
116
- throw new InvalidPayloadException('"field" is required');
116
+ throw new InvalidPayloadError({ reason: '"field" is required' });
117
117
  }
118
118
  if (relation.collection in this.schema.collections === false) {
119
- throw new InvalidPayloadException(`Collection "${relation.collection}" doesn't exist`);
119
+ throw new InvalidPayloadError({ reason: `Collection "${relation.collection}" doesn't exist` });
120
120
  }
121
121
  if (relation.field in this.schema.collections[relation.collection].fields === false) {
122
- throw new InvalidPayloadException(`Field "${relation.field}" doesn't exist in collection "${relation.collection}"`);
122
+ throw new InvalidPayloadError({
123
+ reason: `Field "${relation.field}" doesn't exist in collection "${relation.collection}"`,
124
+ });
123
125
  }
124
126
  // A primary key should not be a foreign key
125
127
  if (this.schema.collections[relation.collection].primary === relation.field) {
126
- throw new InvalidPayloadException(`Field "${relation.field}" in collection "${relation.collection}" is a primary key`);
128
+ throw new InvalidPayloadError({
129
+ reason: `Field "${relation.field}" in collection "${relation.collection}" is a primary key`,
130
+ });
127
131
  }
128
132
  if (relation.related_collection && relation.related_collection in this.schema.collections === false) {
129
- throw new InvalidPayloadException(`Collection "${relation.related_collection}" doesn't exist`);
133
+ throw new InvalidPayloadError({ reason: `Collection "${relation.related_collection}" doesn't exist` });
130
134
  }
131
135
  const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === relation.collection && existingRelation.field === relation.field);
132
136
  if (existingRelation) {
133
- throw new InvalidPayloadException(`Field "${relation.field}" in collection "${relation.collection}" already has an associated relationship`);
137
+ throw new InvalidPayloadError({
138
+ reason: `Field "${relation.field}" in collection "${relation.collection}" already has an associated relationship`,
139
+ });
134
140
  }
135
141
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
136
142
  this.helpers.schema.preRelationChange(relation);
@@ -190,17 +196,19 @@ export class RelationsService {
190
196
  */
191
197
  async updateOne(collection, field, relation, opts) {
192
198
  if (this.accountability && this.accountability.admin !== true) {
193
- throw new ForbiddenException();
199
+ throw new ForbiddenError();
194
200
  }
195
201
  if (collection in this.schema.collections === false) {
196
- throw new InvalidPayloadException(`Collection "${collection}" doesn't exist`);
202
+ throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
197
203
  }
198
204
  if (field in this.schema.collections[collection].fields === false) {
199
- throw new InvalidPayloadException(`Field "${field}" doesn't exist in collection "${collection}"`);
205
+ throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
200
206
  }
201
207
  const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
202
208
  if (!existingRelation) {
203
- throw new InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
209
+ throw new InvalidPayloadError({
210
+ reason: `Field "${field}" in collection "${collection}" doesn't have a relationship.`,
211
+ });
204
212
  }
205
213
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
206
214
  this.helpers.schema.preRelationChange(relation);
@@ -273,17 +281,19 @@ export class RelationsService {
273
281
  */
274
282
  async deleteOne(collection, field, opts) {
275
283
  if (this.accountability && this.accountability.admin !== true) {
276
- throw new ForbiddenException();
284
+ throw new ForbiddenError();
277
285
  }
278
286
  if (collection in this.schema.collections === false) {
279
- throw new InvalidPayloadException(`Collection "${collection}" doesn't exist`);
287
+ throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
280
288
  }
281
289
  if (field in this.schema.collections[collection].fields === false) {
282
- throw new InvalidPayloadException(`Field "${field}" doesn't exist in collection "${collection}"`);
290
+ throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
283
291
  }
284
292
  const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
285
293
  if (!existingRelation) {
286
- throw new InvalidPayloadException(`Field "${field}" in collection "${collection}" doesn't have a relationship.`);
294
+ throw new InvalidPayloadError({
295
+ reason: `Field "${field}" in collection "${collection}" doesn't have a relationship.`,
296
+ });
287
297
  }
288
298
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
289
299
  const nestedActionEvents = [];
@@ -1,4 +1,4 @@
1
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
1
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
2
2
  import { ItemsService } from './items.js';
3
3
  export class RevisionsService extends ItemsService {
4
4
  constructor(options) {
@@ -7,9 +7,9 @@ export class RevisionsService extends ItemsService {
7
7
  async revert(pk) {
8
8
  const revision = await super.readOne(pk);
9
9
  if (!revision)
10
- throw new ForbiddenException();
10
+ throw new ForbiddenError();
11
11
  if (!revision['data'])
12
- throw new InvalidPayloadException(`Revision doesn't contain data to revert to`);
12
+ throw new InvalidPayloadError({ reason: `Revision doesn't contain data to revert to` });
13
13
  const service = new ItemsService(revision['collection'], {
14
14
  accountability: this.accountability,
15
15
  knex: this.knex,
@@ -1,4 +1,4 @@
1
- import { ForbiddenException, UnprocessableEntityException } from '../exceptions/index.js';
1
+ import { ForbiddenError, UnprocessableContentError } from '../errors/index.js';
2
2
  import { ItemsService } from './items.js';
3
3
  import { PermissionsService } from './permissions.js';
4
4
  import { PresetsService } from './presets.js';
@@ -16,13 +16,14 @@ export class RolesService extends ItemsService {
16
16
  .andWhere({ admin_access: true })
17
17
  .first();
18
18
  const otherAdminRolesCount = +(otherAdminRoles?.count || 0);
19
- if (otherAdminRolesCount === 0)
20
- throw new UnprocessableEntityException(`You can't delete the last admin role.`);
19
+ if (otherAdminRolesCount === 0) {
20
+ throw new UnprocessableContentError({ reason: `You can't delete the last admin role` });
21
+ }
21
22
  }
22
23
  async checkForOtherAdminUsers(key, users) {
23
24
  const role = await this.knex.select('admin_access').from('directus_roles').where('id', '=', key).first();
24
25
  if (!role)
25
- throw new ForbiddenException();
26
+ throw new ForbiddenError();
26
27
  // The users that will now be in this new non-admin role
27
28
  let userKeys = [];
28
29
  if (Array.isArray(users)) {
@@ -48,7 +49,7 @@ export class RolesService extends ItemsService {
48
49
  .first();
49
50
  const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
50
51
  if (otherAdminUsersCount === 0) {
51
- throw new UnprocessableEntityException(`You can't remove the last admin user from the admin role.`);
52
+ throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the admin role` });
52
53
  }
53
54
  return;
54
55
  }
@@ -59,7 +60,7 @@ export class RolesService extends ItemsService {
59
60
  }
60
61
  }
61
62
  catch (err) {
62
- (opts || (opts = {})).preMutationException = err;
63
+ (opts || (opts = {})).preMutationError = err;
63
64
  }
64
65
  return super.updateOne(key, data, opts);
65
66
  }
@@ -73,7 +74,7 @@ export class RolesService extends ItemsService {
73
74
  }
74
75
  }
75
76
  catch (err) {
76
- (opts || (opts = {})).preMutationException = err;
77
+ (opts || (opts = {})).preMutationError = err;
77
78
  }
78
79
  return super.updateBatch(data, opts);
79
80
  }
@@ -84,7 +85,7 @@ export class RolesService extends ItemsService {
84
85
  }
85
86
  }
86
87
  catch (err) {
87
- (opts || (opts = {})).preMutationException = err;
88
+ (opts || (opts = {})).preMutationError = err;
88
89
  }
89
90
  return super.updateMany(keys, data, opts);
90
91
  }
@@ -98,7 +99,7 @@ export class RolesService extends ItemsService {
98
99
  await this.checkForOtherAdminRoles(keys);
99
100
  }
100
101
  catch (err) {
101
- opts.preMutationException = err;
102
+ opts.preMutationError = err;
102
103
  }
103
104
  await this.knex.transaction(async (trx) => {
104
105
  const itemsService = new ItemsService('directus_roles', {
@@ -1,8 +1,8 @@
1
1
  import getDatabase from '../database/index.js';
2
- import { ForbiddenException } from '../exceptions/index.js';
2
+ import { ForbiddenError } from '../errors/index.js';
3
3
  import { applyDiff } from '../utils/apply-diff.js';
4
- import { getSnapshot } from '../utils/get-snapshot.js';
5
4
  import { getSnapshotDiff } from '../utils/get-snapshot-diff.js';
5
+ import { getSnapshot } from '../utils/get-snapshot.js';
6
6
  import { getVersionedHash } from '../utils/get-versioned-hash.js';
7
7
  import { validateApplyDiff } from '../utils/validate-diff.js';
8
8
  import { validateSnapshot } from '../utils/validate-snapshot.js';
@@ -15,13 +15,13 @@ export class SchemaService {
15
15
  }
16
16
  async snapshot() {
17
17
  if (this.accountability?.admin !== true)
18
- throw new ForbiddenException();
18
+ throw new ForbiddenError();
19
19
  const currentSnapshot = await getSnapshot({ database: this.knex });
20
20
  return currentSnapshot;
21
21
  }
22
22
  async apply(payload) {
23
23
  if (this.accountability?.admin !== true)
24
- throw new ForbiddenException();
24
+ throw new ForbiddenError();
25
25
  const currentSnapshot = await this.snapshot();
26
26
  const snapshotWithHash = this.getHashedSnapshot(currentSnapshot);
27
27
  if (!validateApplyDiff(payload, snapshotWithHash))
@@ -30,7 +30,7 @@ export class SchemaService {
30
30
  }
31
31
  async diff(snapshot, options) {
32
32
  if (this.accountability?.admin !== true)
33
- throw new ForbiddenException();
33
+ throw new ForbiddenError();
34
34
  validateSnapshot(snapshot, options?.force);
35
35
  const currentSnapshot = options?.currentSnapshot ?? (await getSnapshot({ database: this.knex }));
36
36
  const diff = getSnapshotDiff(currentSnapshot, snapshot);
@@ -13,6 +13,7 @@ import { SERVER_ONLINE } from '../server.js';
13
13
  import { getStorage } from '../storage/index.js';
14
14
  import { version } from '../utils/package.js';
15
15
  import { SettingsService } from './settings.js';
16
+ import { toBoolean } from '../utils/to-boolean.js';
16
17
  export class ServerService {
17
18
  knex;
18
19
  accountability;
@@ -67,6 +68,29 @@ export class ServerService {
67
68
  max: Number.isFinite(env['QUERY_LIMIT_MAX']) ? env['QUERY_LIMIT_MAX'] : -1,
68
69
  };
69
70
  }
71
+ if (this.accountability?.user) {
72
+ if (toBoolean(env['WEBSOCKETS_ENABLED'])) {
73
+ info['websocket'] = {};
74
+ info['websocket'].rest = toBoolean(env['WEBSOCKETS_REST_ENABLED'])
75
+ ? {
76
+ authentication: env['WEBSOCKETS_REST_AUTH'],
77
+ path: env['WEBSOCKETS_REST_PATH'],
78
+ }
79
+ : false;
80
+ info['websocket'].graphql = toBoolean(env['WEBSOCKETS_GRAPHQL_ENABLED'])
81
+ ? {
82
+ authentication: env['WEBSOCKETS_GRAPHQL_AUTH'],
83
+ path: env['WEBSOCKETS_GRAPHQL_PATH'],
84
+ }
85
+ : false;
86
+ info['websocket'].heartbeat = toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED'])
87
+ ? env['WEBSOCKETS_HEARTBEAT_PERIOD']
88
+ : false;
89
+ }
90
+ else {
91
+ info['websocket'] = false;
92
+ }
93
+ }
70
94
  return info;
71
95
  }
72
96
  async health() {
@@ -1,7 +1,7 @@
1
1
  import argon2 from 'argon2';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import env from '../env.js';
4
- import { ForbiddenException, InvalidCredentialsException } from '../exceptions/index.js';
4
+ import { ForbiddenError, InvalidCredentialsError } from '../errors/index.js';
5
5
  import { getMilliseconds } from '../utils/get-milliseconds.js';
6
6
  import { md } from '../utils/md.js';
7
7
  import { Url } from '../utils/url.js';
@@ -51,10 +51,10 @@ export class SharesService extends ItemsService {
51
51
  })
52
52
  .first();
53
53
  if (!record) {
54
- throw new InvalidCredentialsException();
54
+ throw new InvalidCredentialsError();
55
55
  }
56
56
  if (record.share_password && !(await argon2.verify(record.share_password, payload['password']))) {
57
- throw new InvalidCredentialsException();
57
+ throw new InvalidCredentialsError();
58
58
  }
59
59
  await this.knex('directus_shares')
60
60
  .update({ times_used: record.share_times_used + 1 })
@@ -96,7 +96,7 @@ export class SharesService extends ItemsService {
96
96
  */
97
97
  async invite(payload) {
98
98
  if (!this.accountability?.user)
99
- throw new ForbiddenException();
99
+ throw new ForbiddenError();
100
100
  const share = await this.readOne(payload.share, { fields: ['collection'] });
101
101
  const usersService = new UsersService({
102
102
  knex: this.knex,
@@ -1,6 +1,6 @@
1
1
  import { authenticator } from 'otplib';
2
2
  import getDatabase from '../database/index.js';
3
- import { InvalidPayloadException } from '../exceptions/index.js';
3
+ import { InvalidPayloadError } from '../errors/index.js';
4
4
  import { ItemsService } from './items.js';
5
5
  export class TFAService {
6
6
  knex;
@@ -15,17 +15,17 @@ export class TFAService {
15
15
  }
16
16
  const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
17
17
  if (!user?.tfa_secret) {
18
- throw new InvalidPayloadException(`User "${key}" doesn't have TFA enabled.`);
18
+ throw new InvalidPayloadError({ reason: `User "${key}" doesn't have TFA enabled` });
19
19
  }
20
20
  return authenticator.check(otp, user.tfa_secret);
21
21
  }
22
22
  async generateTFA(key) {
23
23
  const user = await this.knex.select('email', 'tfa_secret').from('directus_users').where({ id: key }).first();
24
24
  if (user?.tfa_secret !== null) {
25
- throw new InvalidPayloadException('TFA Secret is already set for this user');
25
+ throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
26
26
  }
27
27
  if (!user?.email) {
28
- throw new InvalidPayloadException('User must have a valid email to enable TFA');
28
+ throw new InvalidPayloadError({ reason: 'User must have a valid email to enable TFA' });
29
29
  }
30
30
  const secret = authenticator.generateSecret();
31
31
  const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
@@ -37,10 +37,10 @@ export class TFAService {
37
37
  async enableTFA(key, otp, secret) {
38
38
  const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
39
39
  if (user?.tfa_secret !== null) {
40
- throw new InvalidPayloadException('TFA Secret is already set for this user');
40
+ throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
41
41
  }
42
42
  if (!authenticator.check(otp, secret)) {
43
- throw new InvalidPayloadException(`"otp" is invalid`);
43
+ throw new InvalidPayloadError({ reason: `"otp" is invalid` });
44
44
  }
45
45
  await this.itemsService.updateOne(key, { tfa_secret: secret });
46
46
  }
@@ -1,7 +1,7 @@
1
1
  import type { Item, PrimaryKey } from '@directus/types';
2
- import { ItemsService } from './items.js';
3
- import type { AbstractServiceOptions } from '../types/services.js';
4
2
  import type { MutationOptions } from '../types/items.js';
3
+ import type { AbstractServiceOptions } from '../types/services.js';
4
+ import { ItemsService } from './items.js';
5
5
  export declare class TranslationsService extends ItemsService {
6
6
  constructor(options: AbstractServiceOptions);
7
7
  private translationKeyExists;
@@ -1,6 +1,6 @@
1
1
  import getDatabase from '../database/index.js';
2
+ import { InvalidPayloadError } from '../errors/index.js';
2
3
  import { ItemsService } from './items.js';
3
- import { InvalidPayloadException } from '../index.js';
4
4
  export class TranslationsService extends ItemsService {
5
5
  constructor(options) {
6
6
  super('directus_translations', options);
@@ -14,20 +14,20 @@ export class TranslationsService extends ItemsService {
14
14
  }
15
15
  async createOne(data, opts) {
16
16
  if (await this.translationKeyExists(data['key'], data['language'])) {
17
- throw new InvalidPayloadException('Duplicate key and language combination.');
17
+ throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
18
18
  }
19
19
  return await super.createOne(data, opts);
20
20
  }
21
21
  async updateMany(keys, data, opts) {
22
22
  if (keys.length > 0 && 'key' in data && 'language' in data) {
23
- throw new InvalidPayloadException('Duplicate key and language combination.');
23
+ throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
24
24
  }
25
25
  else if ('key' in data || 'language' in data) {
26
26
  const items = await this.readMany(keys);
27
27
  for (const item of items) {
28
28
  const updatedData = { ...item, ...data };
29
29
  if (await this.translationKeyExists(updatedData['key'], updatedData['language'])) {
30
- throw new InvalidPayloadException('Duplicate key and language combination.');
30
+ throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
31
31
  }
32
32
  }
33
33
  }