@directus/api 32.1.1 → 33.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/ai/chat/constants/system-prompt.d.ts +1 -0
- package/dist/ai/chat/constants/system-prompt.js +51 -0
- package/dist/ai/chat/controllers/chat.post.d.ts +2 -0
- package/dist/ai/chat/controllers/chat.post.js +47 -0
- package/dist/ai/chat/lib/create-ui-stream.d.ts +15 -0
- package/dist/ai/chat/lib/create-ui-stream.js +42 -0
- package/dist/ai/chat/middleware/load-settings.d.ts +2 -0
- package/dist/ai/chat/middleware/load-settings.js +18 -0
- package/dist/ai/chat/models/chat-request.d.ts +34 -0
- package/dist/ai/chat/models/chat-request.js +26 -0
- package/dist/ai/chat/models/providers.d.ts +9 -0
- package/dist/ai/chat/models/providers.js +9 -0
- package/dist/ai/chat/router.d.ts +1 -0
- package/dist/ai/chat/router.js +5 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.d.ts +9 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +38 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.d.ts +12 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.js +30 -0
- package/dist/ai/chat/utils/parse-json-schema-7.d.ts +13 -0
- package/dist/ai/chat/utils/parse-json-schema-7.js +75 -0
- package/dist/{mcp → ai/mcp}/server.d.ts +13 -16
- package/dist/{mcp → ai/mcp}/server.js +4 -13
- package/dist/ai/mcp/types.d.ts +15 -0
- package/dist/{mcp/tools/assets.js → ai/tools/assets/index.js} +8 -5
- package/dist/{mcp/tools/collections.js → ai/tools/collections/index.js} +7 -4
- package/dist/{mcp/tools/fields.js → ai/tools/fields/index.js} +12 -9
- package/dist/{mcp/tools/files.js → ai/tools/files/index.js} +11 -5
- package/dist/{mcp/tools/flows.js → ai/tools/flows/index.js} +11 -5
- package/dist/{mcp/tools/folders.js → ai/tools/folders/index.js} +12 -5
- package/dist/ai/tools/index.d.ts +15 -0
- package/dist/ai/tools/index.js +29 -0
- package/dist/{mcp/tools/items.js → ai/tools/items/index.js} +13 -6
- package/dist/{mcp/tools/prompts/items.md → ai/tools/items/prompt.md} +19 -15
- package/dist/{mcp/tools/operations.d.ts → ai/tools/operations/index.d.ts} +46 -0
- package/dist/{mcp/tools/operations.js → ai/tools/operations/index.js} +12 -5
- package/dist/{mcp/tools/relations.js → ai/tools/relations/index.js} +7 -4
- package/dist/{mcp/tools/schema.d.ts → ai/tools/schema/index.d.ts} +1 -1
- package/dist/{mcp/tools/schema.js → ai/tools/schema/index.js} +9 -6
- package/dist/{mcp/tools/system.js → ai/tools/system/index.js} +7 -4
- package/dist/{mcp/tools/trigger-flow.js → ai/tools/trigger-flow/index.js} +8 -5
- package/dist/{mcp → ai/tools}/types.d.ts +1 -17
- package/dist/ai/tools/utils.d.ts +9 -0
- package/dist/ai/tools/utils.js +17 -0
- package/dist/app.js +11 -6
- package/dist/auth/drivers/ldap.js +2 -2
- 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 +6 -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.js +2 -2
- 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/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 +40 -3
- package/dist/controllers/extensions.js +1 -1
- package/dist/controllers/fields.js +2 -2
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/items.js +1 -1
- package/dist/controllers/mcp.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/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 +3 -3
- 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/20251103A-add-ai-settings.d.ts +3 -0
- package/dist/database/migrations/20251103A-add-ai-settings.js +14 -0
- 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/20260113A-add-revisions-index.d.ts +3 -0
- package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
- package/dist/database/migrations/run.js +3 -3
- 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/operator.js +1 -1
- package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
- package/dist/database/run-ast/run-ast.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/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 +8 -12
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
- package/dist/extensions/lib/sync/status.d.ts +11 -0
- package/dist/extensions/lib/sync/status.js +34 -0
- package/dist/extensions/lib/sync/sync.d.ts +6 -0
- package/dist/extensions/lib/sync/sync.js +90 -0
- package/dist/extensions/lib/sync/tracker.d.ts +18 -0
- package/dist/extensions/lib/sync/tracker.js +71 -0
- package/dist/extensions/lib/sync/utils.d.ts +24 -0
- package/dist/extensions/lib/sync/utils.js +62 -0
- package/dist/extensions/manager.d.ts +9 -5
- package/dist/extensions/manager.js +36 -19
- 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 +2 -2
- 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/lib/fetch-policies.d.ts +1 -1
- package/dist/permissions/lib/fetch-roles-tree.d.ts +6 -3
- package/dist/permissions/lib/fetch-roles-tree.js +5 -27
- 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-global-access/fetch-global-access.d.ts +9 -7
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +17 -9
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +1 -1
- 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-payload/process-payload.js +1 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +13 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +54 -6
- package/dist/permissions/modules/validate-access/validate-access.js +3 -2
- package/dist/permissions/utils/fetch-raw-permissions.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.js +1 -1
- package/dist/permissions/utils/filter-policies-by-ip.js +1 -1
- package/dist/permissions/utils/get-permissions-for-share.js +8 -8
- package/dist/permissions/utils/with-cache.d.ts +8 -6
- package/dist/permissions/utils/with-cache.js +12 -10
- package/dist/rate-limiter.js +1 -1
- package/dist/request/is-denied-ip.js +2 -2
- 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 +4 -4
- package/dist/services/assets/name-deduper.d.ts +7 -0
- package/dist/services/assets/name-deduper.js +23 -0
- package/dist/services/assets.d.ts +17 -3
- package/dist/services/assets.js +133 -13
- package/dist/services/authentication.js +6 -6
- package/dist/services/collections.js +1 -1
- package/dist/services/comments.js +2 -2
- package/dist/services/extensions.d.ts +1 -1
- package/dist/services/extensions.js +4 -0
- 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/folders.d.ts +27 -2
- package/dist/services/folders.js +75 -0
- 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/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/import-export.d.ts +2 -2
- package/dist/services/import-export.js +6 -7
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/rate-limiter.js +2 -2
- package/dist/services/notifications.js +2 -2
- package/dist/services/payload.js +21 -1
- package/dist/services/roles.js +2 -2
- package/dist/services/schema.js +1 -1
- package/dist/services/server.js +12 -4
- package/dist/services/settings.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 +9 -9
- package/dist/services/users.js +4 -4
- package/dist/services/versions.js +1 -1
- 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/utils/get-settings.d.ts +15 -0
- package/dist/telemetry/utils/get-settings.js +13 -1
- package/dist/test-utils/README.md +95 -24
- package/dist/test-utils/cache.d.ts +2 -2
- package/dist/test-utils/cache.js +2 -2
- package/dist/test-utils/knex.js +1 -1
- package/dist/test-utils/{fields-service.d.ts → services/fields-service.d.ts} +1 -1
- package/dist/test-utils/{fields-service.js → services/fields-service.js} +3 -2
- package/dist/test-utils/services/files-service.d.ts +28 -0
- package/dist/test-utils/services/files-service.js +34 -0
- package/dist/test-utils/services/folders-service.d.ts +28 -0
- package/dist/test-utils/services/folders-service.js +33 -0
- 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.js +2 -2
- package/dist/utils/encrypt.d.ts +2 -0
- package/dist/utils/encrypt.js +64 -0
- package/dist/utils/get-accountability-for-role.js +2 -2
- package/dist/utils/get-accountability-for-token.js +4 -4
- package/dist/utils/get-cache-key.js +3 -3
- 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 +1 -3
- package/dist/utils/get-snapshot-diff.js +1 -1
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/require-text.d.ts +1 -0
- package/dist/utils/require-text.js +4 -0
- package/dist/utils/require-yaml.js +2 -2
- 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/deep-map-with-schema.js +2 -2
- 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/items.js +2 -2
- package/dist/websocket/handlers/subscribe.js +1 -1
- package/dist/websocket/types.d.ts +1 -1
- package/dist/websocket/utils/wait-for-message.js +1 -1
- package/package.json +34 -28
- package/dist/controllers/webhooks.d.ts +0 -2
- package/dist/controllers/webhooks.js +0 -74
- package/dist/extensions/lib/sync-extensions.d.ts +0 -3
- package/dist/extensions/lib/sync-extensions.js +0 -70
- package/dist/extensions/lib/sync-status.d.ts +0 -10
- package/dist/extensions/lib/sync-status.js +0 -27
- package/dist/mcp/tools/index.d.ts +0 -15
- package/dist/mcp/tools/index.js +0 -29
- package/dist/mcp/tools/prompts/index.d.ts +0 -16
- package/dist/mcp/tools/prompts/index.js +0 -19
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
- package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
- package/dist/services/webhooks.d.ts +0 -14
- package/dist/services/webhooks.js +0 -32
- package/dist/utils/get-date-formatted.d.ts +0 -1
- package/dist/utils/get-date-formatted.js +0 -10
- package/dist/utils/ip-in-networks.d.ts +0 -6
- package/dist/utils/ip-in-networks.js +0 -13
- package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
- /package/dist/{mcp → ai/mcp}/index.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/index.js +0 -0
- /package/dist/{mcp → ai/mcp}/transport.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/transport.js +0 -0
- /package/dist/{mcp → ai/mcp}/types.js +0 -0
- /package/dist/{mcp/tools/assets.d.ts → ai/tools/assets/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/assets.md → ai/tools/assets/prompt.md} +0 -0
- /package/dist/{mcp/tools/collections.d.ts → ai/tools/collections/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/collections.md → ai/tools/collections/prompt.md} +0 -0
- /package/dist/{mcp/define.d.ts → ai/tools/define-tool.d.ts} +0 -0
- /package/dist/{mcp/define.js → ai/tools/define-tool.js} +0 -0
- /package/dist/{mcp/tools/fields.d.ts → ai/tools/fields/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/fields.md → ai/tools/fields/prompt.md} +0 -0
- /package/dist/{mcp/tools/files.d.ts → ai/tools/files/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/files.md → ai/tools/files/prompt.md} +0 -0
- /package/dist/{mcp/tools/flows.d.ts → ai/tools/flows/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/flows.md → ai/tools/flows/prompt.md} +0 -0
- /package/dist/{mcp/tools/folders.d.ts → ai/tools/folders/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/folders.md → ai/tools/folders/prompt.md} +0 -0
- /package/dist/{mcp/tools/items.d.ts → ai/tools/items/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/operations.md → ai/tools/operations/prompt.md} +0 -0
- /package/dist/{mcp/tools/relations.d.ts → ai/tools/relations/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/relations.md → ai/tools/relations/prompt.md} +0 -0
- /package/dist/{mcp/tools/prompts/schema.md → ai/tools/schema/prompt.md} +0 -0
- /package/dist/{mcp → ai/tools}/schema.d.ts +0 -0
- /package/dist/{mcp → ai/tools}/schema.js +0 -0
- /package/dist/{mcp/tools/system.d.ts → ai/tools/system/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt-description.md → ai/tools/system/prompt-description.md} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt.md → ai/tools/system/prompt.md} +0 -0
- /package/dist/{mcp/tools/trigger-flow.d.ts → ai/tools/trigger-flow/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/trigger-flow.md → ai/tools/trigger-flow/prompt.md} +0 -0
- /package/dist/{permissions/modules/fetch-global-access → ai/tools}/types.js +0 -0
- /package/dist/test-utils/{items-service.d.ts → services/items-service.d.ts} +0 -0
- /package/dist/test-utils/{items-service.js → services/items-service.js} +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
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
9
|
const primaryKeyField = context.schema.collections[options.collection]?.primary;
|
|
@@ -24,18 +28,62 @@ export async function validateItemAccess(options, context) {
|
|
|
24
28
|
_in: options.primaryKeys,
|
|
25
29
|
},
|
|
26
30
|
};
|
|
31
|
+
let hasItemRules;
|
|
32
|
+
let permissionedFields;
|
|
33
|
+
// Inject the root fields after the permissions have been processed, as to not require access to all collection fields
|
|
34
|
+
if (options.returnAllowedRootFields) {
|
|
35
|
+
const allowedFields = await fetchAllowedFields({ accountability: options.accountability, action: options.action, collection: options.collection }, context);
|
|
36
|
+
const schemaFields = Object.keys(context.schema.collections[options.collection].fields);
|
|
37
|
+
const hasWildcard = allowedFields.includes('*');
|
|
38
|
+
permissionedFields = hasWildcard ? schemaFields : allowedFields;
|
|
39
|
+
const policies = await fetchPolicies(options.accountability, context);
|
|
40
|
+
const permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
|
|
41
|
+
// Only inject cases if there are item-level permission rules
|
|
42
|
+
hasItemRules = permissions.some((p) => p.permissions && Object.keys(p.permissions).length > 0);
|
|
43
|
+
if (hasItemRules) {
|
|
44
|
+
// Create children only for fields that exist in schema and are allowed by permissions
|
|
45
|
+
ast.children = permissionedFields.map((field) => ({
|
|
46
|
+
type: 'field',
|
|
47
|
+
name: field,
|
|
48
|
+
fieldKey: field,
|
|
49
|
+
whenCase: [],
|
|
50
|
+
alias: false,
|
|
51
|
+
}));
|
|
52
|
+
injectCases(ast, permissions);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
27
55
|
const items = await fetchPermittedAstRootFields(ast, {
|
|
28
56
|
schema: context.schema,
|
|
29
57
|
accountability: options.accountability,
|
|
30
58
|
knex: context.knex,
|
|
31
59
|
action: options.action,
|
|
32
60
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
return
|
|
61
|
+
const hasAccess = items && items.length === options.primaryKeys.length;
|
|
62
|
+
if (!hasAccess) {
|
|
63
|
+
if (options.returnAllowedRootFields) {
|
|
64
|
+
return { accessAllowed: false, allowedRootFields: [] };
|
|
65
|
+
}
|
|
66
|
+
return { accessAllowed: false };
|
|
67
|
+
}
|
|
68
|
+
let accessAllowed = true;
|
|
69
|
+
// If specific fields were requested, verify they are all accessible
|
|
70
|
+
if (options.fields) {
|
|
71
|
+
accessAllowed = items.every((item) => options.fields.every((field) => toBoolean(item[field])));
|
|
72
|
+
}
|
|
73
|
+
// If returnAllowedRootFields, return intersection of allowed fields across all items
|
|
74
|
+
if (options.returnAllowedRootFields) {
|
|
75
|
+
// If there are no item-level rules, return the permissioned fields directly
|
|
76
|
+
if (!hasItemRules) {
|
|
77
|
+
return {
|
|
78
|
+
accessAllowed,
|
|
79
|
+
allowedRootFields: permissionedFields,
|
|
80
|
+
};
|
|
37
81
|
}
|
|
38
|
-
|
|
82
|
+
const allowedRootFields = items.length > 0 ? Object.keys(items[0]).filter((field) => items.every((item) => item[field] === 1)) : [];
|
|
83
|
+
return {
|
|
84
|
+
accessAllowed,
|
|
85
|
+
allowedRootFields,
|
|
86
|
+
};
|
|
39
87
|
}
|
|
40
|
-
return
|
|
88
|
+
return { accessAllowed };
|
|
41
89
|
}
|
|
@@ -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);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Accountability, Permission, PermissionsAction } from '@directus/types';
|
|
2
2
|
import type { Context } from '../types.js';
|
|
3
|
-
export declare const fetchRawPermissions:
|
|
3
|
+
export declare const fetchRawPermissions: (options: FetchRawPermissionsOptions, context: Context) => Promise<Permission[]>;
|
|
4
4
|
export interface FetchRawPermissionsOptions {
|
|
5
5
|
action?: PermissionsAction;
|
|
6
6
|
policies: string[];
|
|
@@ -8,5 +8,5 @@ export interface ShareInfo {
|
|
|
8
8
|
role: string;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export declare const fetchShareInfo:
|
|
11
|
+
export declare const fetchShareInfo: (shareId: string, context: AbstractServiceOptions) => Promise<ShareInfo>;
|
|
12
12
|
export declare function _fetchShareInfo(shareId: string, context: AbstractServiceOptions): Promise<ShareInfo>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { withCache } from './with-cache.js';
|
|
2
|
-
export const fetchShareInfo = withCache('share-info', _fetchShareInfo);
|
|
2
|
+
export const fetchShareInfo = withCache('share-info', _fetchShareInfo, (shareId) => ({ shareId }));
|
|
3
3
|
export async function _fetchShareInfo(shareId, context) {
|
|
4
4
|
const { SharesService } = await import('../../services/shares.js');
|
|
5
5
|
const sharesService = new SharesService(context);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ipInNetworks } from '
|
|
1
|
+
import { ipInNetworks } from '@directus/utils/node';
|
|
2
2
|
export function filterPoliciesByIp(policies, ip) {
|
|
3
3
|
return policies.filter(({ policy }) => {
|
|
4
4
|
// Keep policies that don't have an ip address allow list configured
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { schemaPermissions } from '@directus/system-data';
|
|
2
2
|
import { set, uniq } from 'lodash-es';
|
|
3
|
-
import {
|
|
4
|
-
import { fetchShareInfo } from './fetch-share-info.js';
|
|
5
|
-
import { mergePermissions } from './merge-permissions.js';
|
|
3
|
+
import { reduceSchema } from '../../utils/reduce-schema.js';
|
|
6
4
|
import { fetchPermissions } from '../lib/fetch-permissions.js';
|
|
7
5
|
import { fetchPolicies } from '../lib/fetch-policies.js';
|
|
8
6
|
import { fetchRolesTree } from '../lib/fetch-roles-tree.js';
|
|
9
|
-
import {
|
|
7
|
+
import { fetchAllowedFieldMap } from '../modules/fetch-allowed-field-map/fetch-allowed-field-map.js';
|
|
10
8
|
import { fetchGlobalAccess } from '../modules/fetch-global-access/fetch-global-access.js';
|
|
9
|
+
import { fetchShareInfo } from './fetch-share-info.js';
|
|
10
|
+
import { mergePermissions } from './merge-permissions.js';
|
|
11
11
|
export async function getPermissionsForShare(accountability, collections, context) {
|
|
12
12
|
const defaults = {
|
|
13
13
|
action: 'read',
|
|
@@ -22,7 +22,7 @@ export async function getPermissionsForShare(accountability, collections, contex
|
|
|
22
22
|
const userAccountability = {
|
|
23
23
|
user: user_created.id,
|
|
24
24
|
role: user_created.role,
|
|
25
|
-
roles: await fetchRolesTree(user_created.role, context.knex),
|
|
25
|
+
roles: await fetchRolesTree(user_created.role, { knex: context.knex }),
|
|
26
26
|
admin: false,
|
|
27
27
|
app: false,
|
|
28
28
|
ip: accountability.ip,
|
|
@@ -31,14 +31,14 @@ export async function getPermissionsForShare(accountability, collections, contex
|
|
|
31
31
|
const shareAccountability = {
|
|
32
32
|
user: null,
|
|
33
33
|
role: role,
|
|
34
|
-
roles: await fetchRolesTree(role, context.knex),
|
|
34
|
+
roles: await fetchRolesTree(role, { knex: context.knex }),
|
|
35
35
|
admin: false,
|
|
36
36
|
app: false,
|
|
37
37
|
ip: accountability.ip,
|
|
38
38
|
};
|
|
39
39
|
const [{ admin: shareIsAdmin }, { admin: userIsAdmin }, userPermissions, sharePermissions, shareFieldMap, userFieldMap,] = await Promise.all([
|
|
40
|
-
fetchGlobalAccess(shareAccountability, context.knex),
|
|
41
|
-
fetchGlobalAccess(userAccountability, context.knex),
|
|
40
|
+
fetchGlobalAccess(shareAccountability, { knex: context.knex }),
|
|
41
|
+
fetchGlobalAccess(userAccountability, { knex: context.knex }),
|
|
42
42
|
getPermissionsForAccountability(userAccountability, context),
|
|
43
43
|
getPermissionsForAccountability(shareAccountability, context),
|
|
44
44
|
fetchAllowedFieldMap({
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ensuring key order.
|
|
2
|
+
* Wraps a function with caching capabilities.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* @param namespace - A unique namespace for the cache key.
|
|
5
|
+
* @param handler - The function to be wrapped.
|
|
6
|
+
* @param prepareArg - Optional function to prepare arguments for hashing.
|
|
7
|
+
* @returns A new function that caches the results of the original function.
|
|
7
8
|
*
|
|
8
|
-
* @NOTE
|
|
9
|
+
* @NOTE Ensure that the `namespace` is unique to avoid cache key collisions.
|
|
10
|
+
* @NOTE Ensure that the `prepareArg` function returns a JSON stringifiable representation of the arguments.
|
|
9
11
|
*/
|
|
10
|
-
export declare function withCache<F extends (
|
|
12
|
+
export declare function withCache<F extends (...args: any) => any>(namespace: string, handler: F, prepareArg?: (...args: Parameters<F>) => Record<string, unknown>): (...args: Parameters<F>) => Promise<Awaited<ReturnType<F>>>;
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { getSimpleHash } from '@directus/utils';
|
|
2
2
|
import { useCache } from '../cache.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* ensuring key order.
|
|
4
|
+
* Wraps a function with caching capabilities.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* @param namespace - A unique namespace for the cache key.
|
|
7
|
+
* @param handler - The function to be wrapped.
|
|
8
|
+
* @param prepareArg - Optional function to prepare arguments for hashing.
|
|
9
|
+
* @returns A new function that caches the results of the original function.
|
|
9
10
|
*
|
|
10
|
-
* @NOTE
|
|
11
|
+
* @NOTE Ensure that the `namespace` is unique to avoid cache key collisions.
|
|
12
|
+
* @NOTE Ensure that the `prepareArg` function returns a JSON stringifiable representation of the arguments.
|
|
11
13
|
*/
|
|
12
14
|
export function withCache(namespace, handler, prepareArg) {
|
|
13
15
|
const cache = useCache();
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
const key = namespace + '-' + getSimpleHash(JSON.stringify(
|
|
16
|
+
return async (...args) => {
|
|
17
|
+
const hashArgs = prepareArg ? prepareArg(...args) : args;
|
|
18
|
+
const key = namespace + '-' + getSimpleHash(JSON.stringify(hashArgs));
|
|
17
19
|
const cached = await cache.get(key);
|
|
18
20
|
if (cached !== undefined) {
|
|
19
21
|
return cached;
|
|
20
22
|
}
|
|
21
|
-
const res = await handler(
|
|
23
|
+
const res = await handler(...args);
|
|
22
24
|
cache.set(key, res);
|
|
23
25
|
return res;
|
|
24
|
-
}
|
|
26
|
+
};
|
|
25
27
|
}
|
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,8 +1,8 @@
|
|
|
1
|
-
import { useEnv } from '@directus/env';
|
|
2
1
|
import os from 'node:os';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
3
|
+
import { ipInNetworks } from '@directus/utils/node';
|
|
3
4
|
import { matches } from 'ip-matching';
|
|
4
5
|
import { useLogger } from '../logger/index.js';
|
|
5
|
-
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
6
6
|
export function isDeniedIp(ip) {
|
|
7
7
|
const env = useEnv();
|
|
8
8
|
const logger = useLogger();
|
|
@@ -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,19 +1,19 @@
|
|
|
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
18
|
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
19
19
|
export let SERVER_ONLINE = true;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import sanitize from 'sanitize-filename';
|
|
2
|
+
const DEFAULT_GROUP = Symbol('undefined');
|
|
3
|
+
export class NameDeduper {
|
|
4
|
+
map = {};
|
|
5
|
+
add(name, options) {
|
|
6
|
+
name = sanitize(name ?? '') || options?.fallback;
|
|
7
|
+
if (!name) {
|
|
8
|
+
throw Error('Invalid "name" provided');
|
|
9
|
+
}
|
|
10
|
+
const groupKey = options?.group ?? DEFAULT_GROUP;
|
|
11
|
+
const match = this.map[groupKey]?.[name];
|
|
12
|
+
if (match) {
|
|
13
|
+
const dedupedName = `${name} (${match})`;
|
|
14
|
+
this.map[groupKey][name] += 1;
|
|
15
|
+
return dedupedName;
|
|
16
|
+
}
|
|
17
|
+
if (!this.map[groupKey]) {
|
|
18
|
+
this.map[groupKey] = {};
|
|
19
|
+
}
|
|
20
|
+
this.map[groupKey][name] = 1;
|
|
21
|
+
return name;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import type { AbstractServiceOptions, Accountability, Range, Stat, SchemaOverview, TransformationSet } from '@directus/types';
|
|
2
|
-
import type { Knex } from 'knex';
|
|
3
1
|
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { AbstractServiceOptions, Accountability, Range, SchemaOverview, Stat, TransformationSet } from '@directus/types';
|
|
3
|
+
import archiver from 'archiver';
|
|
4
|
+
import type { Knex } from 'knex';
|
|
4
5
|
import { FilesService } from './files.js';
|
|
5
6
|
export declare class AssetsService {
|
|
6
7
|
knex: Knex;
|
|
7
8
|
accountability: Accountability | null;
|
|
8
9
|
schema: SchemaOverview;
|
|
9
|
-
|
|
10
|
+
sudoFilesService: FilesService;
|
|
10
11
|
constructor(options: AbstractServiceOptions);
|
|
12
|
+
private sanitizeFields;
|
|
13
|
+
private zip;
|
|
14
|
+
zipFiles(files: string[]): Promise<{
|
|
15
|
+
archive: archiver.Archiver;
|
|
16
|
+
complete: () => Promise<void>;
|
|
17
|
+
}>;
|
|
18
|
+
zipFolder(root: string): Promise<{
|
|
19
|
+
archive: archiver.Archiver;
|
|
20
|
+
complete: () => Promise<void>;
|
|
21
|
+
metadata: {
|
|
22
|
+
name: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
}>;
|
|
11
25
|
getAsset(id: string, transformation?: TransformationSet, range?: Range, deferStream?: false): Promise<{
|
|
12
26
|
stream: Readable;
|
|
13
27
|
file: any;
|
package/dist/services/assets.js
CHANGED
|
@@ -1,32 +1,139 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { useEnv } from '@directus/env';
|
|
2
|
-
import { ForbiddenError, IllegalAssetTransformationError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
|
|
3
|
+
import { ForbiddenError, IllegalAssetTransformationError, InvalidPayloadError, InvalidQueryError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
|
|
4
|
+
import archiver from 'archiver';
|
|
3
5
|
import { clamp } from 'lodash-es';
|
|
4
|
-
import { contentType } from 'mime-types';
|
|
6
|
+
import { contentType, extension } from 'mime-types';
|
|
5
7
|
import hash from 'object-hash';
|
|
6
|
-
import path from 'path';
|
|
7
8
|
import sharp from 'sharp';
|
|
8
9
|
import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
|
|
9
10
|
import getDatabase from '../database/index.js';
|
|
10
11
|
import { useLogger } from '../logger/index.js';
|
|
11
|
-
import {
|
|
12
|
+
import { validateItemAccess } from '../permissions/modules/validate-access/lib/validate-item-access.js';
|
|
12
13
|
import { getStorage } from '../storage/index.js';
|
|
13
14
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
14
15
|
import { isValidUuid } from '../utils/is-valid-uuid.js';
|
|
15
16
|
import * as TransformationUtils from '../utils/transformations.js';
|
|
16
|
-
import {
|
|
17
|
+
import { NameDeduper } from './assets/name-deduper.js';
|
|
17
18
|
import { getSharpInstance } from './files/lib/get-sharp-instance.js';
|
|
19
|
+
import { FilesService } from './files.js';
|
|
20
|
+
import { FoldersService } from './folders.js';
|
|
18
21
|
const env = useEnv();
|
|
19
22
|
const logger = useLogger();
|
|
20
23
|
export class AssetsService {
|
|
21
24
|
knex;
|
|
22
25
|
accountability;
|
|
23
26
|
schema;
|
|
24
|
-
|
|
27
|
+
sudoFilesService;
|
|
25
28
|
constructor(options) {
|
|
26
29
|
this.knex = options.knex || getDatabase();
|
|
27
30
|
this.accountability = options.accountability || null;
|
|
28
31
|
this.schema = options.schema;
|
|
29
|
-
this.
|
|
32
|
+
this.sudoFilesService = new FilesService({ ...options, accountability: null });
|
|
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
|
+
}
|
|
48
|
+
zip(options) {
|
|
49
|
+
if (options.files.length === 0) {
|
|
50
|
+
throw new InvalidPayloadError({ reason: 'No files found in the selected folders tree' });
|
|
51
|
+
}
|
|
52
|
+
const archive = archiver('zip');
|
|
53
|
+
const complete = async () => {
|
|
54
|
+
const deduper = new NameDeduper();
|
|
55
|
+
const storage = await getStorage();
|
|
56
|
+
for (const { id, folder, filename_download } of options.files) {
|
|
57
|
+
const file = await this.sudoFilesService.readOne(id, {
|
|
58
|
+
fields: ['id', 'storage', 'filename_disk', 'filename_download', 'modified_on', 'type'],
|
|
59
|
+
});
|
|
60
|
+
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
61
|
+
if (!exists)
|
|
62
|
+
throw new ForbiddenError();
|
|
63
|
+
const version = file.modified_on ? (new Date(file.modified_on).getTime() / 1000).toFixed() : undefined;
|
|
64
|
+
const assetStream = await storage.location(file.storage).read(file.filename_disk, { version });
|
|
65
|
+
const fileExtension = path.extname(file.filename_download) || (file.type && '.' + extension(file.type)) || '';
|
|
66
|
+
const dedupedFileName = deduper.add(filename_download, { group: folder, fallback: file.id + fileExtension });
|
|
67
|
+
const folderName = folder ? options.folders?.get(folder) : undefined;
|
|
68
|
+
archive.append(assetStream, { name: dedupedFileName, prefix: folderName });
|
|
69
|
+
}
|
|
70
|
+
// add any empty folders, does not override already filled folder
|
|
71
|
+
if (options.folders) {
|
|
72
|
+
for (const [, folder] of options.folders) {
|
|
73
|
+
archive.append('', { name: folder + '/' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
await archive.finalize();
|
|
77
|
+
};
|
|
78
|
+
return { archive, complete };
|
|
79
|
+
}
|
|
80
|
+
async zipFiles(files) {
|
|
81
|
+
const filesService = new FilesService({
|
|
82
|
+
schema: this.schema,
|
|
83
|
+
knex: this.knex,
|
|
84
|
+
accountability: this.accountability,
|
|
85
|
+
});
|
|
86
|
+
const filesToZip = await filesService.readByQuery({
|
|
87
|
+
filter: {
|
|
88
|
+
id: {
|
|
89
|
+
_in: files,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
limit: -1,
|
|
93
|
+
});
|
|
94
|
+
return this.zip({
|
|
95
|
+
files: filesToZip.map((file) => ({
|
|
96
|
+
id: file['id'],
|
|
97
|
+
folder: file['folder'],
|
|
98
|
+
filename_download: file['filename_download'],
|
|
99
|
+
})),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async zipFolder(root) {
|
|
103
|
+
const foldersService = new FoldersService({
|
|
104
|
+
schema: this.schema,
|
|
105
|
+
knex: this.knex,
|
|
106
|
+
accountability: this.accountability,
|
|
107
|
+
});
|
|
108
|
+
const folderTree = await foldersService.buildTree(root);
|
|
109
|
+
const filesService = new FilesService({
|
|
110
|
+
schema: this.schema,
|
|
111
|
+
knex: this.knex,
|
|
112
|
+
accountability: this.accountability,
|
|
113
|
+
});
|
|
114
|
+
const filesToZip = await filesService.readByQuery({
|
|
115
|
+
filter: {
|
|
116
|
+
folder: {
|
|
117
|
+
_in: Array.from(folderTree.keys()),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
limit: -1,
|
|
121
|
+
});
|
|
122
|
+
const { archive, complete } = this.zip({
|
|
123
|
+
folders: folderTree,
|
|
124
|
+
files: filesToZip.map((file) => ({
|
|
125
|
+
id: file['id'],
|
|
126
|
+
folder: file['folder'],
|
|
127
|
+
filename_download: file['filename_download'],
|
|
128
|
+
})),
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
archive,
|
|
132
|
+
complete,
|
|
133
|
+
metadata: {
|
|
134
|
+
name: folderTree.get(root),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
30
137
|
}
|
|
31
138
|
async getAsset(id, transformation, range, deferStream = false) {
|
|
32
139
|
const storage = await getStorage();
|
|
@@ -42,15 +149,24 @@ export class AssetsService {
|
|
|
42
149
|
*/
|
|
43
150
|
if (!isValidUuid(id))
|
|
44
151
|
throw new ForbiddenError();
|
|
45
|
-
|
|
46
|
-
|
|
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({
|
|
47
156
|
accountability: this.accountability,
|
|
48
157
|
action: 'read',
|
|
49
158
|
collection: 'directus_files',
|
|
50
159
|
primaryKeys: [id],
|
|
160
|
+
returnAllowedRootFields: true,
|
|
51
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;
|
|
52
168
|
}
|
|
53
|
-
const file = (await this.
|
|
169
|
+
const file = (await this.sudoFilesService.readOne(id, { limit: 1 }));
|
|
54
170
|
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
55
171
|
if (!exists)
|
|
56
172
|
throw new ForbiddenError();
|
|
@@ -102,7 +218,7 @@ export class AssetsService {
|
|
|
102
218
|
const assetStream = () => storage.location(file.storage).read(assetFilename, { range });
|
|
103
219
|
return {
|
|
104
220
|
stream: deferStream ? assetStream : await assetStream(),
|
|
105
|
-
file,
|
|
221
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
106
222
|
stat: await storage.location(file.storage).stat(assetFilename),
|
|
107
223
|
};
|
|
108
224
|
}
|
|
@@ -167,13 +283,17 @@ export class AssetsService {
|
|
|
167
283
|
return {
|
|
168
284
|
stream: deferStream ? assetStream : await assetStream(),
|
|
169
285
|
stat: await storage.location(file.storage).stat(assetFilename),
|
|
170
|
-
file,
|
|
286
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
171
287
|
};
|
|
172
288
|
}
|
|
173
289
|
else {
|
|
174
290
|
const assetStream = () => storage.location(file.storage).read(file.filename_disk, { range, version });
|
|
175
291
|
const stat = await storage.location(file.storage).stat(file.filename_disk);
|
|
176
|
-
return {
|
|
292
|
+
return {
|
|
293
|
+
stream: deferStream ? assetStream : await assetStream(),
|
|
294
|
+
file: this.sanitizeFields(file, allowedFields),
|
|
295
|
+
stat,
|
|
296
|
+
};
|
|
177
297
|
}
|
|
178
298
|
}
|
|
179
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';
|
|
@@ -149,8 +149,8 @@ export class AuthenticationService {
|
|
|
149
149
|
throw new InvalidOtpError();
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
const roles = await fetchRolesTree(user.role, this.knex);
|
|
153
|
-
const globalAccess = await fetchGlobalAccess({ roles, user: user.id, ip: this.accountability?.ip ?? null }, this.knex);
|
|
152
|
+
const roles = await fetchRolesTree(user.role, { knex: this.knex });
|
|
153
|
+
const globalAccess = await fetchGlobalAccess({ roles, user: user.id, ip: this.accountability?.ip ?? null }, { knex: this.knex });
|
|
154
154
|
const tokenPayload = {
|
|
155
155
|
id: user.id,
|
|
156
156
|
role: user.role,
|
|
@@ -277,8 +277,8 @@ export class AuthenticationService {
|
|
|
277
277
|
throw new InvalidCredentialsError();
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
|
-
const roles = await fetchRolesTree(record.user_role, this.knex);
|
|
281
|
-
const globalAccess = await fetchGlobalAccess({ user: record.user_id, roles, ip: this.accountability?.ip ?? null }, this.knex);
|
|
280
|
+
const roles = await fetchRolesTree(record.user_role, { knex: this.knex });
|
|
281
|
+
const globalAccess = await fetchGlobalAccess({ user: record.user_id, roles, ip: this.accountability?.ip ?? null }, { knex: this.knex });
|
|
282
282
|
if (record.user_id) {
|
|
283
283
|
const provider = getAuthProvider(record.user_provider);
|
|
284
284
|
await provider.refresh({
|
|
@@ -14,10 +14,10 @@ import { validateAccess } from '../permissions/modules/validate-access/validate-
|
|
|
14
14
|
import { getSchema } from '../utils/get-schema.js';
|
|
15
15
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
16
16
|
import { transaction } from '../utils/transaction.js';
|
|
17
|
-
import { FieldsService } from './fields.js';
|
|
18
17
|
import { buildCollectionAndFieldRelations } from './fields/build-collection-and-field-relations.js';
|
|
19
18
|
import { getCollectionMetaUpdates } from './fields/get-collection-meta-updates.js';
|
|
20
19
|
import { getCollectionRelationList } from './fields/get-collection-relation-list.js';
|
|
20
|
+
import { FieldsService } from './fields.js';
|
|
21
21
|
import { ItemsService } from './items.js';
|
|
22
22
|
export class CollectionsService {
|
|
23
23
|
knex;
|