@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
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { isEqual } from 'lodash-es';
|
|
2
2
|
import getDatabase from '../database/index.js';
|
|
3
3
|
import emitter from '../emitter.js';
|
|
4
|
-
import env from '../env.js';
|
|
5
|
-
import { InvalidCredentialsException } from '../exceptions/index.js';
|
|
6
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
|
+
import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
|
|
7
6
|
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
8
|
-
import isDirectusJWT from '../utils/is-directus-jwt.js';
|
|
9
|
-
import { verifyAccessJWT } from '../utils/jwt.js';
|
|
10
7
|
/**
|
|
11
8
|
* Verify the passed JWT and assign the user ID and role to `req`
|
|
12
9
|
*/
|
|
@@ -36,40 +33,7 @@ export const handler = async (req, _res, next) => {
|
|
|
36
33
|
req.accountability = customAccountability;
|
|
37
34
|
return next();
|
|
38
35
|
}
|
|
39
|
-
req.accountability = defaultAccountability;
|
|
40
|
-
if (req.token) {
|
|
41
|
-
if (isDirectusJWT(req.token)) {
|
|
42
|
-
const payload = verifyAccessJWT(req.token, env['SECRET']);
|
|
43
|
-
req.accountability.role = payload.role;
|
|
44
|
-
req.accountability.admin = payload.admin_access === true || payload.admin_access == 1;
|
|
45
|
-
req.accountability.app = payload.app_access === true || payload.app_access == 1;
|
|
46
|
-
if (payload.share)
|
|
47
|
-
req.accountability.share = payload.share;
|
|
48
|
-
if (payload.share_scope)
|
|
49
|
-
req.accountability.share_scope = payload.share_scope;
|
|
50
|
-
if (payload.id)
|
|
51
|
-
req.accountability.user = payload.id;
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
// Try finding the user with the provided token
|
|
55
|
-
const user = await database
|
|
56
|
-
.select('directus_users.id', 'directus_users.role', 'directus_roles.admin_access', 'directus_roles.app_access')
|
|
57
|
-
.from('directus_users')
|
|
58
|
-
.leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
|
|
59
|
-
.where({
|
|
60
|
-
'directus_users.token': req.token,
|
|
61
|
-
status: 'active',
|
|
62
|
-
})
|
|
63
|
-
.first();
|
|
64
|
-
if (!user) {
|
|
65
|
-
throw new InvalidCredentialsException();
|
|
66
|
-
}
|
|
67
|
-
req.accountability.user = user.id;
|
|
68
|
-
req.accountability.role = user.role;
|
|
69
|
-
req.accountability.admin = user.admin_access === true || user.admin_access == 1;
|
|
70
|
-
req.accountability.app = user.app_access === true || user.app_access == 1;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
36
|
+
req.accountability = await getAccountabilityForToken(req.token, defaultAccountability);
|
|
73
37
|
return next();
|
|
74
38
|
};
|
|
75
39
|
export default asyncHandler(handler);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { InvalidIpError } from '../errors/index.js';
|
|
3
3
|
import asyncHandler from '../utils/async-handler.js';
|
|
4
4
|
export const checkIP = asyncHandler(async (req, _res, next) => {
|
|
5
5
|
const database = getDatabase();
|
|
@@ -13,6 +13,6 @@ export const checkIP = asyncHandler(async (req, _res, next) => {
|
|
|
13
13
|
const role = await query.first();
|
|
14
14
|
const ipAllowlist = (role?.ip_access || '').split(',').filter((ip) => ip);
|
|
15
15
|
if (ipAllowlist.length > 0 && ipAllowlist.includes(req.accountability.ip) === false)
|
|
16
|
-
throw new
|
|
16
|
+
throw new InvalidIpError();
|
|
17
17
|
return next();
|
|
18
18
|
});
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Check if requested collection exists, and save it to req.collection
|
|
3
3
|
*/
|
|
4
4
|
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
5
|
-
import {
|
|
5
|
+
import { ForbiddenError } from '../errors/index.js';
|
|
6
6
|
import asyncHandler from '../utils/async-handler.js';
|
|
7
7
|
const collectionExists = asyncHandler(async (req, _res, next) => {
|
|
8
8
|
if (!req.params['collection'])
|
|
9
9
|
return next();
|
|
10
10
|
if (req.params['collection'] in req.schema.collections === false) {
|
|
11
|
-
throw new
|
|
11
|
+
throw new ForbiddenError();
|
|
12
12
|
}
|
|
13
13
|
req.collection = req.params['collection'];
|
|
14
14
|
if (req.collection.startsWith('directus_')) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDirectusError } from '@directus/errors';
|
|
2
2
|
import { toArray } from '@directus/utils';
|
|
3
3
|
import getDatabase from '../database/index.js';
|
|
4
4
|
import emitter from '../emitter.js';
|
|
5
5
|
import env from '../env.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ErrorCode, MethodNotAllowedError } from '../errors/index.js';
|
|
7
7
|
import logger from '../logger.js';
|
|
8
8
|
// Note: keep all 4 parameters here. That's how Express recognizes it's the error handler, even if
|
|
9
9
|
// we don't use next
|
|
@@ -12,7 +12,7 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
12
12
|
errors: [],
|
|
13
13
|
};
|
|
14
14
|
const errors = toArray(err);
|
|
15
|
-
if (errors.some((err) => err
|
|
15
|
+
if (errors.some((err) => isDirectusError(err) === false)) {
|
|
16
16
|
res.status(500);
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
@@ -33,18 +33,18 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
33
33
|
stack: err.stack,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
if (err
|
|
36
|
+
if (isDirectusError(err)) {
|
|
37
37
|
logger.debug(err);
|
|
38
38
|
res.status(err.status);
|
|
39
39
|
payload.errors.push({
|
|
40
40
|
message: err.message,
|
|
41
41
|
extensions: {
|
|
42
42
|
code: err.code,
|
|
43
|
-
...err.extensions,
|
|
43
|
+
...(err.extensions ?? {}),
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
|
-
if (err
|
|
47
|
-
res.header('Allow', err.extensions
|
|
46
|
+
if (isDirectusError(err, ErrorCode.MethodNotAllowed)) {
|
|
47
|
+
res.header('Allow', err.extensions.allowed.join(', '));
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { parseJSON } from '@directus/utils';
|
|
2
2
|
import { getOperationAST, parse, Source } from 'graphql';
|
|
3
|
-
import {
|
|
3
|
+
import { InvalidPayloadError, InvalidQueryError, MethodNotAllowedError } from '../errors/index.js';
|
|
4
|
+
import { GraphQLValidationError } from '../services/graphql/errors/validation.js';
|
|
4
5
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
6
|
export const parseGraphQL = asyncHandler(async (req, res, next) => {
|
|
6
7
|
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
7
|
-
throw new
|
|
8
|
+
throw new MethodNotAllowedError({ allowed: ['GET', 'POST'], current: req.method });
|
|
8
9
|
}
|
|
9
10
|
let query = null;
|
|
10
11
|
let variables = null;
|
|
@@ -17,7 +18,7 @@ export const parseGraphQL = asyncHandler(async (req, res, next) => {
|
|
|
17
18
|
variables = parseJSON(req.query['variables']);
|
|
18
19
|
}
|
|
19
20
|
catch {
|
|
20
|
-
throw new
|
|
21
|
+
throw new InvalidQueryError({ reason: `Variables are invalid JSON` });
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
else {
|
|
@@ -31,21 +32,22 @@ export const parseGraphQL = asyncHandler(async (req, res, next) => {
|
|
|
31
32
|
operationName = req.body.operationName || null;
|
|
32
33
|
}
|
|
33
34
|
if (query === null) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new InvalidPayloadError({ reason: 'Must provide query string' });
|
|
35
36
|
}
|
|
36
37
|
try {
|
|
37
38
|
document = parse(new Source(query));
|
|
38
39
|
}
|
|
39
40
|
catch (err) {
|
|
40
|
-
throw new
|
|
41
|
-
|
|
41
|
+
throw new GraphQLValidationError({
|
|
42
|
+
errors: [err],
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
const operationAST = getOperationAST(document, operationName);
|
|
45
|
-
//
|
|
46
|
+
// Mutations can't happen through GET requests
|
|
46
47
|
if (req.method === 'GET' && operationAST?.operation !== 'query') {
|
|
47
|
-
throw new
|
|
48
|
-
|
|
48
|
+
throw new MethodNotAllowedError({
|
|
49
|
+
allowed: ['POST'],
|
|
50
|
+
current: 'GET',
|
|
49
51
|
});
|
|
50
52
|
}
|
|
51
53
|
// Prevent caching responses when mutations are made
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RequestHandler } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
|
|
3
3
|
declare let checkRateLimit: RequestHandler;
|
|
4
|
-
export declare let rateLimiterGlobal: RateLimiterRedis |
|
|
4
|
+
export declare let rateLimiterGlobal: RateLimiterRedis | RateLimiterMemory;
|
|
5
5
|
export default checkRateLimit;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import ms from 'ms';
|
|
2
1
|
import env from '../env.js';
|
|
3
|
-
import {
|
|
2
|
+
import { HitRateLimitError } from '../errors/index.js';
|
|
4
3
|
import logger from '../logger.js';
|
|
5
4
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
6
5
|
import asyncHandler from '../utils/async-handler.js';
|
|
@@ -20,7 +19,7 @@ if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
|
|
|
20
19
|
if (rateLimiterRes instanceof Error)
|
|
21
20
|
throw rateLimiterRes;
|
|
22
21
|
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
|
|
23
|
-
throw new
|
|
22
|
+
throw new HitRateLimitError({
|
|
24
23
|
limit: +env['RATE_LIMITER_GLOBAL_POINTS'],
|
|
25
24
|
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
|
|
26
25
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RequestHandler } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
|
|
3
3
|
declare let checkRateLimit: RequestHandler;
|
|
4
|
-
export declare let rateLimiter: RateLimiterRedis |
|
|
4
|
+
export declare let rateLimiter: RateLimiterRedis | RateLimiterMemory;
|
|
5
5
|
export default checkRateLimit;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import ms from 'ms';
|
|
2
1
|
import env from '../env.js';
|
|
3
|
-
import {
|
|
2
|
+
import { HitRateLimitError } from '../errors/index.js';
|
|
4
3
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
5
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
6
5
|
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
@@ -18,7 +17,7 @@ if (env['RATE_LIMITER_ENABLED'] === true) {
|
|
|
18
17
|
if (rateLimiterRes instanceof Error)
|
|
19
18
|
throw rateLimiterRes;
|
|
20
19
|
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
|
|
21
|
-
throw new
|
|
20
|
+
throw new HitRateLimitError({
|
|
22
21
|
limit: +env['RATE_LIMITER_POINTS'],
|
|
23
22
|
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
|
|
24
23
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { FailedValidationException } from '@directus/exceptions';
|
|
2
1
|
import Joi from 'joi';
|
|
3
|
-
import {
|
|
2
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
4
3
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
4
|
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
6
5
|
export const validateBatch = (scope) => asyncHandler(async (req, _res, next) => {
|
|
@@ -12,7 +11,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
|
|
|
12
11
|
return next();
|
|
13
12
|
}
|
|
14
13
|
if (!req.body)
|
|
15
|
-
throw new
|
|
14
|
+
throw new InvalidPayloadError({ reason: 'Payload in body is required' });
|
|
16
15
|
if (['update', 'delete'].includes(scope) && Array.isArray(req.body)) {
|
|
17
16
|
return next();
|
|
18
17
|
}
|
|
@@ -36,7 +35,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
|
|
|
36
35
|
}
|
|
37
36
|
const { error } = batchSchema.validate(req.body);
|
|
38
37
|
if (error) {
|
|
39
|
-
throw new
|
|
38
|
+
throw new InvalidPayloadError({ reason: error.details[0].message });
|
|
40
39
|
}
|
|
41
40
|
return next();
|
|
42
41
|
});
|
package/dist/rate-limiter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { merge } from 'lodash-es';
|
|
2
|
-
import {
|
|
2
|
+
import { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
|
|
3
3
|
import env from './env.js';
|
|
4
4
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
@@ -8,8 +8,6 @@ export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides
|
|
|
8
8
|
switch (env['RATE_LIMITER_STORE']) {
|
|
9
9
|
case 'redis':
|
|
10
10
|
return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
|
|
11
|
-
case 'memcache':
|
|
12
|
-
return new RateLimiterMemcache(getConfig('memcache', configPrefix, configOverrides));
|
|
13
11
|
case 'memory':
|
|
14
12
|
default:
|
|
15
13
|
return new RateLimiterMemory(getConfig('memory', configPrefix, configOverrides));
|
|
@@ -19,12 +17,7 @@ function getConfig(store = 'memory', configPrefix = 'RATE_LIMITER', overrides) {
|
|
|
19
17
|
const config = getConfigFromEnv(`${configPrefix}_`, `${configPrefix}_${store}_`);
|
|
20
18
|
if (store === 'redis') {
|
|
21
19
|
const Redis = require('ioredis');
|
|
22
|
-
|
|
23
|
-
config.storeClient = new Redis(env[`${configPrefix}_REDIS`] || getConfigFromEnv(`${configPrefix}_REDIS_`));
|
|
24
|
-
}
|
|
25
|
-
if (store === 'memcache') {
|
|
26
|
-
const Memcached = require('memcached');
|
|
27
|
-
config.storeClient = new Memcached(env[`${configPrefix}_MEMCACHE`], getConfigFromEnv(`${configPrefix}_MEMCACHE_`));
|
|
20
|
+
config.storeClient = new Redis(env[`REDIS`] || getConfigFromEnv(`REDIS_`));
|
|
28
21
|
}
|
|
29
22
|
delete config.enabled;
|
|
30
23
|
delete config.store;
|
package/dist/server.js
CHANGED
|
@@ -10,6 +10,9 @@ import emitter from './emitter.js';
|
|
|
10
10
|
import env from './env.js';
|
|
11
11
|
import logger from './logger.js';
|
|
12
12
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
13
|
+
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
14
|
+
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
15
|
+
import { toBoolean } from './utils/to-boolean.js';
|
|
13
16
|
export let SERVER_ONLINE = true;
|
|
14
17
|
export async function createServer() {
|
|
15
18
|
const server = http.createServer(await createApp());
|
|
@@ -66,6 +69,11 @@ export async function createServer() {
|
|
|
66
69
|
res.once('finish', complete.bind(null, true));
|
|
67
70
|
res.once('close', complete.bind(null, false));
|
|
68
71
|
});
|
|
72
|
+
if (toBoolean(env['WEBSOCKETS_ENABLED']) === true) {
|
|
73
|
+
createSubscriptionController(server);
|
|
74
|
+
createWebSocketController(server);
|
|
75
|
+
startWebSocketHandlers();
|
|
76
|
+
}
|
|
69
77
|
const terminusOptions = {
|
|
70
78
|
timeout: env['SERVER_SHUTDOWN_TIMEOUT'] >= 0 && env['SERVER_SHUTDOWN_TIMEOUT'] < Infinity
|
|
71
79
|
? env['SERVER_SHUTDOWN_TIMEOUT']
|
|
@@ -84,6 +92,8 @@ export async function createServer() {
|
|
|
84
92
|
SERVER_ONLINE = false;
|
|
85
93
|
}
|
|
86
94
|
async function onSignal() {
|
|
95
|
+
getSubscriptionController()?.terminate();
|
|
96
|
+
getWebSocketController()?.terminate();
|
|
87
97
|
const database = getDatabase();
|
|
88
98
|
await database.destroy();
|
|
89
99
|
logger.info('Database connections destroyed');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
|
+
import { isDirectusError } from '@directus/errors';
|
|
2
3
|
import { uniq } from 'lodash-es';
|
|
3
4
|
import validateUUID from 'uuid-validate';
|
|
4
5
|
import env from '../env.js';
|
|
5
|
-
import {
|
|
6
|
+
import { ErrorCode } from '../errors/index.js';
|
|
6
7
|
import logger from '../logger.js';
|
|
7
8
|
import { getPermissions } from '../utils/get-permissions.js';
|
|
8
9
|
import { Url } from '../utils/url.js';
|
|
@@ -80,7 +81,7 @@ ${comment}
|
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
83
|
catch (err) {
|
|
83
|
-
if (err
|
|
84
|
+
if (isDirectusError(err, ErrorCode.Forbidden)) {
|
|
84
85
|
logger.warn(`User ${userID} doesn't have proper permissions to receive notification for this item.`);
|
|
85
86
|
}
|
|
86
87
|
else {
|
package/dist/services/assets.js
CHANGED
|
@@ -7,10 +7,7 @@ import validateUUID from 'uuid-validate';
|
|
|
7
7
|
import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
|
|
8
8
|
import getDatabase from '../database/index.js';
|
|
9
9
|
import env from '../env.js';
|
|
10
|
-
import {
|
|
11
|
-
import { IllegalAssetTransformation } from '../exceptions/illegal-asset-transformation.js';
|
|
12
|
-
import { RangeNotSatisfiableException } from '../exceptions/range-not-satisfiable.js';
|
|
13
|
-
import { ServiceUnavailableException } from '../exceptions/service-unavailable.js';
|
|
10
|
+
import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '../errors/index.js';
|
|
14
11
|
import logger from '../logger.js';
|
|
15
12
|
import { getStorage } from '../storage/index.js';
|
|
16
13
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
@@ -39,23 +36,23 @@ export class AssetsService {
|
|
|
39
36
|
*/
|
|
40
37
|
const isValidUUID = validateUUID(id, 4);
|
|
41
38
|
if (isValidUUID === false)
|
|
42
|
-
throw new
|
|
39
|
+
throw new ForbiddenError();
|
|
43
40
|
if (systemPublicKeys.includes(id) === false && this.accountability?.admin !== true) {
|
|
44
41
|
await this.authorizationService.checkAccess('read', 'directus_files', id);
|
|
45
42
|
}
|
|
46
43
|
const file = (await this.knex.select('*').from('directus_files').where({ id }).first());
|
|
47
44
|
if (!file)
|
|
48
|
-
throw new
|
|
45
|
+
throw new ForbiddenError();
|
|
49
46
|
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
50
47
|
if (!exists)
|
|
51
|
-
throw new
|
|
48
|
+
throw new ForbiddenError();
|
|
52
49
|
if (range) {
|
|
53
50
|
const missingRangeLimits = range.start === undefined && range.end === undefined;
|
|
54
51
|
const endBeforeStart = range.start !== undefined && range.end !== undefined && range.end <= range.start;
|
|
55
52
|
const startOverflow = range.start !== undefined && range.start >= file.filesize;
|
|
56
53
|
const endUnderflow = range.end !== undefined && range.end <= 0;
|
|
57
54
|
if (missingRangeLimits || endBeforeStart || startOverflow || endUnderflow) {
|
|
58
|
-
throw new
|
|
55
|
+
throw new RangeNotSatisfiableError({ range });
|
|
59
56
|
}
|
|
60
57
|
const lastByte = file.filesize - 1;
|
|
61
58
|
if (range.end) {
|
|
@@ -106,12 +103,14 @@ export class AssetsService {
|
|
|
106
103
|
!height ||
|
|
107
104
|
width > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] ||
|
|
108
105
|
height > env['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION']) {
|
|
109
|
-
|
|
106
|
+
logger.warn(`Image is too large to be transformed, or image size couldn't be determined.`);
|
|
107
|
+
throw new IllegalAssetTransformationError({ invalidTransformations: ['width', 'height'] });
|
|
110
108
|
}
|
|
111
109
|
const { queue, process } = sharp.counters();
|
|
112
110
|
if (queue + process > env['ASSETS_TRANSFORM_MAX_CONCURRENT']) {
|
|
113
|
-
throw new
|
|
111
|
+
throw new ServiceUnavailableError({
|
|
114
112
|
service: 'files',
|
|
113
|
+
reason: 'Server too busy',
|
|
115
114
|
});
|
|
116
115
|
}
|
|
117
116
|
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
|
|
@@ -7,7 +7,8 @@ import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
|
|
|
7
7
|
import getDatabase from '../database/index.js';
|
|
8
8
|
import emitter from '../emitter.js';
|
|
9
9
|
import env from '../env.js';
|
|
10
|
-
import {
|
|
10
|
+
import { InvalidCredentialsError, InvalidProviderError, UserSuspendedError } from '../errors/index.js';
|
|
11
|
+
import { InvalidOtpError } from '../errors/index.js';
|
|
11
12
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
12
13
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
13
14
|
import { stall } from '../utils/stall.js';
|
|
@@ -76,16 +77,16 @@ export class AuthenticationService {
|
|
|
76
77
|
emitStatus('fail');
|
|
77
78
|
if (user?.status === 'suspended') {
|
|
78
79
|
await stall(STALL_TIME, timeStart);
|
|
79
|
-
throw new
|
|
80
|
+
throw new UserSuspendedError();
|
|
80
81
|
}
|
|
81
82
|
else {
|
|
82
83
|
await stall(STALL_TIME, timeStart);
|
|
83
|
-
throw new
|
|
84
|
+
throw new InvalidCredentialsError();
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
else if (user.provider !== providerName) {
|
|
87
88
|
await stall(STALL_TIME, timeStart);
|
|
88
|
-
throw new
|
|
89
|
+
throw new InvalidProviderError();
|
|
89
90
|
}
|
|
90
91
|
const settingsService = new SettingsService({
|
|
91
92
|
knex: this.knex,
|
|
@@ -117,7 +118,7 @@ export class AuthenticationService {
|
|
|
117
118
|
if (user.tfa_secret && !otp) {
|
|
118
119
|
emitStatus('fail');
|
|
119
120
|
await stall(STALL_TIME, timeStart);
|
|
120
|
-
throw new
|
|
121
|
+
throw new InvalidOtpError();
|
|
121
122
|
}
|
|
122
123
|
if (user.tfa_secret && otp) {
|
|
123
124
|
const tfaService = new TFAService({ knex: this.knex, schema: this.schema });
|
|
@@ -125,7 +126,7 @@ export class AuthenticationService {
|
|
|
125
126
|
if (otpValid === false) {
|
|
126
127
|
emitStatus('fail');
|
|
127
128
|
await stall(STALL_TIME, timeStart);
|
|
128
|
-
throw new
|
|
129
|
+
throw new InvalidOtpError();
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
const tokenPayload = {
|
|
@@ -188,7 +189,7 @@ export class AuthenticationService {
|
|
|
188
189
|
const STALL_TIME = env['LOGIN_STALL_TIME'];
|
|
189
190
|
const timeStart = performance.now();
|
|
190
191
|
if (!refreshToken) {
|
|
191
|
-
throw new
|
|
192
|
+
throw new InvalidCredentialsError();
|
|
192
193
|
}
|
|
193
194
|
const record = await this.knex
|
|
194
195
|
.select({
|
|
@@ -230,17 +231,17 @@ export class AuthenticationService {
|
|
|
230
231
|
})
|
|
231
232
|
.first();
|
|
232
233
|
if (!record || (!record.share_id && !record.user_id)) {
|
|
233
|
-
throw new
|
|
234
|
+
throw new InvalidCredentialsError();
|
|
234
235
|
}
|
|
235
236
|
if (record.user_id && record.user_status !== 'active') {
|
|
236
237
|
await this.knex('directus_sessions').where({ token: refreshToken }).del();
|
|
237
238
|
if (record.user_status === 'suspended') {
|
|
238
239
|
await stall(STALL_TIME, timeStart);
|
|
239
|
-
throw new
|
|
240
|
+
throw new UserSuspendedError();
|
|
240
241
|
}
|
|
241
242
|
else {
|
|
242
243
|
await stall(STALL_TIME, timeStart);
|
|
243
|
-
throw new
|
|
244
|
+
throw new InvalidCredentialsError();
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
if (record.user_id) {
|
|
@@ -330,7 +331,7 @@ export class AuthenticationService {
|
|
|
330
331
|
.where('id', userID)
|
|
331
332
|
.first();
|
|
332
333
|
if (!user) {
|
|
333
|
-
throw new
|
|
334
|
+
throw new InvalidCredentialsError();
|
|
334
335
|
}
|
|
335
336
|
const provider = getAuthProvider(user.provider);
|
|
336
337
|
await provider.verify(clone(user), password);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Accountability, PermissionsAction, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
|
-
import type {
|
|
3
|
+
import type { AST, AbstractServiceOptions, Item, PrimaryKey } from '../types/index.js';
|
|
4
4
|
import { PayloadService } from './payload.js';
|
|
5
5
|
export declare class AuthorizationService {
|
|
6
6
|
knex: Knex;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { FailedValidationException } from '@directus/exceptions';
|
|
2
1
|
import { validatePayload } from '@directus/utils';
|
|
2
|
+
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
3
3
|
import { cloneDeep, flatten, isArray, isNil, merge, reduce, uniq, uniqWith } from 'lodash-es';
|
|
4
4
|
import { GENERATE_SPECIAL } from '../constants.js';
|
|
5
5
|
import getDatabase from '../database/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ForbiddenError } from '../errors/forbidden.js';
|
|
7
7
|
import { getRelationInfo } from '../utils/get-relation-info.js';
|
|
8
8
|
import { stripFunction } from '../utils/strip-function.js';
|
|
9
9
|
import { ItemsService } from './items.js';
|
|
@@ -31,7 +31,7 @@ export class AuthorizationService {
|
|
|
31
31
|
// If the permissions don't match the collections, you don't have permission to read all of them
|
|
32
32
|
const uniqueCollectionsRequestedCount = uniq(collectionsRequested.map(({ collection }) => collection)).length;
|
|
33
33
|
if (uniqueCollectionsRequestedCount !== permissionsForCollections.length) {
|
|
34
|
-
throw new
|
|
34
|
+
throw new ForbiddenError();
|
|
35
35
|
}
|
|
36
36
|
validateFields(ast);
|
|
37
37
|
validateFilterPermissions(ast, this.schema, action, this.accountability);
|
|
@@ -94,7 +94,7 @@ export class AuthorizationService {
|
|
|
94
94
|
if (column === '*')
|
|
95
95
|
continue;
|
|
96
96
|
if (allowedFields.includes(column) === false)
|
|
97
|
-
throw new
|
|
97
|
+
throw new ForbiddenError();
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
}
|
|
@@ -107,7 +107,7 @@ export class AuthorizationService {
|
|
|
107
107
|
continue;
|
|
108
108
|
const fieldKey = stripFunction(childNode.name);
|
|
109
109
|
if (allowedFields.includes(fieldKey) === false) {
|
|
110
|
-
throw new
|
|
110
|
+
throw new ForbiddenError();
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -189,7 +189,7 @@ export class AuthorizationService {
|
|
|
189
189
|
});
|
|
190
190
|
// Filter key not found in parent collection
|
|
191
191
|
if (!relation)
|
|
192
|
-
throw new
|
|
192
|
+
throw new ForbiddenError();
|
|
193
193
|
const relatedCollectionName = relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
|
|
194
194
|
// Add the `item` field to the required permissions
|
|
195
195
|
(result[relatedCollectionName] || (result[relatedCollectionName] = new Set())).add(field);
|
|
@@ -211,7 +211,7 @@ export class AuthorizationService {
|
|
|
211
211
|
});
|
|
212
212
|
// Filter key not found in parent collection
|
|
213
213
|
if (!relation)
|
|
214
|
-
throw new
|
|
214
|
+
throw new ForbiddenError();
|
|
215
215
|
parentCollection =
|
|
216
216
|
relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
|
|
217
217
|
(result[parentCollection] || (result[parentCollection] = new Set())).add(filterKey);
|
|
@@ -262,14 +262,14 @@ export class AuthorizationService {
|
|
|
262
262
|
if (action !== 'read' && collection === rootCollection) {
|
|
263
263
|
const actionPermission = accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === action);
|
|
264
264
|
if (!actionPermission || !actionPermission.fields) {
|
|
265
|
-
throw new
|
|
265
|
+
throw new ForbiddenError();
|
|
266
266
|
}
|
|
267
267
|
allowedFields = permission?.fields
|
|
268
268
|
? [...permission.fields, schema.collections[collection].primary]
|
|
269
269
|
: [schema.collections[collection].primary];
|
|
270
270
|
}
|
|
271
271
|
else if (!permission || !permission.fields) {
|
|
272
|
-
throw new
|
|
272
|
+
throw new ForbiddenError();
|
|
273
273
|
}
|
|
274
274
|
else {
|
|
275
275
|
allowedFields = permission.fields;
|
|
@@ -288,7 +288,7 @@ export class AuthorizationService {
|
|
|
288
288
|
originalFieldName = aliasMap[fieldName];
|
|
289
289
|
}
|
|
290
290
|
if (!allowedFields.includes(originalFieldName)) {
|
|
291
|
-
throw new
|
|
291
|
+
throw new ForbiddenError();
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
}
|
|
@@ -356,14 +356,14 @@ export class AuthorizationService {
|
|
|
356
356
|
return permission.collection === collection && permission.action === action;
|
|
357
357
|
});
|
|
358
358
|
if (!permission)
|
|
359
|
-
throw new
|
|
359
|
+
throw new ForbiddenError();
|
|
360
360
|
// Check if you have permission to access the fields you're trying to access
|
|
361
361
|
const allowedFields = permission.fields || [];
|
|
362
362
|
if (allowedFields.includes('*') === false) {
|
|
363
363
|
const keysInData = Object.keys(payload);
|
|
364
364
|
const invalidKeys = keysInData.filter((fieldKey) => allowedFields.includes(fieldKey) === false);
|
|
365
365
|
if (invalidKeys.length > 0) {
|
|
366
|
-
throw new
|
|
366
|
+
throw new ForbiddenError();
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
}
|
|
@@ -412,7 +412,7 @@ export class AuthorizationService {
|
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
const validationErrors = [];
|
|
415
|
-
validationErrors.push(...flatten(validatePayload(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new
|
|
415
|
+
validationErrors.push(...flatten(validatePayload(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))));
|
|
416
416
|
if (validationErrors.length > 0)
|
|
417
417
|
throw validationErrors;
|
|
418
418
|
return payloadWithPresets;
|
|
@@ -431,14 +431,14 @@ export class AuthorizationService {
|
|
|
431
431
|
if (Array.isArray(pk)) {
|
|
432
432
|
const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
|
|
433
433
|
if (!result)
|
|
434
|
-
throw new
|
|
434
|
+
throw new ForbiddenError();
|
|
435
435
|
if (result.length !== pk.length)
|
|
436
|
-
throw new
|
|
436
|
+
throw new ForbiddenError();
|
|
437
437
|
}
|
|
438
438
|
else {
|
|
439
439
|
const result = await itemsService.readOne(pk, query, { permissionsAction: action });
|
|
440
440
|
if (!result)
|
|
441
|
-
throw new
|
|
441
|
+
throw new ForbiddenError();
|
|
442
442
|
}
|
|
443
443
|
}
|
|
444
444
|
}
|