directus 9.23.3 → 9.24.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 (132) hide show
  1. package/dist/app.js +15 -15
  2. package/dist/auth/drivers/ldap.js +22 -22
  3. package/dist/auth/drivers/local.js +7 -7
  4. package/dist/auth/drivers/oauth2.js +27 -25
  5. package/dist/auth/drivers/openid.js +32 -30
  6. package/dist/auth/drivers/saml.js +10 -10
  7. package/dist/auth.js +4 -3
  8. package/dist/cache.js +16 -11
  9. package/dist/cli/commands/bootstrap/index.js +5 -4
  10. package/dist/cli/utils/create-db-connection.js +1 -1
  11. package/dist/cli/utils/create-env/index.js +1 -1
  12. package/dist/constants.d.ts +1 -0
  13. package/dist/constants.js +6 -5
  14. package/dist/controllers/activity.js +10 -10
  15. package/dist/controllers/assets.js +19 -18
  16. package/dist/controllers/auth.js +16 -16
  17. package/dist/controllers/collections.js +11 -11
  18. package/dist/controllers/dashboards.js +11 -11
  19. package/dist/controllers/extensions.js +3 -3
  20. package/dist/controllers/fields.js +17 -17
  21. package/dist/controllers/files.js +18 -17
  22. package/dist/controllers/flows.js +13 -13
  23. package/dist/controllers/folders.js +11 -11
  24. package/dist/controllers/graphql.js +6 -6
  25. package/dist/controllers/items.js +19 -19
  26. package/dist/controllers/notifications.js +11 -11
  27. package/dist/controllers/operations.js +11 -11
  28. package/dist/controllers/panels.js +11 -11
  29. package/dist/controllers/permissions.js +11 -11
  30. package/dist/controllers/presets.js +11 -11
  31. package/dist/controllers/relations.js +11 -11
  32. package/dist/controllers/revisions.js +3 -3
  33. package/dist/controllers/roles.js +11 -11
  34. package/dist/controllers/schema.js +5 -5
  35. package/dist/controllers/server.js +7 -7
  36. package/dist/controllers/settings.js +2 -2
  37. package/dist/controllers/shares.js +14 -14
  38. package/dist/controllers/users.js +16 -16
  39. package/dist/controllers/utils.js +7 -7
  40. package/dist/controllers/webhooks.js +11 -11
  41. package/dist/database/helpers/fn/types.d.ts +0 -1
  42. package/dist/database/helpers/fn/types.js +0 -2
  43. package/dist/database/helpers/index.d.ts +3 -3
  44. package/dist/database/index.js +5 -5
  45. package/dist/database/migrations/20210805B-change-image-metadata-structure.js +15 -15
  46. package/dist/database/migrations/run.js +1 -1
  47. package/dist/database/run-ast.js +6 -6
  48. package/dist/database/system-data/collections/index.js +2 -2
  49. package/dist/database/system-data/fields/index.js +3 -3
  50. package/dist/env.js +1 -1
  51. package/dist/exceptions/database/dialects/mssql.js +2 -2
  52. package/dist/exceptions/database/dialects/mysql.js +6 -6
  53. package/dist/exceptions/database/record-not-unique.d.ts +1 -1
  54. package/dist/extensions.js +10 -10
  55. package/dist/flows.js +33 -31
  56. package/dist/logger.d.ts +1 -0
  57. package/dist/logger.js +32 -32
  58. package/dist/mailer.js +16 -16
  59. package/dist/messenger.js +4 -4
  60. package/dist/middleware/authenticate.d.ts +1 -1
  61. package/dist/middleware/authenticate.js +2 -2
  62. package/dist/middleware/cache.js +11 -11
  63. package/dist/middleware/collection-exists.js +4 -4
  64. package/dist/middleware/cors.js +8 -8
  65. package/dist/middleware/error-handler.js +2 -2
  66. package/dist/middleware/extract-token.js +3 -3
  67. package/dist/middleware/get-permissions.js +1 -1
  68. package/dist/middleware/graphql.js +12 -6
  69. package/dist/middleware/rate-limiter-global.js +5 -5
  70. package/dist/middleware/rate-limiter-ip.js +2 -2
  71. package/dist/middleware/respond.js +16 -16
  72. package/dist/middleware/sanitize-query.js +1 -1
  73. package/dist/middleware/schema.js +1 -1
  74. package/dist/middleware/use-collection.js +1 -1
  75. package/dist/middleware/validate-batch.js +1 -1
  76. package/dist/operations/exec/index.js +2 -2
  77. package/dist/rate-limiter.js +1 -1
  78. package/dist/request/validate-ip.js +2 -2
  79. package/dist/server.js +4 -4
  80. package/dist/services/activity.js +14 -14
  81. package/dist/services/assets.js +6 -6
  82. package/dist/services/authentication.js +9 -9
  83. package/dist/services/collections.js +9 -9
  84. package/dist/services/fields.js +5 -5
  85. package/dist/services/files.js +18 -18
  86. package/dist/services/graphql/index.js +170 -116
  87. package/dist/services/import-export.js +6 -6
  88. package/dist/services/items.js +6 -6
  89. package/dist/services/mail/index.js +5 -5
  90. package/dist/services/meta.js +1 -0
  91. package/dist/services/notifications.js +4 -4
  92. package/dist/services/relations.js +4 -4
  93. package/dist/services/revisions.js +3 -3
  94. package/dist/services/roles.js +5 -5
  95. package/dist/services/server.js +27 -27
  96. package/dist/services/shares.js +9 -9
  97. package/dist/services/specifications.js +5 -3
  98. package/dist/services/users.d.ts +1 -5
  99. package/dist/services/users.js +24 -27
  100. package/dist/storage/register-locations.js +1 -1
  101. package/dist/utils/apply-diff.js +12 -12
  102. package/dist/utils/apply-query.js +3 -2
  103. package/dist/utils/dynamic-import.js +1 -1
  104. package/dist/utils/generate-hash.js +1 -1
  105. package/dist/utils/get-ast-from-query.js +2 -2
  106. package/dist/utils/get-auth-providers.js +1 -1
  107. package/dist/utils/get-cache-headers.js +3 -3
  108. package/dist/utils/get-collection-from-alias.js +1 -0
  109. package/dist/utils/get-column-path.js +2 -1
  110. package/dist/utils/get-default-value.js +1 -1
  111. package/dist/utils/get-ip-from-req.js +2 -2
  112. package/dist/utils/get-permissions.js +11 -11
  113. package/dist/utils/get-schema.js +5 -5
  114. package/dist/utils/get-snapshot-diff.js +1 -1
  115. package/dist/utils/is-url-allowed.js +5 -2
  116. package/dist/utils/parse-image-metadata.js +3 -3
  117. package/dist/utils/reduce-schema.js +5 -5
  118. package/dist/utils/sanitize-query.js +26 -26
  119. package/dist/utils/should-skip-cache.js +13 -4
  120. package/dist/utils/strip-function.js +1 -1
  121. package/dist/utils/telemetry.d.ts +1 -0
  122. package/dist/utils/telemetry.js +30 -0
  123. package/dist/utils/validate-keys.js +1 -1
  124. package/dist/utils/validate-query.js +1 -1
  125. package/dist/utils/validate-storage.js +8 -8
  126. package/dist/webhooks.js +2 -2
  127. package/package.json +13 -13
  128. package/dist/utils/redact-header-cookies.d.ts +0 -1
  129. package/dist/utils/redact-header-cookies.js +0 -11
  130. package/dist/utils/track.d.ts +0 -1
  131. package/dist/utils/track.js +0 -81
  132. /package/dist/{utils/redact-header-cookies.test.d.ts → logger.test.d.ts} +0 -0
