@directus/api 11.0.1 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +3 -4
- package/dist/auth/auth.d.ts +4 -4
- package/dist/auth/auth.js +2 -2
- package/dist/auth/drivers/ldap.js +20 -17
- package/dist/auth/drivers/local.js +5 -5
- package/dist/auth/drivers/oauth2.js +16 -16
- package/dist/auth/drivers/openid.js +18 -17
- package/dist/auth/drivers/saml.js +6 -7
- package/dist/auth.js +3 -2
- package/dist/cache.js +3 -13
- package/dist/cli/utils/create-env/env-stub.liquid +5 -7
- package/dist/controllers/activity.js +7 -6
- package/dist/controllers/assets.js +25 -12
- package/dist/controllers/auth.js +8 -7
- package/dist/controllers/collections.js +4 -3
- package/dist/controllers/dashboards.js +5 -4
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +9 -8
- package/dist/controllers/files.js +11 -11
- package/dist/controllers/flows.js +5 -4
- package/dist/controllers/folders.js +5 -4
- package/dist/controllers/items.js +14 -13
- package/dist/controllers/not-found.js +2 -2
- package/dist/controllers/notifications.js +5 -4
- package/dist/controllers/operations.js +5 -4
- package/dist/controllers/panels.js +5 -4
- package/dist/controllers/permissions.js +5 -4
- package/dist/controllers/presets.js +5 -4
- package/dist/controllers/relations.js +6 -5
- package/dist/controllers/roles.js +5 -4
- package/dist/controllers/schema.js +8 -8
- package/dist/controllers/server.js +2 -2
- package/dist/controllers/settings.js +3 -2
- package/dist/controllers/shares.js +7 -6
- package/dist/controllers/translations.js +6 -5
- package/dist/controllers/users.js +22 -21
- package/dist/controllers/utils.js +10 -10
- package/dist/controllers/webhooks.js +5 -4
- package/dist/{exceptions/database → database/errors}/dialects/mssql.js +8 -18
- package/dist/{exceptions/database → database/errors}/dialects/mysql.js +9 -19
- package/dist/{exceptions/database → database/errors}/dialects/oracle.js +2 -2
- package/dist/{exceptions/database → database/errors}/dialects/postgres.js +7 -18
- package/dist/{exceptions/database → database/errors}/dialects/sqlite.js +7 -10
- package/dist/{exceptions/database → database/errors}/translate.js +1 -1
- package/dist/database/migrations/run.js +10 -1
- package/dist/emitter.d.ts +3 -2
- package/dist/emitter.js +12 -4
- package/dist/env.js +21 -17
- package/dist/errors/codes.d.ts +29 -0
- package/dist/errors/codes.js +30 -0
- package/dist/errors/contains-null-values.d.ts +7 -0
- package/dist/errors/contains-null-values.js +4 -0
- package/dist/errors/content-too-large.d.ts +1 -0
- package/dist/errors/content-too-large.js +3 -0
- package/dist/errors/forbidden.d.ts +1 -0
- package/dist/errors/forbidden.js +3 -0
- package/dist/errors/hit-rate-limit.d.ts +6 -0
- package/dist/errors/hit-rate-limit.js +8 -0
- package/dist/errors/illegal-asset-transformation.d.ts +4 -0
- package/dist/errors/illegal-asset-transformation.js +3 -0
- package/dist/errors/index.d.ts +28 -0
- package/dist/errors/index.js +28 -0
- package/dist/errors/invalid-credentials.d.ts +1 -0
- package/dist/errors/invalid-credentials.js +3 -0
- package/dist/errors/invalid-foreign-key.d.ts +6 -0
- package/dist/errors/invalid-foreign-key.js +14 -0
- package/dist/errors/invalid-ip.d.ts +1 -0
- package/dist/errors/invalid-ip.js +3 -0
- package/dist/errors/invalid-otp.d.ts +1 -0
- package/dist/errors/invalid-otp.js +3 -0
- package/dist/errors/invalid-payload.d.ts +5 -0
- package/dist/errors/invalid-payload.js +4 -0
- package/dist/errors/invalid-provider-config.d.ts +5 -0
- package/dist/errors/invalid-provider-config.js +3 -0
- package/dist/errors/invalid-provider.d.ts +1 -0
- package/dist/errors/invalid-provider.js +3 -0
- package/dist/errors/invalid-query.d.ts +5 -0
- package/dist/errors/invalid-query.js +4 -0
- package/dist/errors/invalid-token.d.ts +1 -0
- package/dist/errors/invalid-token.js +3 -0
- package/dist/errors/method-not-allowed.d.ts +6 -0
- package/dist/errors/method-not-allowed.js +6 -0
- package/dist/errors/not-null-violation.d.ts +6 -0
- package/dist/errors/not-null-violation.js +14 -0
- package/dist/errors/range-not-satisfiable.d.ts +7 -0
- package/dist/errors/range-not-satisfiable.js +7 -0
- package/dist/errors/record-not-unique.d.ts +6 -0
- package/dist/errors/record-not-unique.js +14 -0
- package/dist/errors/route-not-found.d.ts +5 -0
- package/dist/errors/route-not-found.js +4 -0
- package/dist/errors/service-unavailable.d.ts +7 -0
- package/dist/errors/service-unavailable.js +4 -0
- package/dist/errors/token-expired.d.ts +1 -0
- package/dist/errors/token-expired.js +3 -0
- package/dist/errors/unexpected-response.d.ts +1 -0
- package/dist/errors/unexpected-response.js +3 -0
- package/dist/errors/unprocessable-content.d.ts +5 -0
- package/dist/errors/unprocessable-content.js +4 -0
- package/dist/errors/unsupported-media-type.d.ts +6 -0
- package/dist/errors/unsupported-media-type.js +4 -0
- package/dist/errors/user-suspended.d.ts +1 -0
- package/dist/errors/user-suspended.js +3 -0
- package/dist/errors/value-out-of-range.d.ts +6 -0
- package/dist/errors/value-out-of-range.js +14 -0
- package/dist/errors/value-too-long.d.ts +6 -0
- package/dist/errors/value-too-long.js +14 -0
- package/dist/extensions.js +0 -4
- package/dist/flows.js +6 -8
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/messenger.d.ts +3 -3
- package/dist/messenger.js +18 -9
- package/dist/middleware/authenticate.js +2 -38
- package/dist/middleware/check-ip.js +2 -2
- package/dist/middleware/collection-exists.js +2 -2
- package/dist/middleware/error-handler.js +7 -7
- package/dist/middleware/graphql.js +11 -9
- package/dist/middleware/rate-limiter-global.d.ts +2 -2
- package/dist/middleware/rate-limiter-global.js +2 -3
- package/dist/middleware/rate-limiter-ip.d.ts +2 -2
- package/dist/middleware/rate-limiter-ip.js +2 -3
- package/dist/middleware/validate-batch.js +3 -4
- package/dist/rate-limiter.js +2 -9
- package/dist/server.js +10 -0
- package/dist/services/activity.js +3 -2
- package/dist/services/assets.js +9 -10
- package/dist/services/authentication.js +12 -11
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +16 -16
- package/dist/services/collections.js +17 -16
- package/dist/services/fields.js +16 -14
- package/dist/services/files.js +7 -6
- package/dist/services/graphql/errors/execution.d.ts +6 -0
- package/dist/services/graphql/errors/execution.js +2 -0
- package/dist/services/graphql/errors/index.d.ts +2 -0
- package/dist/services/graphql/errors/index.js +2 -0
- package/dist/services/graphql/errors/validation.d.ts +6 -0
- package/dist/services/graphql/errors/validation.js +2 -0
- package/dist/services/graphql/index.d.ts +2 -8
- package/dist/services/graphql/index.js +125 -66
- package/dist/services/graphql/subscription.d.ts +16 -0
- package/dist/services/graphql/subscription.js +77 -0
- package/dist/services/graphql/utils/process-error.js +3 -3
- package/dist/services/import-export.js +7 -7
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +14 -13
- package/dist/services/mail/index.js +3 -3
- package/dist/services/meta.js +3 -3
- package/dist/services/payload.js +11 -7
- package/dist/services/relations.js +32 -22
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +10 -9
- package/dist/services/schema.js +5 -5
- package/dist/services/server.js +24 -0
- package/dist/services/shares.js +4 -4
- package/dist/services/tfa.js +6 -6
- package/dist/services/translations.d.ts +2 -2
- package/dist/services/translations.js +4 -4
- package/dist/services/users.js +26 -29
- package/dist/services/utils.js +4 -4
- package/dist/services/websocket.d.ts +14 -0
- package/dist/services/websocket.js +26 -0
- package/dist/synchronization.js +3 -3
- package/dist/types/items.d.ts +2 -2
- package/dist/utils/apply-diff.js +13 -4
- package/dist/utils/apply-query.js +22 -13
- package/dist/utils/get-accountability-for-role.js +1 -2
- package/dist/utils/get-accountability-for-token.d.ts +2 -0
- package/dist/utils/get-accountability-for-token.js +50 -0
- package/dist/utils/get-column-path.js +5 -3
- package/dist/utils/get-column.js +3 -3
- package/dist/utils/get-service.d.ts +7 -0
- package/dist/utils/get-service.js +49 -0
- package/dist/utils/jwt.js +5 -5
- package/dist/utils/redact.d.ts +4 -0
- package/dist/utils/redact.js +15 -1
- package/dist/utils/to-boolean.d.ts +4 -0
- package/dist/utils/to-boolean.js +6 -0
- package/dist/utils/validate-diff.js +23 -9
- package/dist/utils/validate-keys.js +3 -3
- package/dist/utils/validate-query.d.ts +2 -0
- package/dist/utils/validate-query.js +27 -21
- package/dist/utils/validate-snapshot.js +11 -5
- package/dist/websocket/authenticate.d.ts +6 -0
- package/dist/websocket/authenticate.js +59 -0
- package/dist/websocket/controllers/base.d.ts +42 -0
- package/dist/websocket/controllers/base.js +279 -0
- package/dist/websocket/controllers/graphql.d.ts +12 -0
- package/dist/websocket/controllers/graphql.js +102 -0
- package/dist/websocket/controllers/hooks.d.ts +1 -0
- package/dist/websocket/controllers/hooks.js +122 -0
- package/dist/websocket/controllers/index.d.ts +10 -0
- package/dist/websocket/controllers/index.js +31 -0
- package/dist/websocket/controllers/rest.d.ts +9 -0
- package/dist/websocket/controllers/rest.js +47 -0
- package/dist/websocket/errors.d.ts +16 -0
- package/dist/websocket/errors.js +55 -0
- package/dist/websocket/handlers/heartbeat.d.ts +11 -0
- package/dist/websocket/handlers/heartbeat.js +72 -0
- package/dist/websocket/handlers/index.d.ts +4 -0
- package/dist/websocket/handlers/index.js +11 -0
- package/dist/websocket/handlers/items.d.ts +6 -0
- package/dist/websocket/handlers/items.js +103 -0
- package/dist/websocket/handlers/subscribe.d.ts +43 -0
- package/dist/websocket/handlers/subscribe.js +278 -0
- package/dist/websocket/messages.d.ts +311 -0
- package/dist/websocket/messages.js +96 -0
- package/dist/websocket/types.d.ts +34 -0
- package/dist/websocket/types.js +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.d.ts +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.js +8 -0
- package/dist/websocket/utils/message.d.ts +4 -0
- package/dist/websocket/utils/message.js +27 -0
- package/dist/websocket/utils/wait-for-message.d.ts +4 -0
- package/dist/websocket/utils/wait-for-message.js +45 -0
- package/package.json +21 -16
- package/dist/exceptions/content-too-large.d.ts +0 -4
- package/dist/exceptions/content-too-large.js +0 -6
- package/dist/exceptions/database/contains-null-values.d.ts +0 -9
- package/dist/exceptions/database/contains-null-values.js +0 -6
- package/dist/exceptions/database/invalid-foreign-key.d.ts +0 -10
- package/dist/exceptions/database/invalid-foreign-key.js +0 -11
- package/dist/exceptions/database/not-null-violation.d.ts +0 -9
- package/dist/exceptions/database/not-null-violation.js +0 -6
- package/dist/exceptions/database/record-not-unique.d.ts +0 -10
- package/dist/exceptions/database/record-not-unique.js +0 -11
- package/dist/exceptions/database/value-out-of-range.d.ts +0 -10
- package/dist/exceptions/database/value-out-of-range.js +0 -11
- package/dist/exceptions/database/value-too-long.d.ts +0 -9
- package/dist/exceptions/database/value-too-long.js +0 -11
- package/dist/exceptions/forbidden.d.ts +0 -6
- package/dist/exceptions/forbidden.js +0 -13
- package/dist/exceptions/graphql-validation.d.ts +0 -4
- package/dist/exceptions/graphql-validation.js +0 -6
- package/dist/exceptions/hit-rate-limit.d.ts +0 -9
- package/dist/exceptions/hit-rate-limit.js +0 -6
- package/dist/exceptions/illegal-asset-transformation.d.ts +0 -4
- package/dist/exceptions/illegal-asset-transformation.js +0 -6
- package/dist/exceptions/index.d.ts +0 -21
- package/dist/exceptions/index.js +0 -21
- package/dist/exceptions/invalid-config.d.ts +0 -4
- package/dist/exceptions/invalid-config.js +0 -6
- package/dist/exceptions/invalid-credentials.d.ts +0 -4
- package/dist/exceptions/invalid-credentials.js +0 -6
- package/dist/exceptions/invalid-ip.d.ts +0 -4
- package/dist/exceptions/invalid-ip.js +0 -6
- package/dist/exceptions/invalid-otp.d.ts +0 -4
- package/dist/exceptions/invalid-otp.js +0 -6
- package/dist/exceptions/invalid-payload.d.ts +0 -4
- package/dist/exceptions/invalid-payload.js +0 -6
- package/dist/exceptions/invalid-provider.d.ts +0 -4
- package/dist/exceptions/invalid-provider.js +0 -6
- package/dist/exceptions/invalid-query.d.ts +0 -4
- package/dist/exceptions/invalid-query.js +0 -6
- package/dist/exceptions/invalid-token.d.ts +0 -4
- package/dist/exceptions/invalid-token.js +0 -6
- package/dist/exceptions/method-not-allowed.d.ts +0 -8
- package/dist/exceptions/method-not-allowed.js +0 -6
- package/dist/exceptions/range-not-satisfiable.d.ts +0 -5
- package/dist/exceptions/range-not-satisfiable.js +0 -9
- package/dist/exceptions/route-not-found.d.ts +0 -4
- package/dist/exceptions/route-not-found.js +0 -6
- package/dist/exceptions/service-unavailable.d.ts +0 -9
- package/dist/exceptions/service-unavailable.js +0 -6
- package/dist/exceptions/token-expired.d.ts +0 -4
- package/dist/exceptions/token-expired.js +0 -6
- package/dist/exceptions/unexpected-response.d.ts +0 -4
- package/dist/exceptions/unexpected-response.js +0 -6
- package/dist/exceptions/unprocessable-entity.d.ts +0 -4
- package/dist/exceptions/unprocessable-entity.js +0 -6
- package/dist/exceptions/unsupported-media-type.d.ts +0 -4
- package/dist/exceptions/unsupported-media-type.js +0 -6
- package/dist/exceptions/user-suspended.d.ts +0 -4
- package/dist/exceptions/user-suspended.js +0 -6
- /package/dist/{exceptions/database → database/errors}/dialects/mssql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/mysql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/oracle.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/postgres.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/sqlite.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.js +0 -0
- /package/dist/{exceptions/database → database/errors}/translate.d.ts +0 -0
package/dist/services/users.js
CHANGED
|
@@ -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 {
|
|
9
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = {})).
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = {})).
|
|
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 = {})).
|
|
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
|
|
281
|
+
throw new InvalidPayloadError({ reason: `Url "${url}" can't be used to invite users` });
|
|
285
282
|
}
|
|
286
283
|
}
|
|
287
284
|
catch (err) {
|
|
288
|
-
opts.
|
|
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
|
|
323
|
+
throw new ForbiddenError();
|
|
327
324
|
const user = await this.getUserByEmail(email);
|
|
328
325
|
if (user?.status !== 'invited') {
|
|
329
|
-
throw new
|
|
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
|
|
341
|
+
throw new ForbiddenError();
|
|
345
342
|
}
|
|
346
343
|
if (url && isUrlAllowed(url, env['PASSWORD_RESET_URL_ALLOW_LIST']) === false) {
|
|
347
|
-
throw new
|
|
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
|
|
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.
|
|
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
|
|
383
|
+
throw new ForbiddenError();
|
|
387
384
|
}
|
|
388
385
|
// Allow unauthenticated update
|
|
389
386
|
const service = new UsersService({
|
package/dist/services/utils.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
26
|
+
throw new ForbiddenError();
|
|
27
27
|
}
|
|
28
28
|
const allowedFields = permissions.fields ?? [];
|
|
29
29
|
if (allowedFields[0] !== '*' && allowedFields.includes(sortField) === false) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new ForbiddenError();
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const primaryKeyField = this.schema.collections[collection].primary;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ActionHandler } from '@directus/types';
|
|
2
|
+
import type { WebSocketClient } from '../websocket/types.js';
|
|
3
|
+
import type { WebSocketMessage } from '../websocket/messages.js';
|
|
4
|
+
export declare class WebSocketService {
|
|
5
|
+
private controller;
|
|
6
|
+
constructor();
|
|
7
|
+
on(event: 'connect' | 'message' | 'error' | 'close', callback: ActionHandler): void;
|
|
8
|
+
off(event: 'connect' | 'message' | 'error' | 'close', callback: ActionHandler): void;
|
|
9
|
+
broadcast(message: string | WebSocketMessage, filter?: {
|
|
10
|
+
user?: string;
|
|
11
|
+
role?: string;
|
|
12
|
+
}): void;
|
|
13
|
+
clients(): Set<WebSocketClient>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getWebSocketController } from '../websocket/controllers/index.js';
|
|
2
|
+
import emitter from '../emitter.js';
|
|
3
|
+
export class WebSocketService {
|
|
4
|
+
controller;
|
|
5
|
+
constructor() {
|
|
6
|
+
this.controller = getWebSocketController();
|
|
7
|
+
}
|
|
8
|
+
on(event, callback) {
|
|
9
|
+
emitter.onAction('websocket.' + event, callback);
|
|
10
|
+
}
|
|
11
|
+
off(event, callback) {
|
|
12
|
+
emitter.offAction('websocket.' + event, callback);
|
|
13
|
+
}
|
|
14
|
+
broadcast(message, filter) {
|
|
15
|
+
this.controller.clients.forEach((client) => {
|
|
16
|
+
if (filter && filter.user && filter.user !== client.accountability?.user)
|
|
17
|
+
return;
|
|
18
|
+
if (filter && filter.role && filter.role !== client.accountability?.role)
|
|
19
|
+
return;
|
|
20
|
+
client.send(typeof message === 'string' ? message : JSON.stringify(message));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
clients() {
|
|
24
|
+
return this.controller.clients;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/synchronization.js
CHANGED
|
@@ -73,9 +73,9 @@ class SynchronizationManagerRedis {
|
|
|
73
73
|
namespace;
|
|
74
74
|
client;
|
|
75
75
|
constructor() {
|
|
76
|
-
const config = getConfigFromEnv('
|
|
77
|
-
this.client = new Redis(env['
|
|
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,
|
package/dist/types/items.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
49
|
+
preMutationError?: DirectusError | undefined;
|
|
50
50
|
};
|
|
51
51
|
export type ActionEventParams = {
|
|
52
52
|
event: string | string[];
|
package/dist/utils/apply-diff.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import deepDiff from 'deep-diff';
|
|
2
2
|
import { cloneDeep, merge, set } from 'lodash-es';
|
|
3
|
-
import {
|
|
3
|
+
import { flushCaches } from '../cache.js';
|
|
4
4
|
import { getHelpers } from '../database/helpers/index.js';
|
|
5
5
|
import getDatabase from '../database/index.js';
|
|
6
6
|
import emitter from '../emitter.js';
|
|
@@ -121,7 +121,12 @@ export 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 }) =>
|
|
124
|
+
await deleteCollections(snapshotDiff.collections.filter(({ diff }) => {
|
|
125
|
+
if (diff.length === 0 || diff[0] === undefined)
|
|
126
|
+
return false;
|
|
127
|
+
const collectionDiff = diff[0];
|
|
128
|
+
return collectionDiff.kind === DiffKind.DELETE && collectionDiff.lhs?.meta?.group === null;
|
|
129
|
+
}));
|
|
125
130
|
for (const { collection, diff } of snapshotDiff.collections) {
|
|
126
131
|
if (diff?.[0]?.kind === DiffKind.EDIT || diff?.[0]?.kind === DiffKind.ARRAY) {
|
|
127
132
|
const currentCollection = currentSnapshot.collections.find((field) => {
|
|
@@ -198,7 +203,11 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
|
|
|
198
203
|
}
|
|
199
204
|
if (diff?.[0]?.kind === DiffKind.NEW) {
|
|
200
205
|
try {
|
|
201
|
-
await relationsService.createOne(
|
|
206
|
+
await relationsService.createOne({
|
|
207
|
+
...diff[0].rhs,
|
|
208
|
+
collection,
|
|
209
|
+
field,
|
|
210
|
+
}, mutationOptions);
|
|
202
211
|
}
|
|
203
212
|
catch (err) {
|
|
204
213
|
logger.error(`Failed to create relation "${collection}.${field}"`);
|
|
@@ -237,7 +246,7 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
|
|
|
237
246
|
if (runPostColumnChange) {
|
|
238
247
|
await helpers.schema.postColumnChange();
|
|
239
248
|
}
|
|
240
|
-
await
|
|
249
|
+
await flushCaches();
|
|
241
250
|
if (nestedActionEvents.length > 0) {
|
|
242
251
|
const updatedSchema = await getSchema({ database, bypassCache: true });
|
|
243
252
|
for (const nestedActionEvent of nestedActionEvents) {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { getFilterOperatorsForType, getOutputTypeForFunction } from '@directus/utils';
|
|
2
2
|
import { clone, isPlainObject } from 'lodash-es';
|
|
3
|
+
import { customAlphabet } from 'nanoid/non-secure';
|
|
3
4
|
import validate from 'uuid-validate';
|
|
4
5
|
import { getHelpers } from '../database/helpers/index.js';
|
|
5
|
-
import {
|
|
6
|
+
import { InvalidQueryError } from '../errors/index.js';
|
|
6
7
|
import { getColumnPath } from './get-column-path.js';
|
|
7
8
|
import { getColumn } from './get-column.js';
|
|
8
9
|
import { getRelationInfo } from './get-relation-info.js';
|
|
9
10
|
import { stripFunction } from './strip-function.js';
|
|
10
|
-
// @ts-ignore
|
|
11
|
-
import { customAlphabet } from 'nanoid/non-secure';
|
|
12
11
|
export const generateAlias = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
|
|
13
12
|
/**
|
|
14
13
|
* Apply the Query to a given Knex query builder instance
|
|
@@ -79,7 +78,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
|
|
79
78
|
else if (relationType === 'a2o') {
|
|
80
79
|
const pathScope = pathParts[0].split(':')[1];
|
|
81
80
|
if (!pathScope) {
|
|
82
|
-
throw new
|
|
81
|
+
throw new InvalidQueryError({
|
|
82
|
+
reason: `You have to provide a collection scope when sorting or filtering on a many-to-any item`,
|
|
83
|
+
});
|
|
83
84
|
}
|
|
84
85
|
rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
85
86
|
joinClause
|
|
@@ -113,7 +114,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
|
|
113
114
|
else if (relationType === 'a2o') {
|
|
114
115
|
const pathScope = pathParts[0].split(':')[1];
|
|
115
116
|
if (!pathScope) {
|
|
116
|
-
throw new
|
|
117
|
+
throw new InvalidQueryError({
|
|
118
|
+
reason: `You have to provide a collection scope when sorting or filtering on a many-to-any item`,
|
|
119
|
+
});
|
|
117
120
|
}
|
|
118
121
|
parent = pathScope;
|
|
119
122
|
}
|
|
@@ -288,7 +291,9 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
288
291
|
}
|
|
289
292
|
}
|
|
290
293
|
if (filterPath.includes('_none') || filterPath.includes('_some')) {
|
|
291
|
-
throw new
|
|
294
|
+
throw new InvalidQueryError({
|
|
295
|
+
reason: `"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`,
|
|
296
|
+
});
|
|
292
297
|
}
|
|
293
298
|
const { columnPath, targetCollection, addNestedPkField } = getColumnPath({
|
|
294
299
|
path: filterPath,
|
|
@@ -314,7 +319,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
314
319
|
}
|
|
315
320
|
function validateFilterField(fields, key, collection = 'unknown') {
|
|
316
321
|
if (fields[key] === undefined) {
|
|
317
|
-
throw new
|
|
322
|
+
throw new InvalidQueryError({ reason: `Invalid filter key "${key}" on "${collection}"` });
|
|
318
323
|
}
|
|
319
324
|
return fields[key];
|
|
320
325
|
}
|
|
@@ -323,11 +328,15 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
323
328
|
filterOperator = filterOperator.slice(1);
|
|
324
329
|
}
|
|
325
330
|
if (!getFilterOperatorsForType(type).includes(filterOperator)) {
|
|
326
|
-
throw new
|
|
331
|
+
throw new InvalidQueryError({
|
|
332
|
+
reason: `"${type}" field type does not contain the "_${filterOperator}" filter operator`,
|
|
333
|
+
});
|
|
327
334
|
}
|
|
328
335
|
if (special.includes('conceal') &&
|
|
329
336
|
!getFilterOperatorsForType('hash').includes(filterOperator)) {
|
|
330
|
-
throw new
|
|
337
|
+
throw new InvalidQueryError({
|
|
338
|
+
reason: `Field with "conceal" special does not allow the "_${filterOperator}" filter operator`,
|
|
339
|
+
});
|
|
331
340
|
}
|
|
332
341
|
}
|
|
333
342
|
function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
|
|
@@ -337,18 +346,18 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
337
346
|
// Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
|
|
338
347
|
// See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
|
|
339
348
|
// These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
|
|
340
|
-
if (operator === '_null' || (operator === '_nnull' && compareValue === false)) {
|
|
349
|
+
if ((operator === '_null' && compareValue !== false) || (operator === '_nnull' && compareValue === false)) {
|
|
341
350
|
dbQuery[logical].whereNull(selectionRaw);
|
|
342
351
|
}
|
|
343
|
-
if (operator === '_nnull' || (operator === '_null' && compareValue === false)) {
|
|
352
|
+
if ((operator === '_nnull' && compareValue !== false) || (operator === '_null' && compareValue === false)) {
|
|
344
353
|
dbQuery[logical].whereNotNull(selectionRaw);
|
|
345
354
|
}
|
|
346
|
-
if (operator === '_empty' || (operator === '_nempty' && compareValue === false)) {
|
|
355
|
+
if ((operator === '_empty' && compareValue !== false) || (operator === '_nempty' && compareValue === false)) {
|
|
347
356
|
dbQuery[logical].andWhere((query) => {
|
|
348
357
|
query.whereNull(key).orWhere(key, '=', '');
|
|
349
358
|
});
|
|
350
359
|
}
|
|
351
|
-
if (operator === '_nempty' || (operator === '_empty' && compareValue === false)) {
|
|
360
|
+
if ((operator === '_nempty' && compareValue !== false) || (operator === '_empty' && compareValue === false)) {
|
|
352
361
|
dbQuery[logical].andWhere((query) => {
|
|
353
362
|
query.whereNotNull(key).andWhere(key, '!=', '');
|
|
354
363
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { InvalidConfigException } from '../exceptions/index.js';
|
|
2
1
|
import { getPermissions } from './get-permissions.js';
|
|
3
2
|
export async function getAccountabilityForRole(role, context) {
|
|
4
3
|
let generatedAccountability = context.accountability;
|
|
@@ -27,7 +26,7 @@ export async function getAccountabilityForRole(role, context) {
|
|
|
27
26
|
.where({ id: role })
|
|
28
27
|
.first();
|
|
29
28
|
if (!roleInfo) {
|
|
30
|
-
throw new
|
|
29
|
+
throw new Error(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
|
|
31
30
|
}
|
|
32
31
|
generatedAccountability = {
|
|
33
32
|
role,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import getDatabase from '../database/index.js';
|
|
2
|
+
import env from '../env.js';
|
|
3
|
+
import { InvalidCredentialsError } from '../errors/index.js';
|
|
4
|
+
import isDirectusJWT from './is-directus-jwt.js';
|
|
5
|
+
import { verifyAccessJWT } from './jwt.js';
|
|
6
|
+
export async function getAccountabilityForToken(token, accountability) {
|
|
7
|
+
if (!accountability) {
|
|
8
|
+
accountability = {
|
|
9
|
+
user: null,
|
|
10
|
+
role: null,
|
|
11
|
+
admin: false,
|
|
12
|
+
app: false,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (token) {
|
|
16
|
+
if (isDirectusJWT(token)) {
|
|
17
|
+
const payload = verifyAccessJWT(token, env['SECRET']);
|
|
18
|
+
accountability.role = payload.role;
|
|
19
|
+
accountability.admin = payload.admin_access === true || payload.admin_access == 1;
|
|
20
|
+
accountability.app = payload.app_access === true || payload.app_access == 1;
|
|
21
|
+
if (payload.share)
|
|
22
|
+
accountability.share = payload.share;
|
|
23
|
+
if (payload.share_scope)
|
|
24
|
+
accountability.share_scope = payload.share_scope;
|
|
25
|
+
if (payload.id)
|
|
26
|
+
accountability.user = payload.id;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Try finding the user with the provided token
|
|
30
|
+
const database = getDatabase();
|
|
31
|
+
const user = await database
|
|
32
|
+
.select('directus_users.id', 'directus_users.role', 'directus_roles.admin_access', 'directus_roles.app_access')
|
|
33
|
+
.from('directus_users')
|
|
34
|
+
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
35
|
+
.where({
|
|
36
|
+
'directus_users.token': token,
|
|
37
|
+
status: 'active',
|
|
38
|
+
})
|
|
39
|
+
.first();
|
|
40
|
+
if (!user) {
|
|
41
|
+
throw new InvalidCredentialsError();
|
|
42
|
+
}
|
|
43
|
+
accountability.user = user.id;
|
|
44
|
+
accountability.role = user.role;
|
|
45
|
+
accountability.admin = user.admin_access === true || user.admin_access == 1;
|
|
46
|
+
accountability.app = user.app_access === true || user.app_access == 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return accountability;
|
|
50
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InvalidQueryError } from '../errors/index.js';
|
|
2
2
|
import { getRelationInfo } from './get-relation-info.js';
|
|
3
3
|
/**
|
|
4
4
|
* Converts a Directus field list path to the correct SQL names based on the constructed alias map.
|
|
@@ -15,7 +15,7 @@ export function getColumnPath({ path, collection, aliasMap, relations, schema })
|
|
|
15
15
|
const pathRoot = pathParts[0].split(':')[0];
|
|
16
16
|
const { relation, relationType } = getRelationInfo(relations, parentCollection, pathRoot);
|
|
17
17
|
if (!relation) {
|
|
18
|
-
throw new
|
|
18
|
+
throw new InvalidQueryError({ reason: `"${parentCollection}.${pathRoot}" is not a relational field` });
|
|
19
19
|
}
|
|
20
20
|
const alias = parentFields ? aliasMap[`${parentFields}.${pathParts[0]}`]?.alias : aliasMap[pathParts[0]]?.alias;
|
|
21
21
|
const remainingParts = pathParts.slice(1);
|
|
@@ -23,7 +23,9 @@ export function getColumnPath({ path, collection, aliasMap, relations, schema })
|
|
|
23
23
|
if (relationType === 'a2o') {
|
|
24
24
|
const pathScope = pathParts[0].split(':')[1];
|
|
25
25
|
if (!pathScope) {
|
|
26
|
-
throw new
|
|
26
|
+
throw new InvalidQueryError({
|
|
27
|
+
reason: `You have to provide a collection scope when sorting on a many-to-any item`,
|
|
28
|
+
});
|
|
27
29
|
}
|
|
28
30
|
parent = pathScope;
|
|
29
31
|
}
|
package/dist/utils/get-column.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { REGEX_BETWEEN_PARENS } from '@directus/constants';
|
|
2
2
|
import { getFunctionsForType } from '@directus/utils';
|
|
3
3
|
import { getFunctions } from '../database/helpers/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { InvalidQueryError } from '../errors/index.js';
|
|
5
5
|
import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
|
|
6
6
|
/**
|
|
7
7
|
* Return column prefixed by table. If column includes functions (like `year(date_created)`), the
|
|
@@ -25,7 +25,7 @@ export function getColumn(knex, table, column, alias = applyFunctionToColumnName
|
|
|
25
25
|
const type = schema?.collections[collectionName]?.fields?.[columnName]?.type ?? 'unknown';
|
|
26
26
|
const allowedFunctions = getFunctionsForType(type);
|
|
27
27
|
if (allowedFunctions.includes(functionName) === false) {
|
|
28
|
-
throw new
|
|
28
|
+
throw new InvalidQueryError({ reason: `Invalid function specified "${functionName}"` });
|
|
29
29
|
}
|
|
30
30
|
const result = fn[functionName](table, columnName, {
|
|
31
31
|
type,
|
|
@@ -38,7 +38,7 @@ export function getColumn(knex, table, column, alias = applyFunctionToColumnName
|
|
|
38
38
|
return result;
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
41
|
-
throw new
|
|
41
|
+
throw new InvalidQueryError({ reason: `Invalid function specified "${functionName}"` });
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
if (alias && column !== alias) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ItemsService } from '../services/index.js';
|
|
2
|
+
import type { AbstractServiceOptions } from '../types/services.js';
|
|
3
|
+
/**
|
|
4
|
+
* Select the correct service for the given collection. This allows the individual services to run
|
|
5
|
+
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
|
|
6
|
+
*/
|
|
7
|
+
export declare function getService(collection: string, opts: AbstractServiceOptions): ItemsService;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../services/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Select the correct service for the given collection. This allows the individual services to run
|
|
4
|
+
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
|
|
5
|
+
*/
|
|
6
|
+
export function getService(collection, opts) {
|
|
7
|
+
switch (collection) {
|
|
8
|
+
case 'directus_activity':
|
|
9
|
+
return new ActivityService(opts);
|
|
10
|
+
// case 'directus_collections':
|
|
11
|
+
// return new CollectionsService(opts);
|
|
12
|
+
case 'directus_dashboards':
|
|
13
|
+
return new DashboardsService(opts);
|
|
14
|
+
// case 'directus_fields':
|
|
15
|
+
// return new FieldsService(opts);
|
|
16
|
+
case 'directus_files':
|
|
17
|
+
return new FilesService(opts);
|
|
18
|
+
case 'directus_flows':
|
|
19
|
+
return new FlowsService(opts);
|
|
20
|
+
case 'directus_folders':
|
|
21
|
+
return new FoldersService(opts);
|
|
22
|
+
case 'directus_notifications':
|
|
23
|
+
return new NotificationsService(opts);
|
|
24
|
+
case 'directus_operations':
|
|
25
|
+
return new OperationsService(opts);
|
|
26
|
+
case 'directus_panels':
|
|
27
|
+
return new PanelsService(opts);
|
|
28
|
+
case 'directus_permissions':
|
|
29
|
+
return new PermissionsService(opts);
|
|
30
|
+
case 'directus_presets':
|
|
31
|
+
return new PresetsService(opts);
|
|
32
|
+
// case 'directus_relations':
|
|
33
|
+
// return new RelationsService(opts);
|
|
34
|
+
case 'directus_revisions':
|
|
35
|
+
return new RevisionsService(opts);
|
|
36
|
+
case 'directus_roles':
|
|
37
|
+
return new RolesService(opts);
|
|
38
|
+
case 'directus_settings':
|
|
39
|
+
return new SettingsService(opts);
|
|
40
|
+
case 'directus_shares':
|
|
41
|
+
return new SharesService(opts);
|
|
42
|
+
case 'directus_users':
|
|
43
|
+
return new UsersService(opts);
|
|
44
|
+
case 'directus_webhooks':
|
|
45
|
+
return new WebhooksService(opts);
|
|
46
|
+
default:
|
|
47
|
+
return new ItemsService(collection, opts);
|
|
48
|
+
}
|
|
49
|
+
}
|