@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
@@ -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);
@@ -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
  }
@@ -1,12 +1,12 @@
1
- import { FailedValidationException } from '@directus/exceptions';
2
1
  import { getSimpleHash, toArray } from '@directus/utils';
2
+ import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
3
3
  import jwt from 'jsonwebtoken';
4
4
  import { cloneDeep, isEmpty } from 'lodash-es';
5
5
  import { performance } from 'perf_hooks';
6
6
  import getDatabase from '../database/index.js';
7
7
  import env from '../env.js';
8
- import { RecordNotUniqueException } from '../exceptions/database/record-not-unique.js';
9
- import { ForbiddenException, InvalidPayloadException, UnprocessableEntityException } from '../exceptions/index.js';
8
+ import { ForbiddenError } from '../errors/forbidden.js';
9
+ import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '../errors/index.js';
10
10
  import isUrlAllowed from '../utils/is-url-allowed.js';
11
11
  import { verifyJWT } from '../utils/jwt.js';
12
12
  import { stall } from '../utils/stall.js';
@@ -29,10 +29,9 @@ export class UsersService extends ItemsService {
29
29
  emails = emails.map((email) => email.toLowerCase());
30
30
  const duplicates = emails.filter((value, index, array) => array.indexOf(value) !== index);
31
31
  if (duplicates.length) {
32
- throw new RecordNotUniqueException('email', {
32
+ throw new RecordNotUniqueError({
33
33
  collection: 'directus_users',
34
34
  field: 'email',
35
- invalid: duplicates[0],
36
35
  });
37
36
  }
38
37
  const query = this.knex
@@ -44,10 +43,9 @@ export class UsersService extends ItemsService {
44
43
  }
45
44
  const results = await query;
46
45
  if (results.length) {
47
- throw new RecordNotUniqueException('email', {
46
+ throw new RecordNotUniqueError({
48
47
  collection: 'directus_users',
49
48
  field: 'email',
50
- invalid: results[0].email,
51
49
  });
52
50
  }
53
51
  }
@@ -70,14 +68,14 @@ export class UsersService extends ItemsService {
70
68
  const regex = new RegExp(wrapped ? policyRegExString.slice(1, -1) : policyRegExString);
71
69
  for (const password of passwords) {
72
70
  if (!regex.test(password)) {
73
- throw new FailedValidationException({
71
+ throw new FailedValidationError(joiValidationErrorItemToErrorExtensions({
74
72
  message: `Provided password doesn't match password policy`,
75
73
  path: ['password'],
76
74
  type: 'custom.pattern.base',
77
75
  context: {
78
76
  value: password,
79
77
  },
80
- });
78
+ }));
81
79
  }
82
80
  }
83
81
  }
@@ -92,7 +90,7 @@ export class UsersService extends ItemsService {
92
90
  .first();
93
91
  const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
94
92
  if (otherAdminUsersCount === 0) {
95
- throw new UnprocessableEntityException(`You can't remove the last admin user from the role.`);
93
+ throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the role` });
96
94
  }
97
95
  }
98
96
  /**
@@ -109,7 +107,7 @@ export class UsersService extends ItemsService {
109
107
  .first();
110
108
  const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
111
109
  if (otherAdminUsersCount === 0) {
112
- throw new UnprocessableEntityException(`You can't change the active status of the last admin user.`);
110
+ throw new UnprocessableContentError({ reason: `You can't change the active status of the last admin user` });
113
111
  }
114
112
  }
115
113
  /**
@@ -154,7 +152,7 @@ export class UsersService extends ItemsService {
154
152
  }
155
153
  }
156
154
  catch (err) {
157
- (opts || (opts = {})).preMutationException = err;
155
+ (opts || (opts = {})).preMutationError = err;
158
156
  }
159
157
  return await super.createMany(data, opts);
160
158
  }
@@ -185,7 +183,7 @@ export class UsersService extends ItemsService {
185
183
  });
186
184
  for (const item of data) {
187
185
  if (!item[primaryKeyField])
188
- throw new InvalidPayloadException(`User in update misses primary key.`);
186
+ throw new InvalidPayloadError({ reason: `User in update misses primary key` });
189
187
  keys.push(await service.updateOne(item[primaryKeyField], item, opts));
190
188
  }
191
189
  });
@@ -209,10 +207,9 @@ export class UsersService extends ItemsService {
209
207
  }
210
208
  if (data['email']) {
211
209
  if (keys.length > 1) {
212
- throw new RecordNotUniqueException('email', {
210
+ throw new RecordNotUniqueError({
213
211
  collection: 'directus_users',
214
212
  field: 'email',
215
- invalid: data['email'],
216
213
  });
217
214
  }
218
215
  await this.checkUniqueEmails([data['email']], keys[0]);
@@ -221,23 +218,23 @@ export class UsersService extends ItemsService {
221
218
  await this.checkPasswordPolicy([data['password']]);
222
219
  }
223
220
  if (data['tfa_secret'] !== undefined) {
224
- throw new InvalidPayloadException(`You can't change the "tfa_secret" value manually.`);
221
+ throw new InvalidPayloadError({ reason: `You can't change the "tfa_secret" value manually` });
225
222
  }
226
223
  if (data['provider'] !== undefined) {
227
224
  if (this.accountability && this.accountability.admin !== true) {
228
- throw new InvalidPayloadException(`You can't change the "provider" value manually.`);
225
+ throw new InvalidPayloadError({ reason: `You can't change the "provider" value manually` });
229
226
  }
230
227
  data['auth_data'] = null;
231
228
  }
232
229
  if (data['external_identifier'] !== undefined) {
233
230
  if (this.accountability && this.accountability.admin !== true) {
234
- throw new InvalidPayloadException(`You can't change the "external_identifier" value manually.`);
231
+ throw new InvalidPayloadError({ reason: `You can't change the "external_identifier" value manually` });
235
232
  }
236
233
  data['auth_data'] = null;
237
234
  }
238
235
  }
239
236
  catch (err) {
240
- (opts || (opts = {})).preMutationException = err;
237
+ (opts || (opts = {})).preMutationError = err;
241
238
  }
242
239
  return await super.updateMany(keys, data, opts);
243
240
  }
@@ -256,7 +253,7 @@ export class UsersService extends ItemsService {
256
253
  await this.checkRemainingAdminExistence(keys);
257
254
  }
258
255
  catch (err) {
259
- (opts || (opts = {})).preMutationException = err;
256
+ (opts || (opts = {})).preMutationError = err;
260
257
  }
261
258
  await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
262
259
  await super.deleteMany(keys, opts);
@@ -281,11 +278,11 @@ export class UsersService extends ItemsService {
281
278
  const opts = {};
282
279
  try {
283
280
  if (url && isUrlAllowed(url, env['USER_INVITE_URL_ALLOW_LIST']) === false) {
284
- throw new InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
281
+ throw new InvalidPayloadError({ reason: `Url "${url}" can't be used to invite users` });
285
282
  }
286
283
  }
287
284
  catch (err) {
288
- opts.preMutationException = err;
285
+ opts.preMutationError = err;
289
286
  }
290
287
  const emails = toArray(email);
291
288
  const mailService = new MailService({
@@ -323,10 +320,10 @@ export class UsersService extends ItemsService {
323
320
  async acceptInvite(token, password) {
324
321
  const { email, scope } = verifyJWT(token, env['SECRET']);
325
322
  if (scope !== 'invite')
326
- throw new ForbiddenException();
323
+ throw new ForbiddenError();
327
324
  const user = await this.getUserByEmail(email);
328
325
  if (user?.status !== 'invited') {
329
- throw new InvalidPayloadException(`Email address ${email} hasn't been invited.`);
326
+ throw new InvalidPayloadError({ reason: `Email address ${email} hasn't been invited` });
330
327
  }
331
328
  // Allow unauthenticated update
332
329
  const service = new UsersService({
@@ -341,10 +338,10 @@ export class UsersService extends ItemsService {
341
338
  const user = await this.getUserByEmail(email);
342
339
  if (user?.status !== 'active') {
343
340
  await stall(STALL_TIME, timeStart);
344
- throw new ForbiddenException();
341
+ throw new ForbiddenError();
345
342
  }
346
343
  if (url && isUrlAllowed(url, env['PASSWORD_RESET_URL_ALLOW_LIST']) === false) {
347
- throw new InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
344
+ throw new InvalidPayloadError({ reason: `Url "${url}" can't be used to reset passwords` });
348
345
  }
349
346
  const mailService = new MailService({
350
347
  schema: this.schema,
@@ -373,17 +370,17 @@ export class UsersService extends ItemsService {
373
370
  async resetPassword(token, password) {
374
371
  const { email, scope, hash } = jwt.verify(token, env['SECRET'], { issuer: 'directus' });
375
372
  if (scope !== 'password-reset' || !hash)
376
- throw new ForbiddenException();
373
+ throw new ForbiddenError();
377
374
  const opts = {};
378
375
  try {
379
376
  await this.checkPasswordPolicy([password]);
380
377
  }
381
378
  catch (err) {
382
- opts.preMutationException = err;
379
+ opts.preMutationError = err;
383
380
  }
384
381
  const user = await this.getUserByEmail(email);
385
382
  if (user?.status !== 'active' || hash !== getSimpleHash('' + user.password)) {
386
- throw new ForbiddenException();
383
+ throw new ForbiddenError();
387
384
  }
388
385
  // Allow unauthenticated update
389
386
  const service = new UsersService({
@@ -1,7 +1,7 @@
1
1
  import getDatabase from '../database/index.js';
2
2
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
3
3
  import emitter from '../emitter.js';
4
- import { ForbiddenException, InvalidPayloadException } from '../exceptions/index.js';
4
+ import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
5
5
  export class UtilsService {
6
6
  knex;
7
7
  accountability;
@@ -16,18 +16,18 @@ export class UtilsService {
16
16
  systemCollectionRows;
17
17
  const sortField = sortFieldResponse?.sort_field;
18
18
  if (!sortField) {
19
- throw new InvalidPayloadException(`Collection "${collection}" doesn't have a sort field.`);
19
+ throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't have a sort field` });
20
20
  }
21
21
  if (this.accountability?.admin !== true) {
22
22
  const permissions = this.accountability?.permissions?.find((permission) => {
23
23
  return permission.collection === collection && permission.action === 'update';
24
24
  });
25
25
  if (!permissions) {
26
- throw new ForbiddenException();
26
+ throw new ForbiddenError();
27
27
  }
28
28
  const allowedFields = permissions.fields ?? [];
29
29
  if (allowedFields[0] !== '*' && allowedFields.includes(sortField) === false) {
30
- throw new ForbiddenException();
30
+ throw new ForbiddenError();
31
31
  }
32
32
  }
33
33
  const primaryKeyField = this.schema.collections[collection].primary;
@@ -73,9 +73,9 @@ class SynchronizationManagerRedis {
73
73
  namespace;
74
74
  client;
75
75
  constructor() {
76
- const config = getConfigFromEnv('SYNCHRONIZATION_REDIS');
77
- this.client = new Redis(env['SYNCHRONIZATION_REDIS'] ?? config);
78
- this.namespace = env['SYNCHRONIZATION_NAMESPACE'] ?? 'directus';
76
+ const config = getConfigFromEnv('REDIS');
77
+ this.client = new Redis(env['REDIS'] ?? config);
78
+ this.namespace = env['SYNCHRONIZATION_NAMESPACE'] ?? 'directus-sync';
79
79
  this.client.defineCommand('setGreaterThan', {
80
80
  numberOfKeys: 1,
81
81
  lua: SET_GREATER_THAN_SCRIPT,
@@ -2,7 +2,7 @@
2
2
  * I know this looks a little silly, but it allows us to explicitly differentiate between when we're
3
3
  * expecting an item vs any other generic object.
4
4
  */
5
- import type { BaseException } from '@directus/exceptions';
5
+ import type { DirectusError } from '@directus/errors';
6
6
  import type { EventContext } from '@directus/types';
7
7
  import type { MutationTracker } from '../services/items.js';
8
8
  export type Item = Record<string, any>;
@@ -46,7 +46,7 @@ export type MutationOptions = {
46
46
  * To keep track of mutation limits
47
47
  */
48
48
  mutationTracker?: MutationTracker | undefined;
49
- preMutationException?: BaseException | undefined;
49
+ preMutationError?: DirectusError | undefined;
50
50
  };
51
51
  export type ActionEventParams = {
52
52
  event: string | string[];