@directus/api 15.0.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +6 -4
- package/dist/auth/drivers/ldap.js +7 -4
- package/dist/auth/drivers/local.js +3 -2
- package/dist/auth/drivers/oauth2.js +9 -2
- package/dist/auth/drivers/openid.js +9 -2
- package/dist/auth/drivers/saml.js +6 -4
- package/dist/auth.js +7 -4
- package/dist/bus/index.d.ts +1 -0
- package/dist/bus/index.js +1 -0
- package/dist/bus/lib/use-bus.d.ts +9 -0
- package/dist/bus/lib/use-bus.js +21 -0
- package/dist/cache.js +9 -9
- package/dist/cli/commands/bootstrap/index.js +6 -2
- package/dist/cli/commands/count/index.js +2 -1
- package/dist/cli/commands/database/install.js +2 -1
- package/dist/cli/commands/database/migrate.js +2 -1
- package/dist/cli/commands/roles/create.js +2 -1
- package/dist/cli/commands/schema/apply.js +2 -1
- package/dist/cli/commands/schema/snapshot.js +6 -5
- package/dist/cli/commands/users/create.js +4 -3
- package/dist/cli/commands/users/passwd.js +5 -4
- package/dist/cli/load-extensions.js +4 -2
- package/dist/cli/utils/create-env/env-stub.liquid +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +4 -1
- package/dist/controllers/assets.js +5 -3
- package/dist/controllers/auth.js +5 -4
- package/dist/controllers/extensions.js +18 -6
- package/dist/controllers/files.js +3 -3
- package/dist/controllers/schema.js +3 -2
- package/dist/controllers/shares.js +3 -3
- package/dist/database/helpers/index.d.ts +1 -1
- package/dist/database/index.js +9 -2
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +3 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +2 -1
- package/dist/database/migrations/20231215A-add-focalpoints.d.ts +3 -0
- package/dist/database/migrations/20231215A-add-focalpoints.js +12 -0
- package/dist/database/migrations/run.js +2 -1
- package/dist/database/run-ast.js +5 -2
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -7
- package/dist/database/system-data/fields/files.yaml +16 -0
- package/dist/emitter.js +3 -1
- package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
- package/dist/extensions/lib/get-extensions-path.js +2 -1
- package/dist/extensions/lib/get-extensions.d.ts +1 -1
- package/dist/extensions/lib/get-extensions.js +32 -8
- package/dist/extensions/lib/get-shared-deps-mapping.js +6 -4
- package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
- package/dist/extensions/lib/sync-extensions.js +6 -4
- package/dist/extensions/manager.js +43 -19
- package/dist/flows.js +13 -7
- package/dist/logger.d.ts +7 -7
- package/dist/logger.js +116 -92
- package/dist/mailer.js +4 -2
- package/dist/middleware/cache.js +4 -2
- package/dist/middleware/check-ip.js +25 -6
- package/dist/middleware/cors.js +2 -1
- package/dist/middleware/error-handler.js +5 -5
- package/dist/middleware/rate-limiter-global.js +4 -2
- package/dist/middleware/rate-limiter-ip.js +2 -1
- package/dist/middleware/respond.js +4 -2
- package/dist/operations/log/index.js +2 -1
- package/dist/rate-limiter.d.ts +2 -1
- package/dist/rate-limiter.js +5 -2
- package/dist/redis/index.d.ts +3 -2
- package/dist/redis/index.js +3 -2
- package/dist/redis/{create-redis.js → lib/create-redis.js} +2 -2
- package/dist/redis/utils/redis-config-available.d.ts +4 -0
- package/dist/redis/utils/redis-config-available.js +8 -0
- package/dist/request/request-interceptor.js +7 -5
- package/dist/request/response-interceptor.js +2 -2
- package/dist/request/validate-ip.d.ts +1 -1
- package/dist/request/validate-ip.js +23 -7
- package/dist/server.js +11 -7
- package/dist/services/activity.js +5 -4
- package/dist/services/assets.d.ts +2 -0
- package/dist/services/assets.js +9 -4
- package/dist/services/authentication.js +17 -9
- package/dist/services/collections.js +5 -4
- package/dist/services/extensions.d.ts +15 -9
- package/dist/services/extensions.js +74 -39
- package/dist/services/fields.js +9 -4
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +22 -14
- package/dist/services/graphql/index.js +46 -3
- package/dist/services/graphql/subscription.js +2 -2
- package/dist/services/graphql/types/bigint.js +16 -5
- package/dist/services/graphql/utils/process-error.d.ts +4 -1
- package/dist/services/graphql/utils/process-error.js +10 -8
- package/dist/services/import-export/index.js +5 -3
- package/dist/services/items.js +12 -8
- package/dist/services/mail/index.js +4 -2
- package/dist/services/notifications.js +7 -3
- package/dist/services/relations.js +19 -10
- package/dist/services/server.js +5 -4
- package/dist/services/shares.js +3 -2
- package/dist/services/specifications.js +2 -1
- package/dist/services/users.js +20 -9
- package/dist/services/versions.js +6 -5
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/services/webhooks.js +2 -2
- package/dist/services/websocket.d.ts +1 -1
- package/dist/services/websocket.js +4 -3
- package/dist/storage/register-drivers.js +2 -1
- package/dist/storage/register-locations.js +2 -1
- package/dist/synchronization.js +3 -1
- package/dist/telemetry/lib/get-report.js +1 -1
- package/dist/telemetry/lib/init-telemetry.js +2 -2
- package/dist/telemetry/lib/send-report.js +1 -1
- package/dist/telemetry/lib/track.js +2 -3
- package/dist/telemetry/utils/get-user-count.js +1 -1
- package/dist/types/assets.d.ts +2 -0
- package/dist/utils/apply-diff.js +2 -1
- package/dist/utils/apply-query.js +0 -11
- package/dist/utils/delete-from-require-cache.js +2 -1
- package/dist/utils/get-accountability-for-token.js +3 -2
- package/dist/utils/get-auth-providers.js +2 -1
- package/dist/utils/get-cache-headers.js +5 -2
- package/dist/utils/get-config-from-env.js +2 -1
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-ip-from-req.js +4 -2
- package/dist/utils/get-permissions.js +5 -3
- package/dist/utils/get-schema.js +5 -2
- package/dist/utils/get-snapshot-diff.js +7 -9
- package/dist/utils/get-snapshot.js +4 -4
- package/dist/utils/ip-in-networks.d.ts +6 -0
- package/dist/utils/ip-in-networks.js +13 -0
- package/dist/utils/is-url-allowed.js +2 -1
- package/dist/utils/job-queue.d.ts +1 -0
- package/dist/utils/job-queue.js +3 -0
- package/dist/utils/sanitize-query.js +7 -2
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-clear-cache.js +2 -1
- package/dist/utils/should-skip-cache.js +2 -1
- package/dist/utils/transformations.js +95 -12
- package/dist/utils/validate-env.js +4 -2
- package/dist/utils/validate-query.js +7 -3
- package/dist/utils/validate-storage.js +4 -2
- package/dist/webhooks.js +4 -3
- package/dist/websocket/controllers/base.js +12 -6
- package/dist/websocket/controllers/graphql.js +4 -2
- package/dist/websocket/controllers/hooks.js +3 -2
- package/dist/websocket/controllers/index.js +4 -2
- package/dist/websocket/controllers/rest.js +4 -2
- package/dist/websocket/errors.js +2 -1
- package/dist/websocket/handlers/heartbeat.js +4 -3
- package/dist/websocket/handlers/subscribe.d.ts +2 -2
- package/dist/websocket/handlers/subscribe.js +5 -4
- package/package.json +57 -57
- package/dist/__utils__/items-utils.d.ts +0 -2
- package/dist/__utils__/items-utils.js +0 -31
- package/dist/__utils__/mock-env.d.ts +0 -18
- package/dist/__utils__/mock-env.js +0 -41
- package/dist/__utils__/schemas.d.ts +0 -13
- package/dist/__utils__/schemas.js +0 -301
- package/dist/__utils__/snapshots.d.ts +0 -5
- package/dist/__utils__/snapshots.js +0 -903
- package/dist/env.d.ts +0 -14
- package/dist/env.js +0 -511
- package/dist/messenger.d.ts +0 -24
- package/dist/messenger.js +0 -64
- package/dist/utils/to-boolean.d.ts +0 -4
- package/dist/utils/to-boolean.js +0 -6
- /package/dist/redis/{create-redis.d.ts → lib/create-redis.d.ts} +0 -0
- /package/dist/redis/{use-redis.d.ts → lib/use-redis.d.ts} +0 -0
- /package/dist/redis/{use-redis.js → lib/use-redis.js} +0 -0
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { JAVASCRIPT_FILE_EXTS } from '@directus/constants';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
2
3
|
import { APP_SHARED_DEPS, HYBRID_EXTENSION_TYPES, NESTED_EXTENSION_TYPES } from '@directus/extensions';
|
|
3
4
|
import { generateExtensionsEntrypoint } from '@directus/extensions/node';
|
|
4
|
-
import { isIn, isTypeIn, pluralize } from '@directus/utils';
|
|
5
|
-
import {
|
|
5
|
+
import { isIn, isTypeIn, pluralize, toBoolean } from '@directus/utils';
|
|
6
|
+
import { getNodeEnv } from '@directus/utils/node';
|
|
6
7
|
import aliasDefault from '@rollup/plugin-alias';
|
|
7
8
|
import nodeResolveDefault from '@rollup/plugin-node-resolve';
|
|
8
9
|
import virtualDefault from '@rollup/plugin-virtual';
|
|
9
10
|
import chokidar, { FSWatcher } from 'chokidar';
|
|
10
11
|
import express, { Router } from 'express';
|
|
11
12
|
import ivm from 'isolated-vm';
|
|
12
|
-
import { clone } from 'lodash-es';
|
|
13
|
+
import { clone, debounce } from 'lodash-es';
|
|
13
14
|
import { readFile, readdir } from 'node:fs/promises';
|
|
15
|
+
import os from 'node:os';
|
|
14
16
|
import { dirname } from 'node:path';
|
|
15
17
|
import { fileURLToPath } from 'node:url';
|
|
16
18
|
import path from 'path';
|
|
17
19
|
import { rollup } from 'rollup';
|
|
18
20
|
import getDatabase from '../database/index.js';
|
|
19
21
|
import emitter, { Emitter } from '../emitter.js';
|
|
20
|
-
import env from '../env.js';
|
|
21
22
|
import { getFlowManager } from '../flows.js';
|
|
22
|
-
import
|
|
23
|
+
import { useLogger } from '../logger.js';
|
|
23
24
|
import * as services from '../services/index.js';
|
|
24
25
|
import { deleteFromRequireCache } from '../utils/delete-from-require-cache.js';
|
|
25
26
|
import getModuleDefault from '../utils/get-module-default.js';
|
|
@@ -27,7 +28,6 @@ import { getSchema } from '../utils/get-schema.js';
|
|
|
27
28
|
import { importFileUrl } from '../utils/import-file-url.js';
|
|
28
29
|
import { JobQueue } from '../utils/job-queue.js';
|
|
29
30
|
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';
|
|
30
|
-
import { toBoolean } from '../utils/to-boolean.js';
|
|
31
31
|
import { getExtensionsPath } from './lib/get-extensions-path.js';
|
|
32
32
|
import { getExtensionsSettings } from './lib/get-extensions-settings.js';
|
|
33
33
|
import { getExtensions } from './lib/get-extensions.js';
|
|
@@ -41,9 +41,10 @@ const virtual = virtualDefault;
|
|
|
41
41
|
const alias = aliasDefault;
|
|
42
42
|
const nodeResolve = nodeResolveDefault;
|
|
43
43
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const env = useEnv();
|
|
44
45
|
const defaultOptions = {
|
|
45
46
|
schedule: true,
|
|
46
|
-
watch: env['EXTENSIONS_AUTO_RELOAD'] &&
|
|
47
|
+
watch: env['EXTENSIONS_AUTO_RELOAD'] && getNodeEnv() !== 'development',
|
|
47
48
|
};
|
|
48
49
|
export class ExtensionManager {
|
|
49
50
|
options = defaultOptions;
|
|
@@ -109,6 +110,7 @@ export class ExtensionManager {
|
|
|
109
110
|
* @param {boolean} options.watch - Whether or not to watch the local extensions folder for changes
|
|
110
111
|
*/
|
|
111
112
|
async initialize(options = {}) {
|
|
113
|
+
const logger = useLogger();
|
|
112
114
|
this.options = {
|
|
113
115
|
...defaultOptions,
|
|
114
116
|
...options,
|
|
@@ -134,6 +136,7 @@ export class ExtensionManager {
|
|
|
134
136
|
* Load all extensions from disk and register them in their respective places
|
|
135
137
|
*/
|
|
136
138
|
async load() {
|
|
139
|
+
const logger = useLogger();
|
|
137
140
|
try {
|
|
138
141
|
await syncExtensions();
|
|
139
142
|
}
|
|
@@ -171,12 +174,17 @@ export class ExtensionManager {
|
|
|
171
174
|
* Reload all the extensions. Will unload if extensions have already been loaded
|
|
172
175
|
*/
|
|
173
176
|
reload() {
|
|
177
|
+
if (this.reloadQueue.size > 0) {
|
|
178
|
+
// The pending job in the queue will already handle the additional changes
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const logger = useLogger();
|
|
174
182
|
this.reloadQueue.enqueue(async () => {
|
|
175
183
|
if (this.isLoaded) {
|
|
176
|
-
logger.info('Reloading extensions');
|
|
177
184
|
const prevExtensions = clone(this.extensions);
|
|
178
185
|
await this.unload();
|
|
179
186
|
await this.load();
|
|
187
|
+
logger.info('Extensions reloaded');
|
|
180
188
|
const added = this.extensions.filter((extension) => !prevExtensions.some((prevExtension) => extension.path === prevExtension.path));
|
|
181
189
|
const removed = prevExtensions.filter((prevExtension) => !this.extensions.some((extension) => prevExtension.path === extension.path));
|
|
182
190
|
this.updateWatchedExtensions(added, removed);
|
|
@@ -231,27 +239,34 @@ export class ExtensionManager {
|
|
|
231
239
|
* Start the chokidar watcher for extensions on the local filesystem
|
|
232
240
|
*/
|
|
233
241
|
initializeWatcher() {
|
|
242
|
+
const logger = useLogger();
|
|
234
243
|
logger.info('Watching extensions for changes...');
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
|
|
244
|
+
const extensionsDir = path.resolve(getExtensionsPath());
|
|
245
|
+
const rootPackageJson = path.resolve(env['PACKAGE_FILE_LOCATION'], 'package.json');
|
|
246
|
+
const localExtensions = path.join(extensionsDir, '*', 'package.json');
|
|
247
|
+
const nestedExtensions = NESTED_EXTENSION_TYPES.flatMap((type) => {
|
|
248
|
+
const typeDir = path.join(extensionsDir, pluralize(type));
|
|
238
249
|
if (isIn(type, HYBRID_EXTENSION_TYPES)) {
|
|
239
250
|
return [
|
|
240
|
-
path.
|
|
241
|
-
path.
|
|
251
|
+
path.join(typeDir, '*', `app.{${JAVASCRIPT_FILE_EXTS.join()}}`),
|
|
252
|
+
path.join(typeDir, '*', `api.{${JAVASCRIPT_FILE_EXTS.join()}}`),
|
|
242
253
|
];
|
|
243
254
|
}
|
|
244
255
|
else {
|
|
245
|
-
return path.
|
|
256
|
+
return path.join(typeDir, '*', `index.{${JAVASCRIPT_FILE_EXTS.join()}}`);
|
|
246
257
|
}
|
|
247
258
|
});
|
|
248
|
-
this.watcher = chokidar.watch([
|
|
259
|
+
this.watcher = chokidar.watch([rootPackageJson, localExtensions, ...nestedExtensions], {
|
|
249
260
|
ignoreInitial: true,
|
|
261
|
+
// dotdirs are watched by default and frequently found in 'node_modules'
|
|
262
|
+
ignored: `${extensionsDir}/**/node_modules/**`,
|
|
263
|
+
// on macOS dotdirs in linked extensions are watched too
|
|
264
|
+
followSymlinks: os.platform() === 'darwin' ? false : true,
|
|
250
265
|
});
|
|
251
266
|
this.watcher
|
|
252
|
-
.on('add', () => this.reload())
|
|
253
|
-
.on('change', () => this.reload())
|
|
254
|
-
.on('unlink', () => this.reload());
|
|
267
|
+
.on('add', debounce(() => this.reload(), 500))
|
|
268
|
+
.on('change', debounce(() => this.reload(), 650))
|
|
269
|
+
.on('unlink', debounce(() => this.reload(), 2000));
|
|
255
270
|
}
|
|
256
271
|
/**
|
|
257
272
|
* Close and destroy the local filesystem watcher if enabled
|
|
@@ -268,8 +283,12 @@ export class ExtensionManager {
|
|
|
268
283
|
*/
|
|
269
284
|
updateWatchedExtensions(added, removed = []) {
|
|
270
285
|
if (this.watcher) {
|
|
286
|
+
const extensionDir = path.resolve(getExtensionsPath());
|
|
287
|
+
const nestedExtensionDirs = NESTED_EXTENSION_TYPES.map((type) => {
|
|
288
|
+
return path.join(extensionDir, pluralize(type));
|
|
289
|
+
});
|
|
271
290
|
const toPackageExtensionPaths = (extensions) => extensions
|
|
272
|
-
.filter((extension) => !
|
|
291
|
+
.filter((extension) => !nestedExtensionDirs.some((path) => extension.path.startsWith(path)))
|
|
273
292
|
.flatMap((extension) => isTypeIn(extension, HYBRID_EXTENSION_TYPES) || extension.type === 'bundle'
|
|
274
293
|
? [
|
|
275
294
|
path.resolve(extension.path, extension.entrypoint.app),
|
|
@@ -287,6 +306,7 @@ export class ExtensionManager {
|
|
|
287
306
|
* run.
|
|
288
307
|
*/
|
|
289
308
|
async generateExtensionBundle() {
|
|
309
|
+
const logger = useLogger();
|
|
290
310
|
const sharedDepsMapping = await getSharedDepsMapping(APP_SHARED_DEPS);
|
|
291
311
|
const internalImports = Object.entries(sharedDepsMapping).map(([name, path]) => ({
|
|
292
312
|
find: name,
|
|
@@ -316,6 +336,7 @@ export class ExtensionManager {
|
|
|
316
336
|
return null;
|
|
317
337
|
}
|
|
318
338
|
async registerSandboxedApiExtension(extension) {
|
|
339
|
+
const logger = useLogger();
|
|
319
340
|
const sandboxMemory = Number(env['EXTENSIONS_SANDBOX_MEMORY']);
|
|
320
341
|
const sandboxTimeout = Number(env['EXTENSIONS_SANDBOX_TIMEOUT']);
|
|
321
342
|
const entrypointPath = path.resolve(extension.path, isTypeIn(extension, HYBRID_EXTENSION_TYPES) ? extension.entrypoint.api : extension.entrypoint);
|
|
@@ -503,6 +524,7 @@ export class ExtensionManager {
|
|
|
503
524
|
* Register a single hook
|
|
504
525
|
*/
|
|
505
526
|
registerHook(hookRegistrationCallback, name) {
|
|
527
|
+
const logger = useLogger();
|
|
506
528
|
let scheduleIndex = 0;
|
|
507
529
|
const unregisterFunctions = [];
|
|
508
530
|
const hookRegistrationContext = {
|
|
@@ -582,6 +604,7 @@ export class ExtensionManager {
|
|
|
582
604
|
* Register an individual endpoint
|
|
583
605
|
*/
|
|
584
606
|
registerEndpoint(config, name) {
|
|
607
|
+
const logger = useLogger();
|
|
585
608
|
const endpointRegistrationCallback = typeof config === 'function' ? config : config.handler;
|
|
586
609
|
const nameWithoutType = name.includes(':') ? name.split(':')[0] : name;
|
|
587
610
|
const routeName = typeof config === 'function' ? nameWithoutType : config.id;
|
|
@@ -623,6 +646,7 @@ export class ExtensionManager {
|
|
|
623
646
|
* Otherwise, the error will only be logged as a warning.
|
|
624
647
|
*/
|
|
625
648
|
handleExtensionError({ error, reason }) {
|
|
649
|
+
const logger = useLogger();
|
|
626
650
|
if (toBoolean(env['EXTENSIONS_MUST_LOAD'])) {
|
|
627
651
|
logger.error('EXTENSION_MUST_LOAD is enabled and an extension failed to load.');
|
|
628
652
|
logger.error(reason);
|
package/dist/flows.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
3
|
+
import { ForbiddenError } from '@directus/errors';
|
|
2
4
|
import { applyOptionsData, getRedactedString, isValidJSON, parseJSON, toArray } from '@directus/utils';
|
|
3
5
|
import { omit, pick } from 'lodash-es';
|
|
4
6
|
import { get } from 'micromustache';
|
|
7
|
+
import { useBus } from './bus/index.js';
|
|
5
8
|
import getDatabase from './database/index.js';
|
|
6
9
|
import emitter from './emitter.js';
|
|
7
|
-
import
|
|
8
|
-
import { ForbiddenError } from '@directus/errors';
|
|
9
|
-
import logger from './logger.js';
|
|
10
|
-
import { getMessenger } from './messenger.js';
|
|
10
|
+
import { useLogger } from './logger.js';
|
|
11
11
|
import { ActivityService } from './services/activity.js';
|
|
12
12
|
import { FlowsService } from './services/flows.js';
|
|
13
13
|
import * as services from './services/index.js';
|
|
@@ -40,9 +40,11 @@ class FlowManager {
|
|
|
40
40
|
reloadQueue;
|
|
41
41
|
envs;
|
|
42
42
|
constructor() {
|
|
43
|
+
const env = useEnv();
|
|
44
|
+
const logger = useLogger();
|
|
43
45
|
this.reloadQueue = new JobQueue();
|
|
44
46
|
this.envs = env['FLOWS_ENV_ALLOW_LIST'] ? pick(env, toArray(env['FLOWS_ENV_ALLOW_LIST'])) : {};
|
|
45
|
-
const messenger =
|
|
47
|
+
const messenger = useBus();
|
|
46
48
|
messenger.subscribe('flows', (event) => {
|
|
47
49
|
if (event['type'] === 'reload') {
|
|
48
50
|
this.reloadQueue.enqueue(async () => {
|
|
@@ -63,7 +65,7 @@ class FlowManager {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
async reload() {
|
|
66
|
-
const messenger =
|
|
68
|
+
const messenger = useBus();
|
|
67
69
|
messenger.publish('flows', { type: 'reload' });
|
|
68
70
|
}
|
|
69
71
|
addOperation(id, operation) {
|
|
@@ -73,6 +75,7 @@ class FlowManager {
|
|
|
73
75
|
this.operations.delete(id);
|
|
74
76
|
}
|
|
75
77
|
async runOperationFlow(id, data, context) {
|
|
78
|
+
const logger = useLogger();
|
|
76
79
|
if (!(id in this.operationFlowHandlers)) {
|
|
77
80
|
logger.warn(`Couldn't find operation triggered flow with id "${id}"`);
|
|
78
81
|
return null;
|
|
@@ -81,6 +84,7 @@ class FlowManager {
|
|
|
81
84
|
return handler(data, context);
|
|
82
85
|
}
|
|
83
86
|
async runWebhookFlow(id, data, context) {
|
|
87
|
+
const logger = useLogger();
|
|
84
88
|
if (!(id in this.webhookFlowHandlers)) {
|
|
85
89
|
logger.warn(`Couldn't find webhook or manual triggered flow with id "${id}"`);
|
|
86
90
|
throw new ForbiddenError();
|
|
@@ -89,6 +93,7 @@ class FlowManager {
|
|
|
89
93
|
return handler(data, context);
|
|
90
94
|
}
|
|
91
95
|
async load() {
|
|
96
|
+
const logger = useLogger();
|
|
92
97
|
const flowsService = new FlowsService({ knex: getDatabase(), schema: await getSchema() });
|
|
93
98
|
const flows = await flowsService.readByQuery({
|
|
94
99
|
filter: { status: { _eq: 'active' } },
|
|
@@ -305,6 +310,7 @@ class FlowManager {
|
|
|
305
310
|
return undefined;
|
|
306
311
|
}
|
|
307
312
|
async executeOperation(operation, keyedData, context = {}) {
|
|
313
|
+
const logger = useLogger();
|
|
308
314
|
if (!this.operations.has(operation.type)) {
|
|
309
315
|
logger.warn(`Couldn't find operation ${operation.type}`);
|
|
310
316
|
return { successor: null, status: 'unknown', data: null, options: null };
|
|
@@ -314,7 +320,7 @@ class FlowManager {
|
|
|
314
320
|
try {
|
|
315
321
|
let result = await handler(options, {
|
|
316
322
|
services,
|
|
317
|
-
env,
|
|
323
|
+
env: useEnv(),
|
|
318
324
|
database: getDatabase(),
|
|
319
325
|
logger,
|
|
320
326
|
getSchema,
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/// <reference types="qs" />
|
|
2
2
|
import type { RequestHandler } from 'express';
|
|
3
|
-
import { type
|
|
4
|
-
export declare const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
9
|
-
export
|
|
3
|
+
import { type Logger } from 'pino';
|
|
4
|
+
export declare const _cache: {
|
|
5
|
+
logger: Logger<never> | undefined;
|
|
6
|
+
};
|
|
7
|
+
export declare const useLogger: () => Logger<never>;
|
|
8
|
+
export declare const createLogger: () => Logger<never>;
|
|
9
|
+
export declare const createExpressLogger: () => RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
package/dist/logger.js
CHANGED
|
@@ -1,112 +1,136 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { REDACTED_TEXT, toArray } from '@directus/utils';
|
|
2
3
|
import { merge } from 'lodash-es';
|
|
3
4
|
import { URL } from 'node:url';
|
|
4
5
|
import { pino } from 'pino';
|
|
5
6
|
import { pinoHttp, stdSerializers } from 'pino-http';
|
|
6
|
-
import env from './env.js';
|
|
7
7
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export const httpLoggerOptions = {
|
|
16
|
-
level: env['LOG_LEVEL'] || 'info',
|
|
17
|
-
redact: {
|
|
18
|
-
paths: ['req.headers.authorization', 'req.headers.cookie'],
|
|
19
|
-
censor: REDACTED_TEXT,
|
|
20
|
-
},
|
|
8
|
+
export const _cache = { logger: undefined };
|
|
9
|
+
export const useLogger = () => {
|
|
10
|
+
if (_cache.logger) {
|
|
11
|
+
return _cache.logger;
|
|
12
|
+
}
|
|
13
|
+
_cache.logger = createLogger();
|
|
14
|
+
return _cache.logger;
|
|
21
15
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
export const createLogger = () => {
|
|
17
|
+
const env = useEnv();
|
|
18
|
+
const pinoOptions = {
|
|
19
|
+
level: env['LOG_LEVEL'] || 'info',
|
|
20
|
+
redact: {
|
|
21
|
+
paths: ['req.headers.authorization', 'req.headers.cookie'],
|
|
22
|
+
censor: REDACTED_TEXT,
|
|
28
23
|
},
|
|
29
24
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
translateTime: 'SYS:HH:MM:ss',
|
|
35
|
-
relativeUrl: true,
|
|
36
|
-
prettyOptions: {
|
|
25
|
+
if (env['LOG_STYLE'] !== 'raw') {
|
|
26
|
+
pinoOptions.transport = {
|
|
27
|
+
target: 'pino-pretty',
|
|
28
|
+
options: {
|
|
37
29
|
ignore: 'hostname,pid',
|
|
38
30
|
sync: true,
|
|
39
31
|
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP');
|
|
35
|
+
// Expose custom log levels into formatter function
|
|
36
|
+
if (loggerEnvConfig['levels']) {
|
|
37
|
+
const customLogLevels = {};
|
|
38
|
+
for (const el of toArray(loggerEnvConfig['levels'])) {
|
|
39
|
+
const key_val = el.split(':');
|
|
40
|
+
customLogLevels[key_val[0].trim()] = key_val[1].trim();
|
|
41
|
+
}
|
|
42
|
+
pinoOptions.formatters = {
|
|
43
|
+
level(label, number) {
|
|
44
|
+
return {
|
|
45
|
+
severity: customLogLevels[label] || 'info',
|
|
46
|
+
level: number,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
delete loggerEnvConfig['levels'];
|
|
51
|
+
}
|
|
52
|
+
return pino(merge(pinoOptions, loggerEnvConfig));
|
|
53
|
+
};
|
|
54
|
+
export const createExpressLogger = () => {
|
|
55
|
+
const env = useEnv();
|
|
56
|
+
const httpLoggerEnvConfig = getConfigFromEnv('LOGGER_HTTP', ['LOGGER_HTTP_LOGGER']);
|
|
57
|
+
const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP');
|
|
58
|
+
const httpLoggerOptions = {
|
|
59
|
+
level: env['LOG_LEVEL'] || 'info',
|
|
60
|
+
redact: {
|
|
61
|
+
paths: ['req.headers.authorization', 'req.headers.cookie'],
|
|
62
|
+
censor: REDACTED_TEXT,
|
|
40
63
|
},
|
|
41
64
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
if (env['LOG_STYLE'] !== 'raw') {
|
|
66
|
+
httpLoggerOptions.transport = {
|
|
67
|
+
target: 'pino-http-print',
|
|
68
|
+
options: {
|
|
69
|
+
all: true,
|
|
70
|
+
translateTime: 'SYS:HH:MM:ss',
|
|
71
|
+
relativeUrl: true,
|
|
72
|
+
prettyOptions: {
|
|
73
|
+
ignore: 'hostname,pid',
|
|
74
|
+
sync: true,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (env['LOG_STYLE'] === 'raw') {
|
|
80
|
+
httpLoggerOptions.redact = {
|
|
81
|
+
paths: ['req.headers.authorization', 'req.headers.cookie', 'res.headers'],
|
|
82
|
+
censor: (value, pathParts) => {
|
|
83
|
+
const path = pathParts.join('.');
|
|
84
|
+
if (path === 'res.headers') {
|
|
85
|
+
if ('set-cookie' in value) {
|
|
86
|
+
value['set-cookie'] = REDACTED_TEXT;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
51
89
|
}
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const loggerEnvConfig = getConfigFromEnv('LOGGER_', 'LOGGER_HTTP');
|
|
59
|
-
// Expose custom log levels into formatter function
|
|
60
|
-
if (loggerEnvConfig['levels']) {
|
|
61
|
-
const customLogLevels = {};
|
|
62
|
-
for (const el of toArray(loggerEnvConfig['levels'])) {
|
|
63
|
-
const key_val = el.split(':');
|
|
64
|
-
customLogLevels[key_val[0].trim()] = key_val[1].trim();
|
|
90
|
+
return REDACTED_TEXT;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
65
93
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
output.url = redactQuery(output.url);
|
|
104
|
-
return output;
|
|
94
|
+
// Expose custom log levels into formatter function
|
|
95
|
+
if (loggerEnvConfig['levels']) {
|
|
96
|
+
const customLogLevels = {};
|
|
97
|
+
for (const el of toArray(loggerEnvConfig['levels'])) {
|
|
98
|
+
const key_val = el.split(':');
|
|
99
|
+
customLogLevels[key_val[0].trim()] = key_val[1].trim();
|
|
100
|
+
}
|
|
101
|
+
httpLoggerOptions.formatters = {
|
|
102
|
+
level(label, number) {
|
|
103
|
+
return {
|
|
104
|
+
severity: customLogLevels[label] || 'info',
|
|
105
|
+
level: number,
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
delete loggerEnvConfig['levels'];
|
|
110
|
+
}
|
|
111
|
+
if (env['LOG_HTTP_IGNORE_PATHS']) {
|
|
112
|
+
const ignorePathsSet = new Set(env['LOG_HTTP_IGNORE_PATHS']);
|
|
113
|
+
httpLoggerEnvConfig['autoLogging'] = {
|
|
114
|
+
ignore: (req) => {
|
|
115
|
+
if (!req.url)
|
|
116
|
+
return false;
|
|
117
|
+
const { pathname } = new URL(req.url, 'http://example.com/');
|
|
118
|
+
return ignorePathsSet.has(pathname);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return pinoHttp({
|
|
123
|
+
logger: pino(merge(httpLoggerOptions, loggerEnvConfig)),
|
|
124
|
+
...httpLoggerEnvConfig,
|
|
125
|
+
serializers: {
|
|
126
|
+
req(request) {
|
|
127
|
+
const output = stdSerializers.req(request);
|
|
128
|
+
output.url = redactQuery(output.url);
|
|
129
|
+
return output;
|
|
130
|
+
},
|
|
105
131
|
},
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
export const useLogger = () => logger;
|
|
109
|
-
export default logger;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
110
134
|
function redactQuery(originalPath) {
|
|
111
135
|
const url = new URL(originalPath, 'http://example.com/');
|
|
112
136
|
if (url.searchParams.has('access_token')) {
|
package/dist/mailer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import nodemailer from 'nodemailer';
|
|
2
|
-
import
|
|
3
|
-
import logger from './logger.js';
|
|
3
|
+
import { useLogger } from './logger.js';
|
|
4
4
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
@@ -8,6 +8,8 @@ let transporter;
|
|
|
8
8
|
export default function getMailer() {
|
|
9
9
|
if (transporter)
|
|
10
10
|
return transporter;
|
|
11
|
+
const env = useEnv();
|
|
12
|
+
const logger = useLogger();
|
|
11
13
|
const transportName = env['EMAIL_TRANSPORT'].toLowerCase();
|
|
12
14
|
if (transportName === 'sendmail') {
|
|
13
15
|
transporter = nodemailer.createTransport({
|
package/dist/middleware/cache.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { getCache, getCacheValue } from '../cache.js';
|
|
2
|
-
import
|
|
3
|
-
import logger from '../logger.js';
|
|
3
|
+
import { useLogger } from '../logger.js';
|
|
4
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
5
|
import { getCacheControlHeader } from '../utils/get-cache-headers.js';
|
|
6
6
|
import { getCacheKey } from '../utils/get-cache-key.js';
|
|
7
7
|
import { shouldSkipCache } from '../utils/should-skip-cache.js';
|
|
8
8
|
const checkCacheMiddleware = asyncHandler(async (req, res, next) => {
|
|
9
|
+
const env = useEnv();
|
|
9
10
|
const { cache } = getCache();
|
|
11
|
+
const logger = useLogger();
|
|
10
12
|
if (req.method.toLowerCase() !== 'get' && req.originalUrl?.startsWith('/graphql') === false)
|
|
11
13
|
return next();
|
|
12
14
|
if (env['CACHE_ENABLED'] !== true)
|
|
@@ -1,18 +1,37 @@
|
|
|
1
|
-
import getDatabase from '../database/index.js';
|
|
2
1
|
import { InvalidIpError } from '@directus/errors';
|
|
2
|
+
import getDatabase from '../database/index.js';
|
|
3
|
+
import { useLogger } from '../logger.js';
|
|
3
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
5
|
+
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
4
6
|
export const checkIP = asyncHandler(async (req, _res, next) => {
|
|
5
7
|
const database = getDatabase();
|
|
8
|
+
const logger = useLogger();
|
|
9
|
+
const { role: roleId, ip } = req.accountability;
|
|
6
10
|
const query = database.select('ip_access').from('directus_roles');
|
|
7
|
-
if (
|
|
8
|
-
query.where({ id:
|
|
11
|
+
if (roleId) {
|
|
12
|
+
query.where({ id: roleId });
|
|
9
13
|
}
|
|
10
14
|
else {
|
|
11
15
|
query.whereNull('id');
|
|
12
16
|
}
|
|
13
17
|
const role = await query.first();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
if (!role?.ip_access)
|
|
19
|
+
return next();
|
|
20
|
+
const ipAllowList = role.ip_access.split(',').filter((ip) => ip);
|
|
21
|
+
if (ipAllowList.length > 0) {
|
|
22
|
+
if (!ip)
|
|
23
|
+
throw new InvalidIpError();
|
|
24
|
+
let allowed;
|
|
25
|
+
try {
|
|
26
|
+
allowed = ipInNetworks(ip, ipAllowList);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger.warn(`Invalid IP access configuration for role "${roleId}"`);
|
|
30
|
+
logger.warn(error);
|
|
31
|
+
throw new InvalidIpError();
|
|
32
|
+
}
|
|
33
|
+
if (!allowed)
|
|
34
|
+
throw new InvalidIpError();
|
|
35
|
+
}
|
|
17
36
|
return next();
|
|
18
37
|
});
|
package/dist/middleware/cors.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import cors from 'cors';
|
|
2
|
-
import env from '../env.js';
|
|
3
3
|
let corsMiddleware = (_req, _res, next) => next();
|
|
4
|
+
const env = useEnv();
|
|
4
5
|
if (env['CORS_ENABLED'] === true) {
|
|
5
6
|
corsMiddleware = cors({
|
|
6
7
|
origin: env['CORS_ORIGIN'] || true,
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { isDirectusError } from '@directus/errors';
|
|
1
|
+
import { ErrorCode, MethodNotAllowedError, isDirectusError } from '@directus/errors';
|
|
2
2
|
import { toArray } from '@directus/utils';
|
|
3
|
+
import { getNodeEnv } from '@directus/utils/node';
|
|
3
4
|
import getDatabase from '../database/index.js';
|
|
4
5
|
import emitter from '../emitter.js';
|
|
5
|
-
import
|
|
6
|
-
import { ErrorCode, MethodNotAllowedError } from '@directus/errors';
|
|
7
|
-
import logger from '../logger.js';
|
|
6
|
+
import { useLogger } from '../logger.js';
|
|
8
7
|
// Note: keep all 4 parameters here. That's how Express recognizes it's the error handler, even if
|
|
9
8
|
// we don't use next
|
|
10
9
|
const errorHandler = (err, req, res, _next) => {
|
|
10
|
+
const logger = useLogger();
|
|
11
11
|
let payload = {
|
|
12
12
|
errors: [],
|
|
13
13
|
};
|
|
14
14
|
const errors = toArray(err);
|
|
15
15
|
let status = null;
|
|
16
16
|
for (const err of errors) {
|
|
17
|
-
if (
|
|
17
|
+
if (getNodeEnv() === 'development') {
|
|
18
18
|
err.extensions = {
|
|
19
19
|
...(err.extensions || {}),
|
|
20
20
|
stack: err.stack,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
2
|
import { HitRateLimitError } from '@directus/errors';
|
|
3
|
-
import
|
|
3
|
+
import { useLogger } from '../logger.js';
|
|
4
4
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
5
5
|
import asyncHandler from '../utils/async-handler.js';
|
|
6
6
|
import { validateEnv } from '../utils/validate-env.js';
|
|
7
7
|
const RATE_LIMITER_GLOBAL_KEY = 'global-rate-limit';
|
|
8
|
+
const env = useEnv();
|
|
9
|
+
const logger = useLogger();
|
|
8
10
|
let checkRateLimit = (_req, _res, next) => next();
|
|
9
11
|
export let rateLimiterGlobal;
|
|
10
12
|
if (env['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
2
|
import { HitRateLimitError } from '@directus/errors';
|
|
3
3
|
import { createRateLimiter } from '../rate-limiter.js';
|
|
4
4
|
import asyncHandler from '../utils/async-handler.js';
|
|
@@ -6,6 +6,7 @@ import { getIPFromReq } from '../utils/get-ip-from-req.js';
|
|
|
6
6
|
import { validateEnv } from '../utils/validate-env.js';
|
|
7
7
|
let checkRateLimit = (_req, _res, next) => next();
|
|
8
8
|
export let rateLimiter;
|
|
9
|
+
const env = useEnv();
|
|
9
10
|
if (env['RATE_LIMITER_ENABLED'] === true) {
|
|
10
11
|
validateEnv(['RATE_LIMITER_STORE', 'RATE_LIMITER_DURATION', 'RATE_LIMITER_POINTS']);
|
|
11
12
|
rateLimiter = createRateLimiter('RATE_LIMITER');
|