@directus/api 11.1.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/env.js +6 -13
- 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.js +4 -4
- package/dist/middleware/authenticate.js +1 -1
- 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/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 -2
- package/dist/services/graphql/index.js +30 -12
- 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/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/synchronization.js +3 -3
- package/dist/types/items.d.ts +2 -2
- package/dist/utils/apply-diff.js +2 -2
- package/dist/utils/apply-query.js +17 -7
- package/dist/utils/get-accountability-for-role.js +1 -2
- package/dist/utils/get-accountability-for-token.js +3 -3
- package/dist/utils/get-column-path.js +5 -3
- package/dist/utils/get-column.js +3 -3
- package/dist/utils/get-service.d.ts +1 -1
- package/dist/utils/get-service.js +1 -1
- package/dist/utils/jwt.js +5 -5
- 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.js +12 -15
- package/dist/websocket/controllers/base.js +18 -15
- package/dist/websocket/controllers/graphql.js +2 -2
- package/dist/websocket/controllers/index.js +3 -7
- package/dist/websocket/controllers/rest.js +3 -3
- package/dist/websocket/{exceptions.d.ts → errors.d.ts} +5 -5
- package/dist/websocket/{exceptions.js → errors.js} +10 -10
- package/dist/websocket/handlers/items.js +5 -5
- package/dist/websocket/handlers/subscribe.js +8 -8
- package/package.json +15 -15
- 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/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';
|
|
@@ -246,7 +246,7 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
|
|
|
246
246
|
if (runPostColumnChange) {
|
|
247
247
|
await helpers.schema.postColumnChange();
|
|
248
248
|
}
|
|
249
|
-
await
|
|
249
|
+
await flushCaches();
|
|
250
250
|
if (nestedActionEvents.length > 0) {
|
|
251
251
|
const updatedSchema = await getSchema({ database, bypassCache: true });
|
|
252
252
|
for (const nestedActionEvent of nestedActionEvents) {
|
|
@@ -3,7 +3,7 @@ import { clone, isPlainObject } from 'lodash-es';
|
|
|
3
3
|
import { customAlphabet } from 'nanoid/non-secure';
|
|
4
4
|
import validate from 'uuid-validate';
|
|
5
5
|
import { getHelpers } from '../database/helpers/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { InvalidQueryError } from '../errors/index.js';
|
|
7
7
|
import { getColumnPath } from './get-column-path.js';
|
|
8
8
|
import { getColumn } from './get-column.js';
|
|
9
9
|
import { getRelationInfo } from './get-relation-info.js';
|
|
@@ -78,7 +78,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
|
|
78
78
|
else if (relationType === 'a2o') {
|
|
79
79
|
const pathScope = pathParts[0].split(':')[1];
|
|
80
80
|
if (!pathScope) {
|
|
81
|
-
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
|
+
});
|
|
82
84
|
}
|
|
83
85
|
rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
|
|
84
86
|
joinClause
|
|
@@ -112,7 +114,9 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
|
|
112
114
|
else if (relationType === 'a2o') {
|
|
113
115
|
const pathScope = pathParts[0].split(':')[1];
|
|
114
116
|
if (!pathScope) {
|
|
115
|
-
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
|
+
});
|
|
116
120
|
}
|
|
117
121
|
parent = pathScope;
|
|
118
122
|
}
|
|
@@ -287,7 +291,9 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
287
291
|
}
|
|
288
292
|
}
|
|
289
293
|
if (filterPath.includes('_none') || filterPath.includes('_some')) {
|
|
290
|
-
throw new
|
|
294
|
+
throw new InvalidQueryError({
|
|
295
|
+
reason: `"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`,
|
|
296
|
+
});
|
|
291
297
|
}
|
|
292
298
|
const { columnPath, targetCollection, addNestedPkField } = getColumnPath({
|
|
293
299
|
path: filterPath,
|
|
@@ -313,7 +319,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
313
319
|
}
|
|
314
320
|
function validateFilterField(fields, key, collection = 'unknown') {
|
|
315
321
|
if (fields[key] === undefined) {
|
|
316
|
-
throw new
|
|
322
|
+
throw new InvalidQueryError({ reason: `Invalid filter key "${key}" on "${collection}"` });
|
|
317
323
|
}
|
|
318
324
|
return fields[key];
|
|
319
325
|
}
|
|
@@ -322,11 +328,15 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
322
328
|
filterOperator = filterOperator.slice(1);
|
|
323
329
|
}
|
|
324
330
|
if (!getFilterOperatorsForType(type).includes(filterOperator)) {
|
|
325
|
-
throw new
|
|
331
|
+
throw new InvalidQueryError({
|
|
332
|
+
reason: `"${type}" field type does not contain the "_${filterOperator}" filter operator`,
|
|
333
|
+
});
|
|
326
334
|
}
|
|
327
335
|
if (special.includes('conceal') &&
|
|
328
336
|
!getFilterOperatorsForType('hash').includes(filterOperator)) {
|
|
329
|
-
throw new
|
|
337
|
+
throw new InvalidQueryError({
|
|
338
|
+
reason: `Field with "conceal" special does not allow the "_${filterOperator}" filter operator`,
|
|
339
|
+
});
|
|
330
340
|
}
|
|
331
341
|
}
|
|
332
342
|
function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
|
|
@@ -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,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import isDirectusJWT from './is-directus-jwt.js';
|
|
3
|
-
import { InvalidCredentialsException } from '../index.js';
|
|
4
2
|
import env from '../env.js';
|
|
3
|
+
import { InvalidCredentialsError } from '../errors/index.js';
|
|
4
|
+
import isDirectusJWT from './is-directus-jwt.js';
|
|
5
5
|
import { verifyAccessJWT } from './jwt.js';
|
|
6
6
|
export async function getAccountabilityForToken(token, accountability) {
|
|
7
7
|
if (!accountability) {
|
|
@@ -38,7 +38,7 @@ export async function getAccountabilityForToken(token, accountability) {
|
|
|
38
38
|
})
|
|
39
39
|
.first();
|
|
40
40
|
if (!user) {
|
|
41
|
-
throw new
|
|
41
|
+
throw new InvalidCredentialsError();
|
|
42
42
|
}
|
|
43
43
|
accountability.user = user.id;
|
|
44
44
|
accountability.role = user.role;
|
|
@@ -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) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ItemsService } from '../index.js';
|
|
1
|
+
import { ItemsService } from '../services/index.js';
|
|
2
2
|
import type { AbstractServiceOptions } from '../types/services.js';
|
|
3
3
|
/**
|
|
4
4
|
* Select the correct service for the given collection. This allows the individual services to run
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../index.js';
|
|
1
|
+
import { ActivityService, DashboardsService, FilesService, FlowsService, FoldersService, ItemsService, NotificationsService, OperationsService, PanelsService, PermissionsService, PresetsService, RevisionsService, RolesService, SettingsService, SharesService, UsersService, WebhooksService, } from '../services/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Select the correct service for the given collection. This allows the individual services to run
|
|
4
4
|
* their custom checks (f.e. it allows UsersService to prevent updating TFA secret from outside)
|
package/dist/utils/jwt.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import jwt from 'jsonwebtoken';
|
|
2
|
-
import {
|
|
2
|
+
import { InvalidTokenError, ServiceUnavailableError, TokenExpiredError } from '../errors/index.js';
|
|
3
3
|
export function verifyJWT(token, secret) {
|
|
4
4
|
let payload;
|
|
5
5
|
try {
|
|
@@ -9,13 +9,13 @@ export function verifyJWT(token, secret) {
|
|
|
9
9
|
}
|
|
10
10
|
catch (err) {
|
|
11
11
|
if (err instanceof jwt.TokenExpiredError) {
|
|
12
|
-
throw new
|
|
12
|
+
throw new TokenExpiredError();
|
|
13
13
|
}
|
|
14
14
|
else if (err instanceof jwt.JsonWebTokenError) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new InvalidTokenError();
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
throw new
|
|
18
|
+
throw new ServiceUnavailableError({ service: 'jwt', reason: `Couldn't verify token.` });
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
return payload;
|
|
@@ -23,7 +23,7 @@ export function verifyJWT(token, secret) {
|
|
|
23
23
|
export function verifyAccessJWT(token, secret) {
|
|
24
24
|
const { id, role, app_access, admin_access, share, share_scope } = verifyJWT(token, secret);
|
|
25
25
|
if (role === undefined || app_access === undefined || admin_access === undefined) {
|
|
26
|
-
throw new
|
|
26
|
+
throw new InvalidTokenError();
|
|
27
27
|
}
|
|
28
28
|
return { id, role, app_access, admin_access, share, share_scope };
|
|
29
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Joi from 'joi';
|
|
2
|
-
import {
|
|
2
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
3
3
|
import { DiffKind } from '../types/snapshot.js';
|
|
4
4
|
const deepDiffSchema = Joi.object({
|
|
5
5
|
kind: Joi.string()
|
|
@@ -49,7 +49,7 @@ const applyJoiSchema = Joi.object({
|
|
|
49
49
|
export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
50
50
|
const { error } = applyJoiSchema.validate(applyDiff);
|
|
51
51
|
if (error)
|
|
52
|
-
throw new
|
|
52
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
53
53
|
// No changes to apply
|
|
54
54
|
if (applyDiff.diff.collections.length === 0 &&
|
|
55
55
|
applyDiff.diff.fields.length === 0 &&
|
|
@@ -64,13 +64,17 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
|
64
64
|
if (diffCollection.diff[0]?.kind === DiffKind.NEW) {
|
|
65
65
|
const existingCollection = currentSnapshotWithHash.collections.find((c) => c.collection === diffCollection.collection);
|
|
66
66
|
if (existingCollection) {
|
|
67
|
-
throw new
|
|
67
|
+
throw new InvalidPayloadError({
|
|
68
|
+
reason: `Provided diff is trying to create collection "${collection}" but it already exists. Please generate a new diff and try again`,
|
|
69
|
+
});
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
else if (diffCollection.diff[0]?.kind === DiffKind.DELETE) {
|
|
71
73
|
const existingCollection = currentSnapshotWithHash.collections.find((c) => c.collection === diffCollection.collection);
|
|
72
74
|
if (!existingCollection) {
|
|
73
|
-
throw new
|
|
75
|
+
throw new InvalidPayloadError({
|
|
76
|
+
reason: `Provided diff is trying to delete collection "${collection}" but it does not exist. Please generate a new diff and try again`,
|
|
77
|
+
});
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -79,13 +83,17 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
|
79
83
|
if (diffField.diff[0]?.kind === DiffKind.NEW) {
|
|
80
84
|
const existingField = currentSnapshotWithHash.fields.find((f) => f.collection === diffField.collection && f.field === diffField.field);
|
|
81
85
|
if (existingField) {
|
|
82
|
-
throw new
|
|
86
|
+
throw new InvalidPayloadError({
|
|
87
|
+
reason: `Provided diff is trying to create field "${field}" but it already exists. Please generate a new diff and try again`,
|
|
88
|
+
});
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
else if (diffField.diff[0]?.kind === DiffKind.DELETE) {
|
|
86
92
|
const existingField = currentSnapshotWithHash.fields.find((f) => f.collection === diffField.collection && f.field === diffField.field);
|
|
87
93
|
if (!existingField) {
|
|
88
|
-
throw new
|
|
94
|
+
throw new InvalidPayloadError({
|
|
95
|
+
reason: `Provided diff is trying to delete field "${field}" but it does not exist. Please generate a new diff and try again`,
|
|
96
|
+
});
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
}
|
|
@@ -96,15 +104,21 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
|
96
104
|
if (diffRelation.diff[0]?.kind === DiffKind.NEW) {
|
|
97
105
|
const existingRelation = currentSnapshotWithHash.relations.find((r) => r.collection === diffRelation.collection && r.field === diffRelation.field);
|
|
98
106
|
if (existingRelation) {
|
|
99
|
-
throw new
|
|
107
|
+
throw new InvalidPayloadError({
|
|
108
|
+
reason: `Provided diff is trying to create relation "${relation}" but it already exists. Please generate a new diff and try again`,
|
|
109
|
+
});
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
else if (diffRelation.diff[0]?.kind === DiffKind.DELETE) {
|
|
103
113
|
const existingRelation = currentSnapshotWithHash.relations.find((r) => r.collection === diffRelation.collection && r.field === diffRelation.field);
|
|
104
114
|
if (!existingRelation) {
|
|
105
|
-
throw new
|
|
115
|
+
throw new InvalidPayloadError({
|
|
116
|
+
reason: `Provided diff is trying to delete relation "${relation}" but it does not exist. Please generate a new diff and try again`,
|
|
117
|
+
});
|
|
106
118
|
}
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
|
-
throw new
|
|
121
|
+
throw new InvalidPayloadError({
|
|
122
|
+
reason: `Provided hash does not match the current instance's schema hash, indicating the schema has changed after this diff was generated. Please generate a new diff and try again`,
|
|
123
|
+
});
|
|
110
124
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ForbiddenError } from '../errors/index.js';
|
|
1
2
|
import validateUUID from 'uuid-validate';
|
|
2
|
-
import { ForbiddenException } from '../exceptions/forbidden.js';
|
|
3
3
|
/**
|
|
4
4
|
* Validate keys based on its type
|
|
5
5
|
*/
|
|
@@ -12,10 +12,10 @@ export function validateKeys(schema, collection, keyField, keys) {
|
|
|
12
12
|
else {
|
|
13
13
|
const primaryKeyFieldType = schema.collections[collection]?.fields[keyField]?.type;
|
|
14
14
|
if (primaryKeyFieldType === 'uuid' && !validateUUID(String(keys))) {
|
|
15
|
-
throw new
|
|
15
|
+
throw new ForbiddenError();
|
|
16
16
|
}
|
|
17
17
|
else if (primaryKeyFieldType === 'integer' && !Number.isInteger(Number(keys))) {
|
|
18
|
-
throw new
|
|
18
|
+
throw new ForbiddenError();
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -2,7 +2,7 @@ import Joi from 'joi';
|
|
|
2
2
|
import { isPlainObject, uniq } from 'lodash-es';
|
|
3
3
|
import { stringify } from 'wellknown';
|
|
4
4
|
import env from '../env.js';
|
|
5
|
-
import {
|
|
5
|
+
import { InvalidQueryError } from '../errors/index.js';
|
|
6
6
|
import { calculateFieldDepth } from './calculate-field-depth.js';
|
|
7
7
|
const querySchema = Joi.object({
|
|
8
8
|
fields: Joi.array().items(Joi.string()),
|
|
@@ -31,13 +31,13 @@ export function validateQuery(query) {
|
|
|
31
31
|
}
|
|
32
32
|
validateRelationalDepth(query);
|
|
33
33
|
if (error) {
|
|
34
|
-
throw new
|
|
34
|
+
throw new InvalidQueryError({ reason: error.message });
|
|
35
35
|
}
|
|
36
36
|
return query;
|
|
37
37
|
}
|
|
38
38
|
function validateFilter(filter) {
|
|
39
39
|
if (!filter)
|
|
40
|
-
throw new
|
|
40
|
+
throw new InvalidQueryError({ reason: 'Invalid filter object' });
|
|
41
41
|
for (const [key, nested] of Object.entries(filter)) {
|
|
42
42
|
if (key === '_and' || key === '_or') {
|
|
43
43
|
nested.forEach(validateFilter);
|
|
@@ -73,8 +73,12 @@ function validateFilter(filter) {
|
|
|
73
73
|
case '_ncontains':
|
|
74
74
|
case '_starts_with':
|
|
75
75
|
case '_nstarts_with':
|
|
76
|
+
case '_istarts_with':
|
|
77
|
+
case '_nistarts_with':
|
|
76
78
|
case '_ends_with':
|
|
77
79
|
case '_nends_with':
|
|
80
|
+
case '_iends_with':
|
|
81
|
+
case '_niends_with':
|
|
78
82
|
case '_gt':
|
|
79
83
|
case '_gte':
|
|
80
84
|
case '_lt':
|
|
@@ -100,54 +104,56 @@ function validateFilterPrimitive(value, key) {
|
|
|
100
104
|
return true;
|
|
101
105
|
if ((typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value instanceof Date) ===
|
|
102
106
|
false) {
|
|
103
|
-
throw new
|
|
107
|
+
throw new InvalidQueryError({ reason: `The filter value for "${key}" has to be a string, number, or boolean` });
|
|
104
108
|
}
|
|
105
109
|
if (typeof value === 'number' && (Number.isNaN(value) || value > Number.MAX_SAFE_INTEGER)) {
|
|
106
|
-
throw new
|
|
110
|
+
throw new InvalidQueryError({ reason: `The filter value for "${key}" is not a valid number` });
|
|
107
111
|
}
|
|
108
112
|
if (typeof value === 'string' && value.length === 0) {
|
|
109
|
-
throw new
|
|
113
|
+
throw new InvalidQueryError({
|
|
114
|
+
reason: `You can't filter for an empty string in "${key}". Use "_empty" or "_nempty" instead`,
|
|
115
|
+
});
|
|
110
116
|
}
|
|
111
117
|
return true;
|
|
112
118
|
}
|
|
113
119
|
function validateList(value, key) {
|
|
114
120
|
if (Array.isArray(value) === false || value.length === 0) {
|
|
115
|
-
throw new
|
|
121
|
+
throw new InvalidQueryError({ reason: `"${key}" has to be an array of values` });
|
|
116
122
|
}
|
|
117
123
|
return true;
|
|
118
124
|
}
|
|
119
|
-
function validateBoolean(value, key) {
|
|
120
|
-
if (value === null)
|
|
125
|
+
export function validateBoolean(value, key) {
|
|
126
|
+
if (value === null || value === '')
|
|
121
127
|
return true;
|
|
122
128
|
if (typeof value !== 'boolean') {
|
|
123
|
-
throw new
|
|
129
|
+
throw new InvalidQueryError({ reason: `"${key}" has to be a boolean` });
|
|
124
130
|
}
|
|
125
131
|
return true;
|
|
126
132
|
}
|
|
127
|
-
function validateGeometry(value, key) {
|
|
128
|
-
if (value === null)
|
|
133
|
+
export function validateGeometry(value, key) {
|
|
134
|
+
if (value === null || value === '')
|
|
129
135
|
return true;
|
|
130
136
|
try {
|
|
131
137
|
stringify(value);
|
|
132
138
|
}
|
|
133
139
|
catch {
|
|
134
|
-
throw new
|
|
140
|
+
throw new InvalidQueryError({ reason: `"${key}" has to be a valid GeoJSON object` });
|
|
135
141
|
}
|
|
136
142
|
return true;
|
|
137
143
|
}
|
|
138
144
|
function validateAlias(alias) {
|
|
139
145
|
if (isPlainObject(alias) === false) {
|
|
140
|
-
throw new
|
|
146
|
+
throw new InvalidQueryError({ reason: `"alias" has to be an object` });
|
|
141
147
|
}
|
|
142
148
|
for (const [key, value] of Object.entries(alias)) {
|
|
143
149
|
if (typeof key !== 'string') {
|
|
144
|
-
throw new
|
|
150
|
+
throw new InvalidQueryError({ reason: `"alias" key has to be a string. "${typeof key}" given` });
|
|
145
151
|
}
|
|
146
152
|
if (typeof value !== 'string') {
|
|
147
|
-
throw new
|
|
153
|
+
throw new InvalidQueryError({ reason: `"alias" value has to be a string. "${typeof key}" given` });
|
|
148
154
|
}
|
|
149
155
|
if (key.includes('.') || value.includes('.')) {
|
|
150
|
-
throw new
|
|
156
|
+
throw new InvalidQueryError({ reason: `"alias" key/value can't contain a period character \`.\`` });
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
}
|
|
@@ -175,26 +181,26 @@ function validateRelationalDepth(query) {
|
|
|
175
181
|
fields = uniq(fields);
|
|
176
182
|
for (const field of fields) {
|
|
177
183
|
if (field.split('.').length > maxRelationalDepth) {
|
|
178
|
-
throw new
|
|
184
|
+
throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
187
|
if (query.filter) {
|
|
182
188
|
const filterRelationalDepth = calculateFieldDepth(query.filter);
|
|
183
189
|
if (filterRelationalDepth > maxRelationalDepth) {
|
|
184
|
-
throw new
|
|
190
|
+
throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
|
|
185
191
|
}
|
|
186
192
|
}
|
|
187
193
|
if (query.sort) {
|
|
188
194
|
for (const sort of query.sort) {
|
|
189
195
|
if (sort.split('.').length > maxRelationalDepth) {
|
|
190
|
-
throw new
|
|
196
|
+
throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
|
|
191
197
|
}
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
200
|
if (query.deep) {
|
|
195
201
|
const deepRelationalDepth = calculateFieldDepth(query.deep, ['_sort']);
|
|
196
202
|
if (deepRelationalDepth > maxRelationalDepth) {
|
|
197
|
-
throw new
|
|
203
|
+
throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
}
|
|
@@ -2,7 +2,7 @@ import { TYPES } from '@directus/constants';
|
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import { ALIAS_TYPES } from '../constants.js';
|
|
4
4
|
import { getDatabaseClient } from '../database/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
6
6
|
import { DatabaseClients } from '../types/index.js';
|
|
7
7
|
import { version as currentDirectusVersion } from './package.js';
|
|
8
8
|
const snapshotJoiSchema = Joi.object({
|
|
@@ -47,18 +47,24 @@ const snapshotJoiSchema = Joi.object({
|
|
|
47
47
|
export function validateSnapshot(snapshot, force = false) {
|
|
48
48
|
const { error } = snapshotJoiSchema.validate(snapshot);
|
|
49
49
|
if (error)
|
|
50
|
-
throw new
|
|
50
|
+
throw new InvalidPayloadError({ reason: error.message });
|
|
51
51
|
// Bypass checks when "force" option is enabled
|
|
52
52
|
if (force)
|
|
53
53
|
return;
|
|
54
54
|
if (snapshot.directus !== currentDirectusVersion) {
|
|
55
|
-
throw new
|
|
55
|
+
throw new InvalidPayloadError({
|
|
56
|
+
reason: `Provided snapshot's directus version ${snapshot.directus} does not match the current instance's version ${currentDirectusVersion}. You can bypass this check by passing the "force" query parameter`,
|
|
57
|
+
});
|
|
56
58
|
}
|
|
57
59
|
if (!snapshot.vendor) {
|
|
58
|
-
throw new
|
|
60
|
+
throw new InvalidPayloadError({
|
|
61
|
+
reason: 'Provided snapshot does not contain the "vendor" property. You can bypass this check by passing the "force" query parameter',
|
|
62
|
+
});
|
|
59
63
|
}
|
|
60
64
|
const currentVendor = getDatabaseClient();
|
|
61
65
|
if (snapshot.vendor !== currentVendor) {
|
|
62
|
-
throw new
|
|
66
|
+
throw new InvalidPayloadError({
|
|
67
|
+
reason: `Provided snapshot's vendor ${snapshot.vendor} does not match the current instance's vendor ${currentVendor}. You can bypass this check by passing the "force" query parameter`,
|
|
68
|
+
});
|
|
63
69
|
}
|
|
64
70
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
|
|
2
|
-
import getDatabase from '../database/index.js';
|
|
3
|
-
import { InvalidCredentialsException } from '../exceptions/index.js';
|
|
4
2
|
import { AuthenticationService } from '../services/index.js';
|
|
5
|
-
import { getAccountabilityForRole } from '../utils/get-accountability-for-role.js';
|
|
6
3
|
import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
|
|
4
|
+
import { getPermissions } from '../utils/get-permissions.js';
|
|
7
5
|
import { getSchema } from '../utils/get-schema.js';
|
|
8
|
-
import {
|
|
6
|
+
import { WebSocketError } from './errors.js';
|
|
9
7
|
import { getExpiresAtForToken } from './utils/get-expires-at-for-token.js';
|
|
10
8
|
export async function authenticateConnection(message) {
|
|
11
9
|
let access_token, refresh_token;
|
|
@@ -32,20 +30,19 @@ export async function authenticateConnection(message) {
|
|
|
32
30
|
return { accountability, expires_at, refresh_token };
|
|
33
31
|
}
|
|
34
32
|
catch (error) {
|
|
35
|
-
|
|
36
|
-
throw new WebSocketException('auth', 'TOKEN_EXPIRED', 'Token expired.', message['uid']);
|
|
37
|
-
}
|
|
38
|
-
throw new WebSocketException('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
|
|
33
|
+
throw new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
36
|
export async function refreshAccountability(accountability) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
accountability = accountability ?? {
|
|
38
|
+
role: null,
|
|
39
|
+
user: null,
|
|
40
|
+
admin: false,
|
|
41
|
+
app: false,
|
|
42
|
+
};
|
|
43
|
+
const schema = await getSchema();
|
|
44
|
+
const permissions = await getPermissions(accountability, schema);
|
|
45
|
+
return { ...accountability, permissions };
|
|
49
46
|
}
|
|
50
47
|
export function authenticationSuccess(uid, refresh_token) {
|
|
51
48
|
const message = {
|