@directus/api 11.1.0 → 12.0.1
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 +34 -18
- package/dist/services/fields.js +24 -16
- 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 -4
- 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
|
@@ -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;
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInspector } from '@directus/schema';
|
|
2
2
|
import { addFieldFlag } from '@directus/utils';
|
|
3
|
-
import { chunk, omit } from 'lodash-es';
|
|
3
|
+
import { chunk, groupBy, merge, omit } from 'lodash-es';
|
|
4
4
|
import { clearSystemCache, getCache } from '../cache.js';
|
|
5
5
|
import { ALIAS_TYPES } from '../constants.js';
|
|
6
6
|
import { getHelpers } from '../database/helpers/index.js';
|
|
@@ -8,7 +8,7 @@ import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
|
8
8
|
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
9
9
|
import emitter from '../emitter.js';
|
|
10
10
|
import env from '../env.js';
|
|
11
|
-
import {
|
|
11
|
+
import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
|
|
12
12
|
import { FieldsService } from '../services/fields.js';
|
|
13
13
|
import { ItemsService } from '../services/items.js';
|
|
14
14
|
import { getSchema } from '../utils/get-schema.js';
|
|
@@ -36,12 +36,12 @@ export class CollectionsService {
|
|
|
36
36
|
*/
|
|
37
37
|
async createOne(payload, opts) {
|
|
38
38
|
if (this.accountability && this.accountability.admin !== true) {
|
|
39
|
-
throw new
|
|
39
|
+
throw new ForbiddenError();
|
|
40
40
|
}
|
|
41
41
|
if (!payload.collection)
|
|
42
|
-
throw new
|
|
42
|
+
throw new InvalidPayloadError({ reason: `"collection" is required` });
|
|
43
43
|
if (payload.collection.startsWith('directus_')) {
|
|
44
|
-
throw new
|
|
44
|
+
throw new InvalidPayloadError({ reason: `Collections can't start with "directus_"` });
|
|
45
45
|
}
|
|
46
46
|
const nestedActionEvents = [];
|
|
47
47
|
try {
|
|
@@ -51,7 +51,7 @@ export class CollectionsService {
|
|
|
51
51
|
...Object.keys(this.schema.collections),
|
|
52
52
|
];
|
|
53
53
|
if (existingCollections.includes(payload.collection)) {
|
|
54
|
-
throw new
|
|
54
|
+
throw new InvalidPayloadError({ reason: `Collection "${payload.collection}" already exists` });
|
|
55
55
|
}
|
|
56
56
|
// Create the collection/fields in a transaction so it'll be reverted in case of errors or
|
|
57
57
|
// permission problems. This might not work reliably in MySQL, as it doesn't support DDL in
|
|
@@ -108,7 +108,22 @@ export class CollectionsService {
|
|
|
108
108
|
schema: this.schema,
|
|
109
109
|
});
|
|
110
110
|
const fieldPayloads = payload.fields.filter((field) => field.meta).map((field) => field.meta);
|
|
111
|
-
|
|
111
|
+
// Sort new fields that does not have any group defined, in ascending order.
|
|
112
|
+
// Lodash merge is used so that the "sort" can be overridden if defined.
|
|
113
|
+
let sortedFieldPayloads = fieldPayloads
|
|
114
|
+
.filter((field) => field?.group === undefined || field?.group === null)
|
|
115
|
+
.map((field, index) => merge({ sort: index + 1 }, field));
|
|
116
|
+
// Sort remaining new fields with group defined, if any, in ascending order.
|
|
117
|
+
// sortedFieldPayloads will be less than fieldPayloads if it filtered out any fields with group defined.
|
|
118
|
+
if (sortedFieldPayloads.length < fieldPayloads.length) {
|
|
119
|
+
const fieldsWithGroups = groupBy(fieldPayloads.filter((field) => field?.group), (field) => field?.group);
|
|
120
|
+
// The sort order is restarted from 1 for fields in each group and appended to sortedFieldPayloads.
|
|
121
|
+
// Lodash merge is used so that the "sort" can be overridden if defined.
|
|
122
|
+
for (const [_group, fields] of Object.entries(fieldsWithGroups)) {
|
|
123
|
+
sortedFieldPayloads = sortedFieldPayloads.concat(fields.map((field, index) => merge({ sort: index + 1 }, field)));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await fieldItemsService.createMany(sortedFieldPayloads, {
|
|
112
127
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
113
128
|
bypassLimits: true,
|
|
114
129
|
});
|
|
@@ -255,7 +270,7 @@ export class CollectionsService {
|
|
|
255
270
|
async readOne(collectionKey) {
|
|
256
271
|
const result = await this.readMany([collectionKey]);
|
|
257
272
|
if (result.length === 0)
|
|
258
|
-
throw new
|
|
273
|
+
throw new ForbiddenError();
|
|
259
274
|
return result[0];
|
|
260
275
|
}
|
|
261
276
|
/**
|
|
@@ -270,7 +285,7 @@ export class CollectionsService {
|
|
|
270
285
|
const collectionsYouHavePermissionToRead = permissions.map(({ collection }) => collection);
|
|
271
286
|
for (const collectionKey of collectionKeys) {
|
|
272
287
|
if (collectionsYouHavePermissionToRead.includes(collectionKey) === false) {
|
|
273
|
-
throw new
|
|
288
|
+
throw new ForbiddenError();
|
|
274
289
|
}
|
|
275
290
|
}
|
|
276
291
|
}
|
|
@@ -283,7 +298,7 @@ export class CollectionsService {
|
|
|
283
298
|
*/
|
|
284
299
|
async updateOne(collectionKey, data, opts) {
|
|
285
300
|
if (this.accountability && this.accountability.admin !== true) {
|
|
286
|
-
throw new
|
|
301
|
+
throw new ForbiddenError();
|
|
287
302
|
}
|
|
288
303
|
const nestedActionEvents = [];
|
|
289
304
|
try {
|
|
@@ -336,10 +351,10 @@ export class CollectionsService {
|
|
|
336
351
|
*/
|
|
337
352
|
async updateBatch(data, opts) {
|
|
338
353
|
if (this.accountability && this.accountability.admin !== true) {
|
|
339
|
-
throw new
|
|
354
|
+
throw new ForbiddenError();
|
|
340
355
|
}
|
|
341
356
|
if (!Array.isArray(data)) {
|
|
342
|
-
throw new
|
|
357
|
+
throw new InvalidPayloadError({ reason: 'Input should be an array of collection changes' });
|
|
343
358
|
}
|
|
344
359
|
const collectionKey = 'collection';
|
|
345
360
|
const collectionKeys = [];
|
|
@@ -352,8 +367,9 @@ export class CollectionsService {
|
|
|
352
367
|
schema: this.schema,
|
|
353
368
|
});
|
|
354
369
|
for (const payload of data) {
|
|
355
|
-
if (!payload[collectionKey])
|
|
356
|
-
throw new
|
|
370
|
+
if (!payload[collectionKey]) {
|
|
371
|
+
throw new InvalidPayloadError({ reason: `Collection in update misses collection key` });
|
|
372
|
+
}
|
|
357
373
|
await collectionItemsService.updateOne(payload[collectionKey], omit(payload, collectionKey), {
|
|
358
374
|
autoPurgeCache: false,
|
|
359
375
|
autoPurgeSystemCache: false,
|
|
@@ -385,7 +401,7 @@ export class CollectionsService {
|
|
|
385
401
|
*/
|
|
386
402
|
async updateMany(collectionKeys, data, opts) {
|
|
387
403
|
if (this.accountability && this.accountability.admin !== true) {
|
|
388
|
-
throw new
|
|
404
|
+
throw new ForbiddenError();
|
|
389
405
|
}
|
|
390
406
|
const nestedActionEvents = [];
|
|
391
407
|
try {
|
|
@@ -427,14 +443,14 @@ export class CollectionsService {
|
|
|
427
443
|
*/
|
|
428
444
|
async deleteOne(collectionKey, opts) {
|
|
429
445
|
if (this.accountability && this.accountability.admin !== true) {
|
|
430
|
-
throw new
|
|
446
|
+
throw new ForbiddenError();
|
|
431
447
|
}
|
|
432
448
|
const nestedActionEvents = [];
|
|
433
449
|
try {
|
|
434
450
|
const collections = await this.readByQuery();
|
|
435
451
|
const collectionToBeDeleted = collections.find((collection) => collection.collection === collectionKey);
|
|
436
452
|
if (!!collectionToBeDeleted === false) {
|
|
437
|
-
throw new
|
|
453
|
+
throw new ForbiddenError();
|
|
438
454
|
}
|
|
439
455
|
await this.knex.transaction(async (trx) => {
|
|
440
456
|
if (collectionToBeDeleted.schema) {
|
|
@@ -531,7 +547,7 @@ export class CollectionsService {
|
|
|
531
547
|
*/
|
|
532
548
|
async deleteMany(collectionKeys, opts) {
|
|
533
549
|
if (this.accountability && this.accountability.admin !== true) {
|
|
534
|
-
throw new
|
|
550
|
+
throw new ForbiddenError();
|
|
535
551
|
}
|
|
536
552
|
const nestedActionEvents = [];
|
|
537
553
|
try {
|