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.
- package/dist/app.js +15 -15
- package/dist/auth/drivers/ldap.js +22 -22
- package/dist/auth/drivers/local.js +7 -7
- package/dist/auth/drivers/oauth2.js +27 -25
- package/dist/auth/drivers/openid.js +32 -30
- package/dist/auth/drivers/saml.js +10 -10
- package/dist/auth.js +4 -3
- package/dist/cache.js +16 -11
- package/dist/cli/commands/bootstrap/index.js +5 -4
- package/dist/cli/utils/create-db-connection.js +1 -1
- package/dist/cli/utils/create-env/index.js +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +6 -5
- package/dist/controllers/activity.js +10 -10
- package/dist/controllers/assets.js +19 -18
- package/dist/controllers/auth.js +16 -16
- package/dist/controllers/collections.js +11 -11
- package/dist/controllers/dashboards.js +11 -11
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +17 -17
- package/dist/controllers/files.js +18 -17
- package/dist/controllers/flows.js +13 -13
- package/dist/controllers/folders.js +11 -11
- package/dist/controllers/graphql.js +6 -6
- package/dist/controllers/items.js +19 -19
- package/dist/controllers/notifications.js +11 -11
- package/dist/controllers/operations.js +11 -11
- package/dist/controllers/panels.js +11 -11
- package/dist/controllers/permissions.js +11 -11
- package/dist/controllers/presets.js +11 -11
- package/dist/controllers/relations.js +11 -11
- package/dist/controllers/revisions.js +3 -3
- package/dist/controllers/roles.js +11 -11
- package/dist/controllers/schema.js +5 -5
- package/dist/controllers/server.js +7 -7
- package/dist/controllers/settings.js +2 -2
- package/dist/controllers/shares.js +14 -14
- package/dist/controllers/users.js +16 -16
- package/dist/controllers/utils.js +7 -7
- package/dist/controllers/webhooks.js +11 -11
- package/dist/database/helpers/fn/types.d.ts +0 -1
- package/dist/database/helpers/fn/types.js +0 -2
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/index.js +5 -5
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +15 -15
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.js +6 -6
- package/dist/database/system-data/collections/index.js +2 -2
- package/dist/database/system-data/fields/index.js +3 -3
- package/dist/env.js +1 -1
- package/dist/exceptions/database/dialects/mssql.js +2 -2
- package/dist/exceptions/database/dialects/mysql.js +6 -6
- package/dist/exceptions/database/record-not-unique.d.ts +1 -1
- package/dist/extensions.js +10 -10
- package/dist/flows.js +33 -31
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +32 -32
- package/dist/mailer.js +16 -16
- package/dist/messenger.js +4 -4
- package/dist/middleware/authenticate.d.ts +1 -1
- package/dist/middleware/authenticate.js +2 -2
- package/dist/middleware/cache.js +11 -11
- package/dist/middleware/collection-exists.js +4 -4
- package/dist/middleware/cors.js +8 -8
- package/dist/middleware/error-handler.js +2 -2
- package/dist/middleware/extract-token.js +3 -3
- package/dist/middleware/get-permissions.js +1 -1
- package/dist/middleware/graphql.js +12 -6
- package/dist/middleware/rate-limiter-global.js +5 -5
- package/dist/middleware/rate-limiter-ip.js +2 -2
- package/dist/middleware/respond.js +16 -16
- package/dist/middleware/sanitize-query.js +1 -1
- package/dist/middleware/schema.js +1 -1
- package/dist/middleware/use-collection.js +1 -1
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/exec/index.js +2 -2
- package/dist/rate-limiter.js +1 -1
- package/dist/request/validate-ip.js +2 -2
- package/dist/server.js +4 -4
- package/dist/services/activity.js +14 -14
- package/dist/services/assets.js +6 -6
- package/dist/services/authentication.js +9 -9
- package/dist/services/collections.js +9 -9
- package/dist/services/fields.js +5 -5
- package/dist/services/files.js +18 -18
- package/dist/services/graphql/index.js +170 -116
- package/dist/services/import-export.js +6 -6
- package/dist/services/items.js +6 -6
- package/dist/services/mail/index.js +5 -5
- package/dist/services/meta.js +1 -0
- package/dist/services/notifications.js +4 -4
- package/dist/services/relations.js +4 -4
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +5 -5
- package/dist/services/server.js +27 -27
- package/dist/services/shares.js +9 -9
- package/dist/services/specifications.js +5 -3
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +24 -27
- package/dist/storage/register-locations.js +1 -1
- package/dist/utils/apply-diff.js +12 -12
- package/dist/utils/apply-query.js +3 -2
- package/dist/utils/dynamic-import.js +1 -1
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-ast-from-query.js +2 -2
- package/dist/utils/get-auth-providers.js +1 -1
- package/dist/utils/get-cache-headers.js +3 -3
- package/dist/utils/get-collection-from-alias.js +1 -0
- package/dist/utils/get-column-path.js +2 -1
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +2 -2
- package/dist/utils/get-permissions.js +11 -11
- package/dist/utils/get-schema.js +5 -5
- package/dist/utils/get-snapshot-diff.js +1 -1
- package/dist/utils/is-url-allowed.js +5 -2
- package/dist/utils/parse-image-metadata.js +3 -3
- package/dist/utils/reduce-schema.js +5 -5
- package/dist/utils/sanitize-query.js +26 -26
- package/dist/utils/should-skip-cache.js +13 -4
- package/dist/utils/strip-function.js +1 -1
- package/dist/utils/telemetry.d.ts +1 -0
- package/dist/utils/telemetry.js +30 -0
- package/dist/utils/validate-keys.js +1 -1
- package/dist/utils/validate-query.js +1 -1
- package/dist/utils/validate-storage.js +8 -8
- package/dist/webhooks.js +2 -2
- package/package.json +13 -13
- package/dist/utils/redact-header-cookies.d.ts +0 -1
- package/dist/utils/redact-header-cookies.js +0 -11
- package/dist/utils/track.d.ts +0 -1
- package/dist/utils/track.js +0 -81
- /package/dist/{utils/redact-header-cookies.test.d.ts → logger.test.d.ts} +0 -0
package/dist/services/users.js
CHANGED
|
@@ -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
|
|
135
|
-
const passwords = data
|
|
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
|
|
186
|
-
// data
|
|
187
|
-
const roleId = data
|
|
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
|
|
190
|
+
if (data['status'] !== undefined && data['status'] !== 'active') {
|
|
194
191
|
await this.checkRemainingActiveAdmin(keys);
|
|
195
192
|
}
|
|
196
|
-
if (data
|
|
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
|
|
198
|
+
invalid: data['email'],
|
|
202
199
|
});
|
|
203
200
|
}
|
|
204
|
-
await this.checkUniqueEmails([data
|
|
201
|
+
await this.checkUniqueEmails([data['email']], keys[0]);
|
|
205
202
|
}
|
|
206
|
-
if (data
|
|
207
|
-
await this.checkPasswordPolicy([data
|
|
203
|
+
if (data['password']) {
|
|
204
|
+
await this.checkPasswordPolicy([data['password']]);
|
|
208
205
|
}
|
|
209
|
-
if (data
|
|
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
|
|
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
|
|
213
|
+
data['auth_data'] = null;
|
|
217
214
|
}
|
|
218
|
-
if (data
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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()}_`);
|
package/dist/utils/apply-diff.js
CHANGED
|
@@ -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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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]
|
|
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).
|
|
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
|
|
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
|
|
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, (
|
|
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
|
|
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
|
|
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
|
|
39
|
-
headerValues.push(`s-maxage=${env_1.default
|
|
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
|
}
|
|
@@ -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 &&
|
|
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
|
}
|
|
@@ -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
|
|
13
|
-
const customIPHeaderValue = req.get(env_1.default
|
|
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
|
|
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
|
|
35
|
-
return processPermissions(accountability, cachedPermissions
|
|
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
|
|
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
|
|
39
|
+
return processPermissions(accountability, cachedPermissions['permissions'], cachedFilterContext);
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(cachedPermissions
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
+
filterContext['$CURRENT_ROLE'] = await rolesService.readOne(accountability.role, {
|
|
146
146
|
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
147
147
|
});
|
|
148
148
|
}
|
package/dist/utils/get-schema.js
CHANGED
|
@@ -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
|
|
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
|
|
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]
|
|
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]
|
|
112
|
-
const column = schemaOverview[field.collection]
|
|
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]
|
|
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
|
|
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]
|
|
57
|
-
if (value
|
|
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
|
|
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]
|
|
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]
|
|
68
|
-
allowedFieldsInCollection[relation.collection]
|
|
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]
|
|
75
|
-
allowedFieldsInCollection[relation.related_collection]
|
|
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;
|