@@ -20,9 +20,6 @@ const items_1 = require("./items");
20
20
  const mail_1 = require("./mail");
21
21
  const settings_1 = require("./settings");
22
22
  class UsersService extends items_1.ItemsService {
23
- knex;
24
- accountability;
25
- schema;
26
23
  constructor(options) {
27
24
  super('directus_users', options);
28
25
  this.knex = options.knex || (0, database_1.default)();
@@ -131,8 +128,8 @@ class UsersService extends items_1.ItemsService {
131
128
  * Create multiple new users
132
129
  */
133
130
  async createMany(data, opts) {
134
- const emails = data.map((payload) => payload.email).filter((email) => email);
135
- const passwords = data.map((payload) => payload.password).filter((password) => password);
131
+ const emails = data['map']((payload) => payload['email']).filter((email) => email);
132
+ const passwords = data['map']((payload) => payload['password']).filter((password) => password);
136
133
  try {
137
134
  if (emails.length) {
138
135
  await this.checkUniqueEmails(emails);
@@ -182,44 +179,44 @@ class UsersService extends items_1.ItemsService {
182
179
  */
183
180
  async updateMany(keys, data, opts) {
184
181
  try {
185
- if (data.role) {
186
- // data.role will be an object with id with GraphQL mutations
187
- const roleId = data.role?.id ?? data.role;
182
+ if (data['role']) {
183
+ // data['role'] will be an object with id with GraphQL mutations
184
+ const roleId = data['role']?.id ?? data['role'];
188
185
  const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
189
186
  if (!newRole?.admin_access) {
190
187
  await this.checkRemainingAdminExistence(keys);
191
188
  }
192
189
  }
193
- if (data.status !== undefined && data.status !== 'active') {
190
+ if (data['status'] !== undefined && data['status'] !== 'active') {
194
191
  await this.checkRemainingActiveAdmin(keys);
195
192
  }
196
- if (data.email) {
193
+ if (data['email']) {
197
194
  if (keys.length > 1) {
198
195
  throw new record_not_unique_1.RecordNotUniqueException('email', {
199
196
  collection: 'directus_users',
200
197
  field: 'email',
201
- invalid: data.email,
198
+ invalid: data['email'],
202
199
  });
203
200
  }
204
- await this.checkUniqueEmails([data.email], keys[0]);
201
+ await this.checkUniqueEmails([data['email']], keys[0]);
205
202
  }
206
- if (data.password) {
207
- await this.checkPasswordPolicy([data.password]);
203
+ if (data['password']) {
204
+ await this.checkPasswordPolicy([data['password']]);
208
205
  }
209
- if (data.tfa_secret !== undefined) {
206
+ if (data['tfa_secret'] !== undefined) {
210
207
  throw new exceptions_2.InvalidPayloadException(`You can't change the "tfa_secret" value manually.`);
211
208
  }
212
- if (data.provider !== undefined) {
209
+ if (data['provider'] !== undefined) {
213
210
  if (this.accountability && this.accountability.admin !== true) {
214
211
  throw new exceptions_2.InvalidPayloadException(`You can't change the "provider" value manually.`);
215
212
  }
216
- data.auth_data = null;
213
+ data['auth_data'] = null;
217
214
  }
218
- if (data.external_identifier !== undefined) {
215
+ if (data['external_identifier'] !== undefined) {
219
216
  if (this.accountability && this.accountability.admin !== true) {
220
217
  throw new exceptions_2.InvalidPayloadException(`You can't change the "external_identifier" value manually.`);
221
218
  }
222
- data.auth_data = null;
219
+ data['auth_data'] = null;
223
220
  }
224
221
  }
225
222
  catch (err) {
@@ -266,7 +263,7 @@ class UsersService extends items_1.ItemsService {
266
263
  async inviteUser(email, role, url, subject) {
267
264
  const opts = {};
268
265
  try {
269
- if (url && (0, is_url_allowed_1.default)(url, env_1.default.USER_INVITE_URL_ALLOW_LIST) === false) {
266
+ if (url && (0, is_url_allowed_1.default)(url, env_1.default['USER_INVITE_URL_ALLOW_LIST']) === false) {
270
267
  throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
271
268
  }
272
269
  }
@@ -280,9 +277,9 @@ class UsersService extends items_1.ItemsService {
280
277
  });
281
278
  for (const email of emails) {
282
279
  const payload = { email, scope: 'invite' };
283
- const token = jsonwebtoken_1.default.sign(payload, env_1.default.SECRET, { expiresIn: '7d', issuer: 'directus' });
280
+ const token = jsonwebtoken_1.default.sign(payload, env_1.default['SECRET'], { expiresIn: '7d', issuer: 'directus' });
284
281
  const subjectLine = subject ?? "You've been invited";
285
- const inviteURL = url ? new url_1.Url(url) : new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'accept-invite');
282
+ const inviteURL = url ? new url_1.Url(url) : new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'accept-invite');
286
283
  inviteURL.setQuery('token', token);
287
284
  // Create user first to verify uniqueness
288
285
  await this.createOne({ email, role, status: 'invited' }, opts);
@@ -300,7 +297,7 @@ class UsersService extends items_1.ItemsService {
300
297
  }
301
298
  }
302
299
  async acceptInvite(token, password) {
303
- const { email, scope } = jsonwebtoken_1.default.verify(token, env_1.default.SECRET, { issuer: 'directus' });
300
+ const { email, scope } = jsonwebtoken_1.default.verify(token, env_1.default['SECRET'], { issuer: 'directus' });
304
301
  if (scope !== 'invite')
305
302
  throw new exceptions_2.ForbiddenException();
306
303
  const user = await this.knex.select('id', 'status').from('directus_users').where({ email }).first();
@@ -326,7 +323,7 @@ class UsersService extends items_1.ItemsService {
326
323
  await (0, stall_1.stall)(STALL_TIME, timeStart);
327
324
  throw new exceptions_2.ForbiddenException();
328
325
  }
329
- if (url && (0, is_url_allowed_1.default)(url, env_1.default.PASSWORD_RESET_URL_ALLOW_LIST) === false) {
326
+ if (url && (0, is_url_allowed_1.default)(url, env_1.default['PASSWORD_RESET_URL_ALLOW_LIST']) === false) {
330
327
  throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
331
328
  }
332
329
  const mailService = new mail_1.MailService({
@@ -335,10 +332,10 @@ class UsersService extends items_1.ItemsService {
335
332
  accountability: this.accountability,
336
333
  });
337
334
  const payload = { email, scope: 'password-reset', hash: (0, utils_1.getSimpleHash)('' + user.password) };
338
- const token = jsonwebtoken_1.default.sign(payload, env_1.default.SECRET, { expiresIn: '1d', issuer: 'directus' });
335
+ const token = jsonwebtoken_1.default.sign(payload, env_1.default['SECRET'], { expiresIn: '1d', issuer: 'directus' });
339
336
  const acceptURL = url
340
337
  ? new url_1.Url(url).setQuery('token', token).toString()
341
- : new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'reset-password').setQuery('token', token);
338
+ : new url_1.Url(env_1.default['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token).toString();
342
339
  const subjectLine = subject ? subject : 'Password Reset Request';
343
340
  await mailService.send({
344
341
  to: email,
@@ -354,7 +351,7 @@ class UsersService extends items_1.ItemsService {
354
351
  await (0, stall_1.stall)(STALL_TIME, timeStart);
355
352
  }
356
353
  async resetPassword(token, password) {
357
- const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default.SECRET, { issuer: 'directus' });
354
+ const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default['SECRET'], { issuer: 'directus' });
358
355
  if (scope !== 'password-reset' || !hash)
359
356
  throw new exceptions_2.ForbiddenException();
360
357
  const opts = {};
@@ -6,7 +6,7 @@ const env_1 = require("../env");
6
6
  const get_config_from_env_1 = require("../utils/get-config-from-env");
7
7
  const registerLocations = async (storage) => {
8
8
  const env = (0, env_1.getEnv)();
9
- const locations = (0, utils_1.toArray)(env.STORAGE_LOCATIONS);
9
+ const locations = (0, utils_1.toArray)(env['STORAGE_LOCATIONS']);
10
10
  locations.forEach((location) => {
11
11
  location = location.trim();
12
12
  const driverConfig = (0, get_config_from_env_1.getConfigFromEnv)(`STORAGE_${location.toUpperCase()}_`);
@@ -27,7 +27,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
27
27
  const getNestedCollectionsToDelete = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => diff[0].lhs?.meta?.group === currentLevelCollection);
28
28
  const createCollections = async (collections) => {
29
29
  for (const { collection, diff } of collections) {
30
- if (diff?.[0].kind === types_1.DiffKind.NEW && diff[0].rhs) {
30
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW && diff[0].rhs) {
31
31
  // We'll nest the to-be-created fields in the same collection creation, to prevent
32
32
  // creating a collection without a primary key
33
33
  const fields = snapshotDiff.fields
@@ -64,7 +64,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
64
64
  };
65
65
  const deleteCollections = async (collections) => {
66
66
  for (const { collection, diff } of collections) {
67
- if (diff?.[0].kind === types_1.DiffKind.DELETE) {
67
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE) {
68
68
  const relations = schema.relations.filter((r) => r.related_collection === collection || r.collection === collection);
69
69
  if (relations.length > 0) {
70
70
  const relationsService = new services_1.RelationsService({ knex: trx, schema });
@@ -94,7 +94,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
94
94
  // Finds all collections that need to be created
95
95
  const filterCollectionsForCreation = ({ diff }) => {
96
96
  // Check new collections only
97
- const isNewCollection = diff[0].kind === types_1.DiffKind.NEW;
97
+ const isNewCollection = diff[0]?.kind === types_1.DiffKind.NEW;
98
98
  if (!isNewCollection)
99
99
  return false;
100
100
  // Create now if no group
@@ -111,7 +111,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
111
111
  // TopLevelCollection - I exist in current schema
112
112
  // NestedCollection - I exist in snapshotDiff as a new collection
113
113
  // TheCurrentCollectionInIteration - I exist in snapshotDiff as a new collection but will be created as part of NestedCollection
114
- const parentWillBeCreatedInThisApply = snapshotDiff.collections.filter(({ collection, diff }) => diff[0].kind === types_1.DiffKind.NEW && collection === groupName).length > 0;
114
+ const parentWillBeCreatedInThisApply = snapshotDiff.collections.filter(({ collection, diff }) => diff[0]?.kind === types_1.DiffKind.NEW && collection === groupName).length > 0;
115
115
  // Has group, but parent is not new, parent is also not being created in this snapshot apply
116
116
  if (parentExists && !parentWillBeCreatedInThisApply)
117
117
  return true;
@@ -121,9 +121,9 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
121
121
  // then continue with nested collections recursively
122
122
  await createCollections(snapshotDiff.collections.filter(filterCollectionsForCreation));
123
123
  // delete top level collections (no group) first, then continue with nested collections recursively
124
- await deleteCollections(snapshotDiff.collections.filter(({ diff }) => diff[0].kind === types_1.DiffKind.DELETE && diff[0].lhs.meta?.group === null));
124
+ await deleteCollections(snapshotDiff.collections.filter(({ diff }) => diff[0]?.kind === types_1.DiffKind.DELETE && diff[0].lhs.meta?.group === null));
125
125
  for (const { collection, diff } of snapshotDiff.collections) {
126
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY) {
126
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY) {
127
127
  const currentCollection = currentSnapshot.collections.find((field) => {
128
128
  return field.collection === collection;
129
129
  });
@@ -147,7 +147,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
147
147
  schema: await (0, get_schema_1.getSchema)({ database: trx, bypassCache: true }),
148
148
  });
149
149
  for (const { collection, field, diff } of snapshotDiff.fields) {
150
- if (diff?.[0].kind === types_1.DiffKind.NEW && !isNestedMetaUpdate(diff?.[0])) {
150
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW && !isNestedMetaUpdate(diff?.[0])) {
151
151
  try {
152
152
  await fieldsService.createField(collection, diff[0].rhs, undefined, mutationOptions);
153
153
  }
@@ -156,7 +156,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
156
156
  throw err;
157
157
  }
158
158
  }
159
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY || isNestedMetaUpdate(diff?.[0])) {
159
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY || isNestedMetaUpdate(diff[0])) {
160
160
  const currentField = currentSnapshot.fields.find((snapshotField) => {
161
161
  return snapshotField.collection === collection && snapshotField.field === field;
162
162
  });
@@ -174,7 +174,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
174
174
  }
175
175
  }
176
176
  }
177
- if (diff?.[0].kind === types_1.DiffKind.DELETE && !isNestedMetaUpdate(diff?.[0])) {
177
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE && !isNestedMetaUpdate(diff?.[0])) {
178
178
  try {
179
179
  await fieldsService.deleteField(collection, field, mutationOptions);
180
180
  }
@@ -196,7 +196,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
196
196
  for (const diffEdit of diff) {
197
197
  (0, lodash_1.set)(structure, diffEdit.path, undefined);
198
198
  }
199
- if (diff?.[0].kind === types_1.DiffKind.NEW) {
199
+ if (diff?.[0]?.kind === types_1.DiffKind.NEW) {
200
200
  try {
201
201
  await relationsService.createOne(diff[0].rhs, mutationOptions);
202
202
  }
@@ -205,7 +205,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
205
205
  throw err;
206
206
  }
207
207
  }
208
- if (diff?.[0].kind === types_1.DiffKind.EDIT || diff?.[0].kind === types_1.DiffKind.ARRAY) {
208
+ if (diff?.[0]?.kind === types_1.DiffKind.EDIT || diff?.[0]?.kind === types_1.DiffKind.ARRAY) {
209
209
  const currentRelation = currentSnapshot.relations.find((relation) => {
210
210
  return relation.collection === collection && relation.field === field;
211
211
  });
@@ -223,7 +223,7 @@ async function applyDiff(currentSnapshot, snapshotDiff, options) {
223
223
  }
224
224
  }
225
225
  }
226
- if (diff?.[0].kind === types_1.DiffKind.DELETE) {
226
+ if (diff?.[0]?.kind === types_1.DiffKind.DELETE) {
227
227
  try {
228
228
  await relationsService.deleteOne(collection, field, mutationOptions);
229
229
  }
@@ -171,6 +171,7 @@ function applySort(knex, schema, rootQuery, rootSort, collection, aliasMap, retu
171
171
  // Clears the order if any, eg: from MSSQL offset
172
172
  rootQuery.clear('order');
173
173
  rootQuery.orderBy(sortRecords);
174
+ return undefined;
174
175
  }
175
176
  exports.applySort = applySort;
176
177
  function applyLimit(knex, rootQuery, limit) {
@@ -337,7 +338,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap)
337
338
  }
338
339
  if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
339
340
  dbQuery[logical].andWhere((query) => {
340
- query.whereNotNull(key).orWhere(key, '!=', '');
341
+ query.whereNotNull(key).andWhere(key, '!=', '');
341
342
  });
342
343
  }
343
344
  // The following fields however, require a value to be run. If no value is passed, we
@@ -361,7 +362,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap)
361
362
  }
362
363
  // Cast filter value (compareValue) based on type of field being filtered against
363
364
  const [collection, field] = key.split('.');
364
- const mappedCollection = originalCollectionName || collection;
365
+ const mappedCollection = (originalCollectionName || collection);
365
366
  if (mappedCollection in schema.collections && field in schema.collections[mappedCollection].fields) {
366
367
  const type = schema.collections[mappedCollection].fields[field].type;
367
368
  if (['date', 'dateTime', 'time', 'timestamp'].includes(type)) {
@@ -2,6 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dynamicImport = void 0;
4
4
  const dynamicImport = async (mod) => {
5
- return process.env.VITEST ? await import(mod) : require(mod);
5
+ return process.env['VITEST'] ? await import(mod) : require(mod);
6
6
  };
7
7
  exports.dynamicImport = dynamicImport;
@@ -10,7 +10,7 @@ function generateHash(stringToHash) {
10
10
  const argon2HashConfigOptions = (0, get_config_from_env_1.getConfigFromEnv)('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805
11
11
  // associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
12
12
  'associatedData' in argon2HashConfigOptions &&
13
- (argon2HashConfigOptions.associatedData = Buffer.from(argon2HashConfigOptions.associatedData));
13
+ (argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']));
14
14
  return argon2_1.default.hash(stringToHash, argon2HashConfigOptions);
15
15
  }
16
16
  exports.generateHash = generateHash;
@@ -181,7 +181,7 @@ async function getASTFromQuery(collection, query, schema, options) {
181
181
  continue;
182
182
  }
183
183
  // update query alias for children parseFields
184
- const deepAlias = getDeepQuery(deep?.[fieldKey] || {})?.alias;
184
+ const deepAlias = getDeepQuery(deep?.[fieldKey] || {})?.['alias'];
185
185
  if (!(0, lodash_1.isEmpty)(deepAlias))
186
186
  query.alias = deepAlias;
187
187
  child = {
@@ -296,5 +296,5 @@ async function getASTFromQuery(collection, query, schema, options) {
296
296
  }
297
297
  exports.default = getASTFromQuery;
298
298
  function getDeepQuery(query) {
299
- return (0, lodash_1.mapKeys)((0, lodash_1.omitBy)(query, (value, key) => key.startsWith('_') === false), (value, key) => key.substring(1));
299
+ return (0, lodash_1.mapKeys)((0, lodash_1.omitBy)(query, (_value, key) => key.startsWith('_') === false), (_value, key) => key.substring(1));
300
300
  }
@@ -7,7 +7,7 @@ exports.getAuthProviders = void 0;
7
7
  const utils_1 = require("@directus/shared/utils");
8
8
  const env_1 = __importDefault(require("../env"));
9
9
  function getAuthProviders() {
10
- return (0, utils_1.toArray)(env_1.default.AUTH_PROVIDERS)
10
+ return (0, utils_1.toArray)(env_1.default['AUTH_PROVIDERS'])
11
11
  .filter((provider) => provider && env_1.default[`AUTH_${provider.toUpperCase()}_DRIVER`])
12
12
  .map((provider) => ({
13
13
  name: provider,
@@ -22,7 +22,7 @@ function getCacheControlHeader(req, ttl, globalCacheSettings, personalized) {
22
22
  if (ttl === undefined || ttl < 0)
23
23
  return 'no-cache';
24
24
  // When the API cache can invalidate at any moment
25
- if (globalCacheSettings && env_1.default.CACHE_AUTO_PURGE === true)
25
+ if (globalCacheSettings && env_1.default['CACHE_AUTO_PURGE'] === true)
26
26
  return 'no-cache';
27
27
  const headerValues = [];
28
28
  // When caching depends on the authentication status of the users
@@ -35,8 +35,8 @@ function getCacheControlHeader(req, ttl, globalCacheSettings, personalized) {
35
35
  const ttlSeconds = Math.round(ttl / 1000);
36
36
  headerValues.push(`max-age=${ttlSeconds}`);
37
37
  // When the s-maxage flag should be included
38
- if (globalCacheSettings && Number.isInteger(env_1.default.CACHE_CONTROL_S_MAXAGE) && env_1.default.CACHE_CONTROL_S_MAXAGE >= 0) {
39
- headerValues.push(`s-maxage=${env_1.default.CACHE_CONTROL_S_MAXAGE}`);
38
+ if (globalCacheSettings && Number.isInteger(env_1.default['CACHE_CONTROL_S_MAXAGE']) && env_1.default['CACHE_CONTROL_S_MAXAGE'] >= 0) {
39
+ headerValues.push(`s-maxage=${env_1.default['CACHE_CONTROL_S_MAXAGE']}`);
40
40
  }
41
41
  return headerValues.join(', ');
42
42
  }
@@ -11,5 +11,6 @@ function getCollectionFromAlias(alias, aliasMap) {
11
11
  return aliasValue.collection;
12
12
  }
13
13
  }
14
+ return undefined;
14
15
  }
15
16
  exports.getCollectionFromAlias = getCollectionFromAlias;
@@ -43,7 +43,8 @@ function getColumnPath({ path, collection, aliasMap, relations, schema }) {
43
43
  addNestedPkField = schema.collections[parent].primary;
44
44
  }
45
45
  // Nested level alias field
46
- else if (remainingParts.length === 1 && schema.collections[parent].fields[remainingParts[0]].type === 'alias') {
46
+ else if (remainingParts.length === 1 &&
47
+ schema.collections[parent].fields[remainingParts[0]].type === 'alias') {
47
48
  remainingParts.push(schema.collections[relation.related_collection].primary);
48
49
  addNestedPkField = schema.collections[relation.related_collection].primary;
49
50
  }
@@ -52,7 +52,7 @@ function castToObject(value) {
52
52
  return (0, utils_1.parseJSON)(value);
53
53
  }
54
54
  catch (err) {
55
- if (env_1.default.NODE_ENV === 'development') {
55
+ if (env_1.default['NODE_ENV'] === 'development') {
56
56
  logger_1.default.error(err);
57
57
  }
58
58
  return value;
@@ -9,8 +9,8 @@ const env_1 = __importDefault(require("../env"));
9
9
  const logger_1 = __importDefault(require("../logger"));
10
10
  function getIPFromReq(req) {
11
11
  let ip = req.ip;
12
- if (env_1.default.IP_CUSTOM_HEADER) {
13
- const customIPHeaderValue = req.get(env_1.default.IP_CUSTOM_HEADER);
12
+ if (env_1.default['IP_CUSTOM_HEADER']) {
13
+ const customIPHeaderValue = req.get(env_1.default['IP_CUSTOM_HEADER']);
14
14
  if (typeof customIPHeaderValue === 'string' && (0, net_1.isIP)(customIPHeaderValue) !== 0) {
15
15
  ip = customIPHeaderValue;
16
16
  }
@@ -22,7 +22,7 @@ async function getPermissions(accountability, schema) {
22
22
  let permissions = [];
23
23
  const { user, role, app, admin, share_scope } = accountability;
24
24
  const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin, share_scope })}`;
25
- if (cache && env_1.default.CACHE_PERMISSIONS !== false) {
25
+ if (cache && env_1.default['CACHE_PERMISSIONS'] !== false) {
26
26
  let cachedPermissions;
27
27
  try {
28
28
  cachedPermissions = await (0, cache_1.getSystemCache)(cacheKey);
@@ -31,20 +31,20 @@ async function getPermissions(accountability, schema) {
31
31
  logger_1.default.warn(err, `[cache] Couldn't read key ${cacheKey}. ${err.message}`);
32
32
  }
33
33
  if (cachedPermissions) {
34
- if (!cachedPermissions.containDynamicData) {
35
- return processPermissions(accountability, cachedPermissions.permissions, {});
34
+ if (!cachedPermissions['containDynamicData']) {
35
+ return processPermissions(accountability, cachedPermissions['permissions'], {});
36
36
  }
37
- const cachedFilterContext = await (0, cache_1.getCacheValue)(cache, `filterContext-${(0, object_hash_1.default)({ user, role, permissions: cachedPermissions.permissions })}`);
37
+ const cachedFilterContext = await (0, cache_1.getCacheValue)(cache, `filterContext-${(0, object_hash_1.default)({ user, role, permissions: cachedPermissions['permissions'] })}`);
38
38
  if (cachedFilterContext) {
39
- return processPermissions(accountability, cachedPermissions.permissions, cachedFilterContext);
39
+ return processPermissions(accountability, cachedPermissions['permissions'], cachedFilterContext);
40
40
  }
41
41
  else {
42
- const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(cachedPermissions.permissions);
42
+ const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(cachedPermissions['permissions']);
43
43
  permissions = parsedPermissions;
44
44
  const filterContext = containDynamicData
45
45
  ? await getFilterContext(schema, accountability, requiredPermissionData)
46
46
  : {};
47
- if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
47
+ if (containDynamicData && env_1.default['CACHE_ENABLED'] !== false) {
48
48
  await (0, cache_1.setCacheValue)(cache, `filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext);
49
49
  }
50
50
  return processPermissions(accountability, permissions, filterContext);
@@ -71,9 +71,9 @@ async function getPermissions(accountability, schema) {
71
71
  const filterContext = containDynamicData
72
72
  ? await getFilterContext(schema, accountability, requiredPermissionData)
73
73
  : {};
74
- if (cache && env_1.default.CACHE_PERMISSIONS !== false) {
74
+ if (cache && env_1.default['CACHE_PERMISSIONS'] !== false) {
75
75
  await (0, cache_1.setSystemCache)(cacheKey, { permissions, containDynamicData });
76
- if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
76
+ if (containDynamicData && env_1.default['CACHE_ENABLED'] !== false) {
77
77
  await (0, cache_1.setCacheValue)(cache, `filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext);
78
78
  }
79
79
  }
@@ -137,12 +137,12 @@ async function getFilterContext(schema, accountability, requiredPermissionData)
137
137
  const rolesService = new roles_1.RolesService({ schema });
138
138
  const filterContext = {};
139
139
  if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
140
- filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, {
140
+ filterContext['$CURRENT_USER'] = await usersService.readOne(accountability.user, {
141
141
  fields: requiredPermissionData.$CURRENT_USER,
142
142
  });
143
143
  }
144
144
  if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
145
- filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, {
145
+ filterContext['$CURRENT_ROLE'] = await rolesService.readOne(accountability.role, {
146
146
  fields: requiredPermissionData.$CURRENT_ROLE,
147
147
  });
148
148
  }
@@ -21,7 +21,7 @@ async function getSchema(options) {
21
21
  const database = options?.database || (0, database_1.default)();
22
22
  const schemaInspector = (0, schema_1.default)(database);
23
23
  let result;
24
- if (!options?.bypassCache && env_1.default.CACHE_SCHEMA !== false) {
24
+ if (!options?.bypassCache && env_1.default['CACHE_SCHEMA'] !== false) {
25
25
  let cachedSchema;
26
26
  try {
27
27
  cachedSchema = await (0, cache_1.getSchemaCache)();
@@ -61,7 +61,7 @@ async function getDatabaseSchema(database, schemaInspector) {
61
61
  ...collections_1.systemCollectionRows,
62
62
  ];
63
63
  for (const [collection, info] of Object.entries(schemaOverview)) {
64
- if ((0, utils_1.toArray)(env_1.default.DB_EXCLUDE_TABLES).includes(collection)) {
64
+ if ((0, utils_1.toArray)(env_1.default['DB_EXCLUDE_TABLES']).includes(collection)) {
65
65
  logger_1.default.trace(`Collection "${collection}" is configured to be excluded and will be ignored`);
66
66
  continue;
67
67
  }
@@ -81,7 +81,7 @@ async function getDatabaseSchema(database, schemaInspector) {
81
81
  note: collectionMeta?.note || null,
82
82
  sortField: collectionMeta?.sort_field || null,
83
83
  accountability: collectionMeta ? collectionMeta.accountability : 'all',
84
- fields: (0, lodash_1.mapValues)(schemaOverview[collection].columns, (column) => {
84
+ fields: (0, lodash_1.mapValues)(schemaOverview[collection]?.columns, (column) => {
85
85
  return {
86
86
  field: column.column_name,
87
87
  defaultValue: (0, get_default_value_1.default)(column) ?? null,
@@ -108,8 +108,8 @@ async function getDatabaseSchema(database, schemaInspector) {
108
108
  for (const field of fields) {
109
109
  if (!result.collections[field.collection])
110
110
  continue;
111
- const existing = result.collections[field.collection].fields[field.field];
112
- const column = schemaOverview[field.collection].columns[field.field];
111
+ const existing = result.collections[field.collection]?.fields[field.field];
112
+ const column = schemaOverview[field.collection]?.columns[field.field];
113
113
  const special = field.special ? (0, utils_1.toArray)(field.special) : [];
114
114
  if (constants_1.ALIAS_TYPES.some((type) => special.includes(type)) === false && !existing)
115
115
  continue;
@@ -73,7 +73,7 @@ function getSnapshotDiff(current, after) {
73
73
  * When you delete a collection, we don't have to individually drop all the fields/relations as well
74
74
  */
75
75
  const deletedCollections = diffedSnapshot.collections
76
- .filter((collection) => collection.diff?.[0].kind === types_1.DiffKind.DELETE)
76
+ .filter((collection) => collection.diff?.[0]?.kind === types_1.DiffKind.DELETE)
77
77
  .map(({ collection }) => collection);
78
78
  diffedSnapshot.fields = diffedSnapshot.fields.filter((field) => deletedCollections.includes(field.collection) === false);
79
79
  diffedSnapshot.relations = diffedSnapshot.relations.filter((relation) => deletedCollections.includes(relation.collection) === false);
@@ -13,7 +13,8 @@ function isUrlAllowed(url, allowList) {
13
13
  const urlAllowList = (0, utils_1.toArray)(allowList);
14
14
  if (urlAllowList.includes(url))
15
15
  return true;
16
- const parsedWhitelist = urlAllowList.map((allowedURL) => {
16
+ const parsedWhitelist = urlAllowList
17
+ .map((allowedURL) => {
17
18
  try {
18
19
  const { hostname, pathname } = new url_1.URL(allowedURL);
19
20
  return hostname + pathname;
@@ -21,7 +22,9 @@ function isUrlAllowed(url, allowList) {
21
22
  catch {
22
23
  logger_1.default.warn(`Invalid URL used "${url}"`);
23
24
  }
24
- });
25
+ return null;
26
+ })
27
+ .filter((f) => f);
25
28
  try {
26
29
  const { hostname, pathname } = new url_1.URL(url);
27
30
  return parsedWhitelist.includes(hostname + pathname);
@@ -53,8 +53,8 @@ function parseXmp(buffer) {
53
53
  if (!tagMatches || tagMatches.length === 0) {
54
54
  return;
55
55
  }
56
- const value = tagMatches[1].trim();
57
- if (value.toLowerCase().indexOf('<rdf:bag>') === 0) {
56
+ const value = tagMatches[1]?.trim();
57
+ if (value?.toLowerCase().indexOf('<rdf:bag>') === 0) {
58
58
  const r = new RegExp('<rdf:li>(.*?)</rdf:li>', 'smig');
59
59
  let match = r.exec(value);
60
60
  const result = [];
@@ -65,7 +65,7 @@ function parseXmp(buffer) {
65
65
  xmp[x] = result;
66
66
  }
67
67
  else {
68
- xmp[x] = value.replace(/<[^>]*>?/gm, '').trim();
68
+ xmp[x] = value?.replace(/<[^>]*>?/gm, '').trim();
69
69
  }
70
70
  });
71
71
  return xmp;
@@ -56,7 +56,7 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
56
56
  if (relation.related_collection &&
57
57
  (Object.keys(allowedFieldsInCollection).includes(relation.related_collection) === false ||
58
58
  // Ignore legacy permissions with an empty fields array
59
- allowedFieldsInCollection[relation.related_collection].length === 0)) {
59
+ allowedFieldsInCollection[relation.related_collection]?.length === 0)) {
60
60
  collectionsAllowed = false;
61
61
  }
62
62
  if (relation.meta?.one_allowed_collections &&
@@ -64,15 +64,15 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
64
64
  collectionsAllowed = false;
65
65
  }
66
66
  if (!allowedFieldsInCollection[relation.collection] ||
67
- (allowedFieldsInCollection[relation.collection].includes('*') === false &&
68
- allowedFieldsInCollection[relation.collection].includes(relation.field) === false)) {
67
+ (allowedFieldsInCollection[relation.collection]?.includes('*') === false &&
68
+ allowedFieldsInCollection[relation.collection]?.includes(relation.field) === false)) {
69
69
  fieldsAllowed = false;
70
70
  }
71
71
  if (relation.related_collection &&
72
72
  relation.meta?.one_field &&
73
73
  (!allowedFieldsInCollection[relation.related_collection] ||
74
- (allowedFieldsInCollection[relation.related_collection].includes('*') === false &&
75
- allowedFieldsInCollection[relation.related_collection].includes(relation.meta?.one_field) === false))) {
74
+ (allowedFieldsInCollection[relation.related_collection]?.includes('*') === false &&
75
+ allowedFieldsInCollection[relation.related_collection]?.includes(relation.meta?.one_field) === false))) {
76
76
  fieldsAllowed = false;
77
77
  }
78
78
  return collectionsAllowed && fieldsAllowed;