@directus/api 32.2.0 → 33.1.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/ai/chat/controllers/chat.post.js +19 -4
- package/dist/ai/chat/lib/create-ui-stream.d.ts +8 -7
- package/dist/ai/chat/lib/create-ui-stream.js +28 -25
- package/dist/ai/chat/middleware/load-settings.js +31 -7
- package/dist/ai/chat/models/chat-request.d.ts +135 -2
- package/dist/ai/chat/models/chat-request.js +56 -2
- package/dist/ai/chat/models/providers.d.ts +16 -2
- package/dist/ai/chat/models/providers.js +16 -2
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +3 -4
- package/dist/ai/chat/utils/format-context.d.ts +5 -0
- package/dist/ai/chat/utils/format-context.js +122 -0
- package/dist/ai/mcp/server.d.ts +27 -1
- package/dist/ai/providers/index.d.ts +3 -0
- package/dist/ai/providers/index.js +3 -0
- package/dist/ai/providers/options.d.ts +14 -0
- package/dist/ai/providers/options.js +26 -0
- package/dist/ai/providers/registry.d.ts +6 -0
- package/dist/ai/providers/registry.js +65 -0
- package/dist/ai/providers/types.d.ts +34 -0
- package/dist/ai/providers/types.js +1 -0
- package/dist/ai/tools/assets/index.js +1 -1
- package/dist/ai/tools/collections/index.js +2 -2
- package/dist/ai/tools/fields/index.js +2 -2
- package/dist/ai/tools/files/index.js +1 -1
- package/dist/ai/tools/flows/index.js +1 -1
- package/dist/ai/tools/folders/index.js +1 -1
- package/dist/ai/tools/items/index.js +6 -3
- package/dist/ai/tools/items/prompt.md +7 -9
- package/dist/ai/tools/relations/index.js +1 -1
- package/dist/ai/tools/schema.js +1 -1
- package/dist/ai/tools/trigger-flow/index.js +1 -1
- package/dist/app.js +12 -8
- package/dist/auth/drivers/ldap.d.ts +1 -1
- package/dist/auth/drivers/ldap.js +144 -139
- package/dist/auth/drivers/local.js +1 -1
- package/dist/auth/drivers/oauth2.d.ts +1 -2
- package/dist/auth/drivers/oauth2.js +22 -17
- package/dist/auth/drivers/openid.d.ts +1 -2
- package/dist/auth/drivers/openid.js +18 -13
- package/dist/auth/drivers/saml.js +3 -3
- package/dist/auth/utils/generate-callback-url.d.ts +11 -0
- package/dist/auth/utils/generate-callback-url.js +40 -0
- package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
- package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +27 -3
- package/dist/cli/commands/bootstrap/index.js +2 -2
- package/dist/cli/commands/database/install.js +1 -1
- package/dist/cli/commands/database/migrate.js +1 -1
- package/dist/cli/commands/init/index.js +2 -2
- package/dist/cli/commands/roles/create.js +4 -4
- package/dist/cli/commands/schema/apply.js +3 -3
- package/dist/cli/commands/schema/snapshot.js +1 -1
- package/dist/cli/utils/create-db-connection.d.ts +1 -1
- package/dist/cli/utils/create-db-connection.js +1 -1
- package/dist/cli/utils/create-env/env-stub.liquid +3 -0
- package/dist/cli/utils/create-env/index.js +1 -1
- package/dist/constants.d.ts +7 -3
- package/dist/constants.js +7 -3
- package/dist/controllers/access.js +1 -1
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/deployment.js +481 -0
- package/dist/controllers/extensions.js +1 -1
- package/dist/controllers/fields.js +8 -6
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/items.js +1 -1
- package/dist/controllers/not-found.js +1 -1
- package/dist/controllers/relations.js +1 -1
- package/dist/database/errors/dialects/mysql.d.ts +1 -1
- package/dist/database/errors/dialects/postgres.d.ts +1 -1
- package/dist/database/errors/dialects/sqlite.d.ts +1 -1
- package/dist/database/errors/translate.d.ts +1 -1
- package/dist/database/errors/translate.js +1 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
- package/dist/database/helpers/date/dialects/mssql.js +1 -1
- package/dist/database/helpers/date/dialects/mysql.js +1 -1
- package/dist/database/helpers/date/types.js +1 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
- package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +16 -3
- package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
- package/dist/database/helpers/schema/types.d.ts +13 -0
- package/dist/database/helpers/schema/types.js +24 -0
- package/dist/database/index.js +4 -4
- package/dist/database/migrations/20220429A-add-flows.js +1 -1
- package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
- package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
- package/dist/database/migrations/20240204A-marketplace.js +9 -7
- package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
- package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
- package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
- package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
- package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
- package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.d.ts +3 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.js +35 -0
- package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
- package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
- package/dist/database/migrations/20260128A-add-collaborative-editing.d.ts +3 -0
- package/dist/database/migrations/20260128A-add-collaborative-editing.js +10 -0
- package/dist/database/migrations/20260204A-add-deployment.d.ts +3 -0
- package/dist/database/migrations/20260204A-add-deployment.js +32 -0
- package/dist/database/migrations/run.js +3 -3
- package/dist/database/run-ast/lib/apply-query/add-join.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/index.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
- package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
- package/dist/database/run-ast/utils/get-column.js +1 -1
- package/dist/database/seeds/run.js +3 -3
- package/dist/deployment/deployment.d.ts +94 -0
- package/dist/deployment/deployment.js +29 -0
- package/dist/deployment/drivers/index.d.ts +1 -0
- package/dist/deployment/drivers/index.js +1 -0
- package/dist/deployment/drivers/vercel.d.ts +32 -0
- package/dist/deployment/drivers/vercel.js +208 -0
- package/dist/deployment/index.d.ts +2 -0
- package/dist/deployment/index.js +2 -0
- package/dist/deployment.d.ts +24 -0
- package/dist/deployment.js +39 -0
- package/dist/extensions/lib/get-extensions-path.js +1 -1
- package/dist/extensions/lib/get-extensions-settings.js +1 -1
- package/dist/extensions/lib/get-extensions.js +1 -1
- package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
- package/dist/extensions/lib/installation/manager.js +3 -3
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
- package/dist/extensions/lib/sync/status.js +1 -1
- package/dist/extensions/lib/sync/sync.js +7 -7
- package/dist/extensions/lib/sync/utils.js +2 -2
- package/dist/extensions/manager.d.ts +1 -1
- package/dist/extensions/manager.js +8 -8
- package/dist/flows.d.ts +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/logs-stream.d.ts +1 -1
- package/dist/logger/logs-stream.js +1 -1
- package/dist/mailer.js +1 -1
- package/dist/metrics/lib/create-metrics.js +2 -2
- package/dist/middleware/authenticate.js +3 -3
- package/dist/middleware/collection-exists.js +1 -1
- package/dist/middleware/extract-token.js +1 -1
- package/dist/middleware/graphql.js +2 -2
- package/dist/middleware/respond.js +27 -14
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/exec/index.js +2 -1
- package/dist/operations/mail/index.js +1 -1
- package/dist/operations/mail/rate-limiter.js +2 -2
- package/dist/permissions/cache.js +5 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
- package/dist/permissions/modules/process-ast/process-ast.js +1 -1
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +1 -1
- package/dist/permissions/modules/process-payload/process-payload.js +1 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +14 -2
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +72 -13
- package/dist/permissions/modules/validate-access/validate-access.js +3 -2
- package/dist/rate-limiter.js +1 -1
- package/dist/request/is-denied-ip.js +1 -1
- package/dist/schedules/project.js +1 -1
- package/dist/schedules/telemetry.js +1 -1
- package/dist/schedules/tus.js +1 -1
- package/dist/server.js +6 -5
- package/dist/services/assets.d.ts +2 -1
- package/dist/services/assets.js +35 -8
- package/dist/services/authentication.js +2 -2
- package/dist/services/collections.js +1 -1
- package/dist/services/deployment-projects.d.ts +20 -0
- package/dist/services/deployment-projects.js +34 -0
- package/dist/services/deployment-runs.d.ts +13 -0
- package/dist/services/deployment-runs.js +6 -0
- package/dist/services/deployment.d.ts +40 -0
- package/dist/services/deployment.js +202 -0
- package/dist/services/extensions.d.ts +1 -1
- package/dist/services/files/utils/get-metadata.d.ts +1 -1
- package/dist/services/files/utils/get-metadata.js +1 -1
- package/dist/services/files.d.ts +1 -1
- package/dist/services/files.js +4 -4
- package/dist/services/graphql/index.d.ts +1 -1
- package/dist/services/graphql/index.js +1 -1
- package/dist/services/graphql/resolvers/mutation.js +1 -1
- package/dist/services/graphql/resolvers/system-admin.js +2 -3
- package/dist/services/graphql/schema/get-types.d.ts +1 -1
- package/dist/services/graphql/schema/read.js +1 -1
- package/dist/services/graphql/subscription.d.ts +1 -1
- package/dist/services/graphql/types/date.js +1 -1
- package/dist/services/graphql/types/hash.js +1 -1
- package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
- package/dist/services/graphql/utils/filter-replace-m2a.js +3 -4
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +2 -2
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.js +3 -1
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/rate-limiter.js +2 -2
- package/dist/services/payload.js +2 -2
- package/dist/services/schema.js +1 -1
- package/dist/services/server.js +13 -4
- package/dist/services/settings.js +2 -2
- package/dist/services/specifications.js +2 -2
- package/dist/services/tfa.js +1 -1
- package/dist/services/translations.js +1 -1
- package/dist/services/tus/data-store.d.ts +1 -3
- package/dist/services/tus/data-store.js +2 -5
- package/dist/services/tus/server.js +6 -6
- package/dist/services/users.js +4 -4
- package/dist/services/versions.js +1 -1
- package/dist/telemetry/lib/get-report.js +2 -0
- package/dist/telemetry/lib/send-report.d.ts +1 -1
- package/dist/telemetry/lib/send-report.js +1 -1
- package/dist/telemetry/lib/track.js +1 -1
- package/dist/telemetry/types/report.d.ts +8 -0
- package/dist/telemetry/utils/get-settings.d.ts +2 -0
- package/dist/telemetry/utils/get-settings.js +5 -0
- package/dist/test-utils/knex.js +1 -1
- package/dist/types/collection.d.ts +1 -1
- package/dist/utils/async-handler.d.ts +1 -1
- package/dist/utils/calculate-field-depth.js +1 -1
- package/dist/utils/compress.js +1 -1
- package/dist/utils/deep-map-response.d.ts +1 -1
- package/dist/utils/deep-map-response.js +2 -2
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-column-path.js +1 -1
- package/dist/utils/get-field-system-rows.js +1 -1
- package/dist/utils/get-ip-from-req.d.ts +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-local-type.js +7 -3
- package/dist/utils/get-service.js +7 -3
- package/dist/utils/get-snapshot-diff.js +1 -1
- package/dist/utils/is-field-allowed.d.ts +4 -0
- package/dist/utils/is-field-allowed.js +9 -0
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-clear-cache.d.ts +1 -1
- package/dist/utils/should-skip-cache.js +2 -2
- package/dist/utils/validate-diff.js +1 -1
- package/dist/utils/validate-snapshot.js +3 -3
- package/dist/utils/validate-storage.js +2 -2
- package/dist/utils/verify-session-jwt.js +1 -1
- package/dist/utils/versioning/handle-version.js +1 -1
- package/dist/websocket/collab/calculate-cache-metadata.d.ts +9 -0
- package/dist/websocket/collab/calculate-cache-metadata.js +121 -0
- package/dist/websocket/collab/collab.d.ts +63 -0
- package/dist/websocket/collab/collab.js +481 -0
- package/dist/websocket/collab/constants.d.ts +1 -0
- package/dist/websocket/collab/constants.js +13 -0
- package/dist/websocket/collab/filter-to-fields.d.ts +2 -0
- package/dist/websocket/collab/filter-to-fields.js +11 -0
- package/dist/websocket/collab/messenger.d.ts +43 -0
- package/dist/websocket/collab/messenger.js +225 -0
- package/dist/websocket/collab/payload-permissions.d.ts +18 -0
- package/dist/websocket/collab/payload-permissions.js +158 -0
- package/dist/websocket/collab/permissions-cache.d.ts +52 -0
- package/dist/websocket/collab/permissions-cache.js +204 -0
- package/dist/websocket/collab/room.d.ts +125 -0
- package/dist/websocket/collab/room.js +593 -0
- package/dist/websocket/collab/store.d.ts +7 -0
- package/dist/websocket/collab/store.js +33 -0
- package/dist/websocket/collab/types.d.ts +21 -0
- package/dist/websocket/collab/types.js +1 -0
- package/dist/websocket/collab/verify-permissions.d.ts +11 -0
- package/dist/websocket/collab/verify-permissions.js +100 -0
- package/dist/websocket/controllers/base.d.ts +2 -2
- package/dist/websocket/controllers/base.js +3 -3
- package/dist/websocket/controllers/graphql.d.ts +1 -1
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/controllers/logs.d.ts +1 -1
- package/dist/websocket/controllers/rest.d.ts +1 -1
- package/dist/websocket/controllers/rest.js +2 -2
- package/dist/websocket/handlers/heartbeat.js +1 -1
- package/dist/websocket/handlers/index.d.ts +2 -0
- package/dist/websocket/handlers/index.js +9 -0
- package/dist/websocket/handlers/items.js +2 -2
- package/dist/websocket/handlers/subscribe.js +1 -1
- package/dist/websocket/types.d.ts +1 -1
- package/dist/websocket/utils/items.d.ts +2 -2
- package/dist/websocket/utils/message.d.ts +1 -1
- package/dist/websocket/utils/message.js +2 -2
- package/dist/websocket/utils/wait-for-message.js +1 -1
- package/package.json +35 -33
- package/dist/controllers/webhooks.js +0 -74
- package/dist/services/webhooks.d.ts +0 -14
- package/dist/services/webhooks.js +0 -32
- package/dist/utils/get-relation-info.d.ts +0 -6
- package/dist/utils/get-relation-info.js +0 -43
- package/dist/utils/get-relation-type.d.ts +0 -6
- package/dist/utils/get-relation-type.js +0 -18
- package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
- package/dist/utils/versioning/deep-map-with-schema.d.ts +0 -23
- package/dist/utils/versioning/deep-map-with-schema.js +0 -81
- /package/dist/controllers/{webhooks.d.ts → deployment.d.ts} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useEnv } from '@directus/env';
|
|
2
|
-
import { toArray } from '@directus/utils';
|
|
3
1
|
import { randomUUID } from 'node:crypto';
|
|
4
2
|
import { Readable } from 'node:stream';
|
|
5
3
|
import { promisify } from 'node:util';
|
|
4
|
+
import { useEnv } from '@directus/env';
|
|
5
|
+
import { toArray } from '@directus/utils';
|
|
6
6
|
import pm2 from 'pm2';
|
|
7
7
|
import { AggregatorRegistry, Counter, Histogram, register } from 'prom-client';
|
|
8
8
|
import { getCache } from '../../cache.js';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
1
3
|
import { isEqual } from 'lodash-es';
|
|
4
|
+
import { SESSION_COOKIE_OPTIONS } from '../constants.js';
|
|
2
5
|
import getDatabase from '../database/index.js';
|
|
3
6
|
import emitter from '../emitter.js';
|
|
4
7
|
import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
|
|
5
8
|
import asyncHandler from '../utils/async-handler.js';
|
|
6
9
|
import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
|
|
7
10
|
import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
8
|
-
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
9
|
-
import { useEnv } from '@directus/env';
|
|
10
|
-
import { SESSION_COOKIE_OPTIONS } from '../constants.js';
|
|
11
11
|
/**
|
|
12
12
|
* Verify the passed JWT and assign the user ID and role to `req`
|
|
13
13
|
*/
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Check if requested collection exists, and save it to req.collection
|
|
3
3
|
*/
|
|
4
4
|
import { systemCollectionRows } from '@directus/system-data';
|
|
5
|
-
import asyncHandler from '../utils/async-handler.js';
|
|
6
5
|
import { createCollectionForbiddenError } from '../permissions/modules/process-ast/utils/validate-path/create-error.js';
|
|
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();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { InvalidPayloadError, InvalidQueryError, MethodNotAllowedError } from '@directus/errors';
|
|
1
3
|
import { parseJSON } from '@directus/utils';
|
|
2
4
|
import { getOperationAST, parse, Source } from 'graphql';
|
|
3
|
-
import { InvalidPayloadError, InvalidQueryError, MethodNotAllowedError } from '@directus/errors';
|
|
4
5
|
import { GraphQLValidationError } from '../services/graphql/errors/validation.js';
|
|
5
6
|
import asyncHandler from '../utils/async-handler.js';
|
|
6
|
-
import { useEnv } from '@directus/env';
|
|
7
7
|
export const parseGraphQL = asyncHandler(async (req, res, next) => {
|
|
8
8
|
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
9
9
|
throw new MethodNotAllowedError({ allowed: ['GET', 'POST'], current: req.method });
|
|
@@ -15,6 +15,10 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
15
15
|
const env = useEnv();
|
|
16
16
|
const logger = useLogger();
|
|
17
17
|
const { cache } = getCache();
|
|
18
|
+
// Support custom cache instance and TTL via res.locals
|
|
19
|
+
const cacheInstance = res.locals['cacheInstance'] || cache;
|
|
20
|
+
const cacheTTL = res.locals['cacheTTL'] ?? getMilliseconds(env['CACHE_TTL']);
|
|
21
|
+
const hasCustomCache = !!res.locals['cacheInstance'];
|
|
18
22
|
let exceedsMaxSize = false;
|
|
19
23
|
if (env['CACHE_VALUE_MAX_SIZE'] !== false) {
|
|
20
24
|
const valueSize = res.locals['payload'] ? stringByteSize(JSON.stringify(res.locals['payload'])) : 0;
|
|
@@ -22,26 +26,35 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
22
26
|
if (maxSize !== null)
|
|
23
27
|
exceedsMaxSize = valueSize > maxSize;
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
// Custom cache bypasses global cache settings (CACHE_ENABLED, permissionsCacheable)
|
|
30
|
+
const shouldCache = hasCustomCache
|
|
31
|
+
? res.locals['cache'] !== false && cacheInstance && !req.sanitizedQuery.export && exceedsMaxSize === false
|
|
32
|
+
: (req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
|
|
33
|
+
req.originalUrl?.startsWith('/auth') === false &&
|
|
34
|
+
env['CACHE_ENABLED'] === true &&
|
|
35
|
+
cache &&
|
|
36
|
+
!req.sanitizedQuery.export &&
|
|
37
|
+
res.locals['cache'] !== false &&
|
|
38
|
+
exceedsMaxSize === false &&
|
|
39
|
+
(await permissionsCacheable(req.collection, {
|
|
40
|
+
knex: getDatabase(),
|
|
41
|
+
schema: req.schema,
|
|
42
|
+
}, req.accountability));
|
|
43
|
+
if (shouldCache) {
|
|
36
44
|
const key = await getCacheKey(req);
|
|
37
45
|
try {
|
|
38
|
-
await setCacheValue(
|
|
39
|
-
await setCacheValue(
|
|
46
|
+
await setCacheValue(cacheInstance, key, res.locals['payload'], cacheTTL);
|
|
47
|
+
await setCacheValue(cacheInstance, `${key}__expires_at`, { exp: Date.now() + cacheTTL });
|
|
40
48
|
}
|
|
41
49
|
catch (err) {
|
|
42
50
|
logger.warn(err, `[cache] Couldn't set key ${key}. ${err}`);
|
|
43
51
|
}
|
|
44
|
-
res.setHeader('Cache-Control', getCacheControlHeader(req,
|
|
52
|
+
res.setHeader('Cache-Control', getCacheControlHeader(req, cacheTTL, !hasCustomCache, true));
|
|
53
|
+
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
54
|
+
}
|
|
55
|
+
else if (res.locals['cacheTTL'] !== undefined) {
|
|
56
|
+
// Custom TTL for headers only (no storage) - useful when caching is handled elsewhere
|
|
57
|
+
res.setHeader('Cache-Control', getCacheControlHeader(req, cacheTTL, false, true));
|
|
45
58
|
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
46
59
|
}
|
|
47
60
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Joi from 'joi';
|
|
2
1
|
import { InvalidPayloadError } from '@directus/errors';
|
|
2
|
+
import Joi from 'joi';
|
|
3
3
|
import asyncHandler from '../utils/async-handler.js';
|
|
4
4
|
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
5
5
|
import { validateQuery } from '../utils/validate-query.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { defineOperationApi } from '@directus/extensions';
|
|
2
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import { defineOperationApi } from '@directus/extensions';
|
|
3
|
+
// eslint-disable-next-line import/order
|
|
3
4
|
import { sieveFunctions } from '@directus/utils';
|
|
4
5
|
const require = createRequire(import.meta.url);
|
|
5
6
|
const ivm = require('isolated-vm');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
|
+
import { useLogger } from '../../logger/index.js';
|
|
2
3
|
import { MailService } from '../../services/mail/index.js';
|
|
3
4
|
import { md } from '../../utils/md.js';
|
|
4
|
-
import { useLogger } from '../../logger/index.js';
|
|
5
5
|
import { useFlowsEmailRateLimiter } from './rate-limiter.js';
|
|
6
6
|
const logger = useLogger();
|
|
7
7
|
export default defineOperationApi({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
|
+
import { EmailLimitExceededError } from '@directus/errors';
|
|
3
|
+
import { toBoolean } from '@directus/utils';
|
|
2
4
|
import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible';
|
|
3
5
|
import { createRateLimiter } from '../../rate-limiter.js';
|
|
4
|
-
import { toBoolean } from '@directus/utils';
|
|
5
|
-
import { EmailLimitExceededError } from '@directus/errors';
|
|
6
6
|
let emailRateLimiter;
|
|
7
7
|
const env = useEnv();
|
|
8
8
|
if (toBoolean(env['RATE_LIMITER_EMAIL_FLOWS_ENABLED']) === true) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { defineCache } from '@directus/memory';
|
|
2
3
|
import { redisConfigAvailable, useRedis } from '../redis/index.js';
|
|
4
|
+
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
3
5
|
const localOnly = redisConfigAvailable() === false;
|
|
6
|
+
const env = useEnv();
|
|
7
|
+
const ttl = getMilliseconds(env['CACHE_SYSTEM_TTL']);
|
|
4
8
|
const config = localOnly
|
|
5
9
|
? {
|
|
6
10
|
type: 'local',
|
|
@@ -11,6 +15,7 @@ const config = localOnly
|
|
|
11
15
|
redis: {
|
|
12
16
|
namespace: 'permissions',
|
|
13
17
|
redis: useRedis(),
|
|
18
|
+
...(ttl !== undefined ? { ttl } : {}),
|
|
14
19
|
},
|
|
15
20
|
local: {
|
|
16
21
|
maxKeys: 100,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { uniq } from 'lodash-es';
|
|
2
|
-
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
2
|
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
3
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
4
4
|
export async function fetchAllowedCollections({ action, accountability }, { knex, schema }) {
|
|
5
5
|
if (accountability.admin) {
|
|
6
6
|
return Object.keys(schema.collections);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { uniq } from 'lodash-es';
|
|
2
|
-
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
2
|
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
3
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
4
4
|
export async function fetchAllowedFieldMap({ accountability, action }, { knex, schema }) {
|
|
5
5
|
const fieldMap = {};
|
|
6
6
|
if (accountability.admin) {
|
package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
1
|
+
import { difference, intersection, uniq } from 'lodash-es';
|
|
3
2
|
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
3
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
4
4
|
/**
|
|
5
5
|
* Fetch a field map for fields that may or may not be null based on item-by-item permissions.
|
|
6
6
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getUnaliasedFieldKey } from '../../../utils/get-unaliased-field-key.js';
|
|
2
1
|
import { uniq } from 'lodash-es';
|
|
2
|
+
import { getUnaliasedFieldKey } from '../../../utils/get-unaliased-field-key.js';
|
|
3
3
|
import { getCases } from './get-cases.js';
|
|
4
4
|
/**
|
|
5
5
|
* Mutates passed AST
|
|
@@ -3,8 +3,8 @@ import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
|
3
3
|
import { fieldMapFromAst } from './lib/field-map-from-ast.js';
|
|
4
4
|
import { injectCases } from './lib/inject-cases.js';
|
|
5
5
|
import { collectionsInFieldMap } from './utils/collections-in-field-map.js';
|
|
6
|
-
import { validatePathPermissions } from './utils/validate-path/validate-path-permissions.js';
|
|
7
6
|
import { validatePathExistence } from './utils/validate-path/validate-path-existence.js';
|
|
7
|
+
import { validatePathPermissions } from './utils/validate-path/validate-path-permissions.js';
|
|
8
8
|
export async function processAst(options, context) {
|
|
9
9
|
// FieldMap is a Map of paths in the AST, with each path containing the collection and fields in
|
|
10
10
|
// that collection that the AST path tries to access
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getRelationInfo } from '
|
|
1
|
+
import { getRelationInfo } from '@directus/utils';
|
|
2
2
|
export function findRelatedCollection(collection, field, schema) {
|
|
3
3
|
const { relation } = getRelationInfo(schema.relations, collection, field);
|
|
4
4
|
if (!relation)
|
|
@@ -6,8 +6,8 @@ import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
|
6
6
|
import { extractRequiredDynamicVariableContext } from '../../utils/extract-required-dynamic-variable-context.js';
|
|
7
7
|
import { fetchDynamicVariableData } from '../../utils/fetch-dynamic-variable-data.js';
|
|
8
8
|
import { contextHasDynamicVariables } from '../process-ast/utils/context-has-dynamic-variables.js';
|
|
9
|
-
import { isFieldNullable } from './lib/is-field-nullable.js';
|
|
10
9
|
import { createCollectionForbiddenError, createFieldsForbiddenError, } from '../process-ast/utils/validate-path/create-error.js';
|
|
10
|
+
import { isFieldNullable } from './lib/is-field-nullable.js';
|
|
11
11
|
/**
|
|
12
12
|
* @note this only validates the top-level fields. The expectation is that this function is called
|
|
13
13
|
* for each level of nested insert separately
|
|
@@ -4,7 +4,19 @@ export interface ValidateItemAccessOptions {
|
|
|
4
4
|
accountability: Accountability;
|
|
5
5
|
action: PermissionsAction;
|
|
6
6
|
collection: string;
|
|
7
|
-
primaryKeys
|
|
7
|
+
primaryKeys?: PrimaryKey[];
|
|
8
8
|
fields?: string[];
|
|
9
|
+
returnAllowedRootFields?: boolean;
|
|
9
10
|
}
|
|
10
|
-
export
|
|
11
|
+
export interface ValidateItemAccessOptionsWithRootFields extends ValidateItemAccessOptions {
|
|
12
|
+
returnAllowedRootFields: true;
|
|
13
|
+
}
|
|
14
|
+
export interface ValidateItemAccessResult {
|
|
15
|
+
accessAllowed: boolean;
|
|
16
|
+
allowedRootFields?: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface ValidateItemAccessResultWithRootFields extends ValidateItemAccessResult {
|
|
19
|
+
allowedRootFields: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function validateItemAccess(options: ValidateItemAccessOptionsWithRootFields, context: Context): Promise<ValidateItemAccessResultWithRootFields>;
|
|
22
|
+
export declare function validateItemAccess(options: ValidateItemAccessOptions, context: Context): Promise<ValidateItemAccessResult>;
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { toBoolean } from '@directus/utils';
|
|
2
2
|
import { fetchPermittedAstRootFields } from '../../../../database/run-ast/modules/fetch-permitted-ast-root-fields.js';
|
|
3
|
+
import { fetchPermissions } from '../../../lib/fetch-permissions.js';
|
|
4
|
+
import { fetchPolicies } from '../../../lib/fetch-policies.js';
|
|
5
|
+
import { fetchAllowedFields } from '../../fetch-allowed-fields/fetch-allowed-fields.js';
|
|
6
|
+
import { injectCases } from '../../process-ast/lib/inject-cases.js';
|
|
3
7
|
import { processAst } from '../../process-ast/process-ast.js';
|
|
4
8
|
export async function validateItemAccess(options, context) {
|
|
5
|
-
const
|
|
9
|
+
const collectionInfo = context.schema.collections[options.collection];
|
|
10
|
+
const primaryKeyField = collectionInfo?.primary;
|
|
6
11
|
if (!primaryKeyField) {
|
|
7
12
|
throw new Error(`Cannot find primary key for collection "${options.collection}"`);
|
|
8
13
|
}
|
|
14
|
+
const isSingleton = collectionInfo?.singleton === true;
|
|
15
|
+
const hasPrimaryKeys = options.primaryKeys && options.primaryKeys.length > 0;
|
|
16
|
+
// For non-singletons, we must have PKs to validate against
|
|
17
|
+
if (!isSingleton && !hasPrimaryKeys) {
|
|
18
|
+
throw new Error(`Primary keys are required for non-singleton collection "${options.collection}"`);
|
|
19
|
+
}
|
|
9
20
|
// When we're looking up access to specific items, we have to read them from the database to
|
|
10
21
|
// make sure you are allowed to access them.
|
|
11
22
|
const ast = {
|
|
12
23
|
type: 'root',
|
|
13
24
|
name: options.collection,
|
|
14
|
-
query: { limit: options.primaryKeys.length },
|
|
25
|
+
query: { limit: isSingleton && !hasPrimaryKeys ? 1 : options.primaryKeys.length },
|
|
15
26
|
// Act as if every field was a "normal" field
|
|
16
27
|
children: options.fields?.map((field) => ({ type: 'field', name: field, fieldKey: field, whenCase: [], alias: false })) ??
|
|
17
28
|
[],
|
|
@@ -19,23 +30,71 @@ export async function validateItemAccess(options, context) {
|
|
|
19
30
|
};
|
|
20
31
|
await processAst({ ast, ...options }, context);
|
|
21
32
|
// Inject the filter after the permissions have been processed, as to not require access to the primary key
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
// Skip adding filter for singletons without explicit PKs
|
|
34
|
+
if (hasPrimaryKeys) {
|
|
35
|
+
ast.query.filter = {
|
|
36
|
+
[primaryKeyField]: {
|
|
37
|
+
_in: options.primaryKeys,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
let hasItemRules;
|
|
42
|
+
let permissionedFields;
|
|
43
|
+
// Inject the root fields after the permissions have been processed, as to not require access to all collection fields
|
|
44
|
+
if (options.returnAllowedRootFields) {
|
|
45
|
+
const allowedFields = await fetchAllowedFields({ accountability: options.accountability, action: options.action, collection: options.collection }, context);
|
|
46
|
+
const schemaFields = Object.keys(context.schema.collections[options.collection].fields);
|
|
47
|
+
const hasWildcard = allowedFields.includes('*');
|
|
48
|
+
permissionedFields = hasWildcard ? schemaFields : allowedFields;
|
|
49
|
+
const policies = await fetchPolicies(options.accountability, context);
|
|
50
|
+
const permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
|
|
51
|
+
// Only inject cases if there are item-level permission rules
|
|
52
|
+
hasItemRules = permissions.some((p) => p.permissions && Object.keys(p.permissions).length > 0);
|
|
53
|
+
if (hasItemRules) {
|
|
54
|
+
// Create children only for fields that exist in schema and are allowed by permissions
|
|
55
|
+
ast.children = permissionedFields.map((field) => ({
|
|
56
|
+
type: 'field',
|
|
57
|
+
name: field,
|
|
58
|
+
fieldKey: field,
|
|
59
|
+
whenCase: [],
|
|
60
|
+
alias: false,
|
|
61
|
+
}));
|
|
62
|
+
injectCases(ast, permissions);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
27
65
|
const items = await fetchPermittedAstRootFields(ast, {
|
|
28
66
|
schema: context.schema,
|
|
29
67
|
accountability: options.accountability,
|
|
30
68
|
knex: context.knex,
|
|
31
69
|
action: options.action,
|
|
32
70
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
71
|
+
const expectedCount = isSingleton && !hasPrimaryKeys ? 1 : options.primaryKeys.length;
|
|
72
|
+
const hasAccess = items && items.length === expectedCount;
|
|
73
|
+
if (!hasAccess) {
|
|
74
|
+
if (options.returnAllowedRootFields) {
|
|
75
|
+
return { accessAllowed: false, allowedRootFields: [] };
|
|
76
|
+
}
|
|
77
|
+
return { accessAllowed: false };
|
|
78
|
+
}
|
|
79
|
+
let accessAllowed = true;
|
|
80
|
+
// If specific fields were requested, verify they are all accessible
|
|
81
|
+
if (options.fields) {
|
|
82
|
+
accessAllowed = items.every((item) => options.fields.every((field) => toBoolean(item[field])));
|
|
83
|
+
}
|
|
84
|
+
// If returnAllowedRootFields, return intersection of allowed fields across all items
|
|
85
|
+
if (options.returnAllowedRootFields) {
|
|
86
|
+
// If there are no item-level rules, return the permissioned fields directly
|
|
87
|
+
if (!hasItemRules) {
|
|
88
|
+
return {
|
|
89
|
+
accessAllowed,
|
|
90
|
+
allowedRootFields: permissionedFields,
|
|
91
|
+
};
|
|
37
92
|
}
|
|
38
|
-
|
|
93
|
+
const allowedRootFields = items.length > 0 ? Object.keys(items[0]).filter((field) => items.every((item) => item[field] === 1)) : [];
|
|
94
|
+
return {
|
|
95
|
+
accessAllowed,
|
|
96
|
+
allowedRootFields,
|
|
97
|
+
};
|
|
39
98
|
}
|
|
40
|
-
return
|
|
99
|
+
return { accessAllowed };
|
|
41
100
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ForbiddenError } from '@directus/errors';
|
|
2
|
+
import { createCollectionForbiddenError } from '../process-ast/utils/validate-path/create-error.js';
|
|
2
3
|
import { validateCollectionAccess } from './lib/validate-collection-access.js';
|
|
3
4
|
import { validateItemAccess } from './lib/validate-item-access.js';
|
|
4
|
-
import { createCollectionForbiddenError } from '../process-ast/utils/validate-path/create-error.js';
|
|
5
5
|
/**
|
|
6
6
|
* Validate if the current user has access to perform action against the given collection and
|
|
7
7
|
* optional primary keys. This is done by reading the item from the database using the access
|
|
@@ -20,7 +20,8 @@ export async function validateAccess(options, context) {
|
|
|
20
20
|
// from the database. If no keys are passed, we can simply check if the collection+action combo
|
|
21
21
|
// exists within permissions
|
|
22
22
|
if (options.primaryKeys) {
|
|
23
|
-
|
|
23
|
+
const result = await validateItemAccess(options, context);
|
|
24
|
+
access = result.accessAllowed;
|
|
24
25
|
}
|
|
25
26
|
else {
|
|
26
27
|
access = await validateCollectionAccess(options, context);
|
package/dist/rate-limiter.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import { useEnv } from '@directus/env';
|
|
2
3
|
import { merge } from 'lodash-es';
|
|
3
4
|
import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible';
|
|
4
5
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides) {
|
|
8
8
|
const env = useEnv();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
1
2
|
import { useEnv } from '@directus/env';
|
|
2
3
|
import { ipInNetworks } from '@directus/utils/node';
|
|
3
4
|
import { matches } from 'ip-matching';
|
|
4
|
-
import os from 'node:os';
|
|
5
5
|
import { useLogger } from '../logger/index.js';
|
|
6
6
|
export function isDeniedIp(ip) {
|
|
7
7
|
const env = useEnv();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { version } from 'directus/version';
|
|
1
2
|
import { random } from 'lodash-es';
|
|
2
3
|
import getDatabase from '../database/index.js';
|
|
3
4
|
import { sendReport } from '../telemetry/index.js';
|
|
4
5
|
import { scheduleSynchronizedJob } from '../utils/schedule.js';
|
|
5
|
-
import { version } from 'directus/version';
|
|
6
6
|
/**
|
|
7
7
|
* Schedule the project status job
|
|
8
8
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { toBoolean } from '@directus/utils';
|
|
3
3
|
import { getCache } from '../cache.js';
|
|
4
|
-
import { scheduleSynchronizedJob } from '../utils/schedule.js';
|
|
5
4
|
import { track } from '../telemetry/index.js';
|
|
5
|
+
import { scheduleSynchronizedJob } from '../utils/schedule.js';
|
|
6
6
|
/**
|
|
7
7
|
* Exported to be able to test the anonymous callback function
|
|
8
8
|
*/
|
package/dist/schedules/tus.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RESUMABLE_UPLOADS } from '../constants.js';
|
|
2
|
-
import { getSchema } from '../utils/get-schema.js';
|
|
3
2
|
import { createTusServer } from '../services/tus/index.js';
|
|
3
|
+
import { getSchema } from '../utils/get-schema.js';
|
|
4
4
|
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';
|
|
5
5
|
/**
|
|
6
6
|
* Schedule the tus cleanup
|
package/dist/server.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import * as http from 'http';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import url from 'url';
|
|
1
4
|
import { useEnv } from '@directus/env';
|
|
2
5
|
import { toBoolean } from '@directus/utils';
|
|
3
6
|
import { getNodeEnv } from '@directus/utils/node';
|
|
4
7
|
import { createTerminus } from '@godaddy/terminus';
|
|
5
|
-
import * as http from 'http';
|
|
6
|
-
import * as https from 'https';
|
|
7
8
|
import { once } from 'lodash-es';
|
|
8
9
|
import qs from 'qs';
|
|
9
|
-
import url from 'url';
|
|
10
10
|
import createApp from './app.js';
|
|
11
11
|
import getDatabase from './database/index.js';
|
|
12
12
|
import emitter from './emitter.js';
|
|
13
13
|
import { useLogger } from './logger/index.js';
|
|
14
|
+
import { getAddress } from './utils/get-address.js';
|
|
14
15
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
15
16
|
import { getIPFromReq } from './utils/get-ip-from-req.js';
|
|
16
|
-
import { getAddress } from './utils/get-address.js';
|
|
17
17
|
import { createLogsController, createSubscriptionController, createWebSocketController, getLogsController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
18
|
-
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
18
|
+
import { getCollabHandler, startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
19
19
|
export let SERVER_ONLINE = true;
|
|
20
20
|
const env = useEnv();
|
|
21
21
|
const logger = useLogger();
|
|
@@ -101,6 +101,7 @@ export async function createServer() {
|
|
|
101
101
|
getSubscriptionController()?.terminate();
|
|
102
102
|
getWebSocketController()?.terminate();
|
|
103
103
|
getLogsController()?.terminate();
|
|
104
|
+
await getCollabHandler()?.terminate();
|
|
104
105
|
const database = getDatabase();
|
|
105
106
|
await database.destroy();
|
|
106
107
|
logger.info('Database connections destroyed');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
1
2
|
import type { AbstractServiceOptions, Accountability, Range, SchemaOverview, Stat, TransformationSet } from '@directus/types';
|
|
2
3
|
import archiver from 'archiver';
|
|
3
4
|
import type { Knex } from 'knex';
|
|
4
|
-
import type { Readable } from 'node:stream';
|
|
5
5
|
import { FilesService } from './files.js';
|
|
6
6
|
export declare class AssetsService {
|
|
7
7
|
knex: Knex;
|
|
@@ -9,6 +9,7 @@ export declare class AssetsService {
|
|
|
9
9
|
schema: SchemaOverview;
|
|
10
10
|
sudoFilesService: FilesService;
|
|
11
11
|
constructor(options: AbstractServiceOptions);
|
|
12
|
+
private sanitizeFields;
|
|
12
13
|
private zip;
|
|
13
14
|
zipFiles(files: string[]): Promise<{
|
|
14
15
|
archive: archiver.Archiver;
|
package/dist/services/assets.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { useEnv } from '@directus/env';
|
|
2
3
|
import { ForbiddenError, IllegalAssetTransformationError, InvalidPayloadError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
|
|
3
4
|
import archiver from 'archiver';
|
|
4
5
|
import { clamp } from 'lodash-es';
|
|
5
6
|
import { contentType, extension } from 'mime-types';
|
|
6
7
|
import hash from 'object-hash';
|
|
7
|
-
import path from 'path';
|
|
8
8
|
import sharp from 'sharp';
|
|
9
9
|
import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
|
|
10
10
|
import getDatabase from '../database/index.js';
|
|
11
11
|
import { useLogger } from '../logger/index.js';
|
|
12
|
-
import {
|
|
12
|
+
import { validateItemAccess } from '../permissions/modules/validate-access/lib/validate-item-access.js';
|
|
13
13
|
import { getStorage } from '../storage/index.js';
|
|
14
14
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
15
15
|
import { isValidUuid } from '../utils/is-valid-uuid.js';
|
|
16
16
|
import * as TransformationUtils from '../utils/transformations.js';
|
|
17
17
|
import { NameDeduper } from './assets/name-deduper.js';
|
|
18
|
-
import { FilesService } from './files.js';
|
|
19
18
|
import { getSharpInstance } from './files/lib/get-sharp-instance.js';
|
|
19
|
+
import { FilesService } from './files.js';
|
|
20
20
|
import { FoldersService } from './folders.js';
|
|
21
21
|
const env = useEnv();
|
|
22
22
|
const logger = useLogger();
|
|
@@ -31,6 +31,20 @@ export class AssetsService {
|
|
|
31
31
|
this.schema = options.schema;
|
|
32
32
|
this.sudoFilesService = new FilesService({ ...options, accountability: null });
|
|
33
33
|
}
|
|
34
|
+
sanitizeFields(file, allowedFields) {
|
|
35
|
+
if (allowedFields.includes('*')) {
|
|
36
|
+
return file;
|
|
37
|
+
}
|
|
38
|
+
const bypassFields = ['type', 'filesize'];
|
|
39
|
+
const fieldsToKeep = new Set([...allowedFields, ...bypassFields]);
|
|
40
|
+
const filteredFile = {};
|
|
41
|
+
for (const field of fieldsToKeep) {
|
|
42
|
+
if (field in file) {
|
|
43
|
+
filteredFile[field] = file[field];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return filteredFile;
|
|
47
|
+
}
|
|
34
48
|
zip(options) {
|
|
35
49
|
if (options.files.length === 0) {
|
|
36
50
|
throw new InvalidPayloadError({ reason: 'No files found in the selected folders tree' });
|
|
@@ -135,13 +149,22 @@ export class AssetsService {
|
|
|
135
149
|
*/
|
|
136
150
|
if (!isValidUuid(id))
|
|
137
151
|
throw new ForbiddenError();
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
let allowedFields = ['*'];
|
|
153
|
+
if (!systemPublicKeys.includes(id) && this.accountability && this.accountability.admin !== true) {
|
|
154
|
+
// Use validateItemAccess to check access and get allowed fields
|
|
155
|
+
const { allowedRootFields, accessAllowed } = await validateItemAccess({
|
|
140
156
|
accountability: this.accountability,
|
|
141
157
|
action: 'read',
|
|
142
158
|
collection: 'directus_files',
|
|
143
159
|
primaryKeys: [id],
|
|
160
|
+
returnAllowedRootFields: true,
|
|
144
161
|
}, { knex: this.knex, schema: this.schema });
|
|
162
|
+
if (!accessAllowed) {
|
|
163
|
+
throw new ForbiddenError({
|
|
164
|
+
reason: `You don't have permission to perform "read" for collection "directus_files" or it does not exist.`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
allowedFields = allowedRootFields;
|
|
145
168
|
}
|
|
146
169
|
const file = (await this.sudoFilesService.readOne(id, { limit: 1 }));
|
|
147
170
|
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
@@ -195,7 +218,7 @@ export class AssetsService {
|
|
|
195
218
|
const assetStream = () => storage.location(file.storage).read(assetFilename, { range });
|
|
196
219
|
return {
|
|
197
220
|
stream: deferStream ? assetStream : await assetStream(),
|
|
198
|
-
file,
|
|
221
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
199
222
|
stat: await storage.location(file.storage).stat(assetFilename),
|
|
200
223
|
};
|
|
201
224
|
}
|
|
@@ -260,13 +283,17 @@ export class AssetsService {
|
|
|
260
283
|
return {
|
|
261
284
|
stream: deferStream ? assetStream : await assetStream(),
|
|
262
285
|
stat: await storage.location(file.storage).stat(assetFilename),
|
|
263
|
-
file,
|
|
286
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
264
287
|
};
|
|
265
288
|
}
|
|
266
289
|
else {
|
|
267
290
|
const assetStream = () => storage.location(file.storage).read(file.filename_disk, { range, version });
|
|
268
291
|
const stat = await storage.location(file.storage).stat(file.filename_disk);
|
|
269
|
-
return {
|
|
292
|
+
return {
|
|
293
|
+
stream: deferStream ? assetStream : await assetStream(),
|
|
294
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
295
|
+
stat,
|
|
296
|
+
};
|
|
270
297
|
}
|
|
271
298
|
}
|
|
272
299
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import { performance } from 'perf_hooks';
|
|
1
2
|
import { Action } from '@directus/constants';
|
|
2
3
|
import { useEnv } from '@directus/env';
|
|
3
4
|
import { InvalidCredentialsError, InvalidOtpError, ServiceUnavailableError, UserSuspendedError, } from '@directus/errors';
|
|
4
5
|
import jwt from 'jsonwebtoken';
|
|
5
6
|
import { clone, cloneDeep } from 'lodash-es';
|
|
6
|
-
import { performance } from 'perf_hooks';
|
|
7
7
|
import { getAuthProvider } from '../auth.js';
|
|
8
8
|
import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
|
|
9
9
|
import getDatabase from '../database/index.js';
|
|
10
10
|
import emitter from '../emitter.js';
|
|
11
11
|
import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
|
|
12
12
|
import { fetchGlobalAccess } from '../permissions/modules/fetch-global-access/fetch-global-access.js';
|
|
13
|
-
import {
|
|
13
|
+
import { createRateLimiter, RateLimiterRes } from '../rate-limiter.js';
|
|
14
14
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
15
15
|
import { getSecret } from '../utils/get-secret.js';
|
|
16
16
|
import { stall } from '../utils/stall.js';
|