@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,8 +1,8 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { parse as parseBytesConfiguration } from 'bytes';
|
|
2
3
|
import { assign } from 'lodash-es';
|
|
3
4
|
import { getCache, setCacheValue } from '../cache.js';
|
|
4
|
-
import
|
|
5
|
-
import logger from '../logger.js';
|
|
5
|
+
import { useLogger } from '../logger.js';
|
|
6
6
|
import { ExportService } from '../services/import-export/index.js';
|
|
7
7
|
import { VersionsService } from '../services/versions.js';
|
|
8
8
|
import asyncHandler from '../utils/async-handler.js';
|
|
@@ -12,6 +12,8 @@ import { getDateFormatted } from '../utils/get-date-formatted.js';
|
|
|
12
12
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
13
13
|
import { stringByteSize } from '../utils/get-string-byte-size.js';
|
|
14
14
|
export const respond = asyncHandler(async (req, res) => {
|
|
15
|
+
const env = useEnv();
|
|
16
|
+
const logger = useLogger();
|
|
15
17
|
const { cache } = getCache();
|
|
16
18
|
let exceedsMaxSize = false;
|
|
17
19
|
if (env['CACHE_VALUE_MAX_SIZE'] !== false) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineOperationApi } from '@directus/extensions';
|
|
2
2
|
import { optionToString } from '@directus/utils';
|
|
3
|
-
import
|
|
3
|
+
import { useLogger } from '../../logger.js';
|
|
4
4
|
export default defineOperationApi({
|
|
5
5
|
id: 'log',
|
|
6
6
|
handler: ({ message }) => {
|
|
7
|
+
const logger = useLogger();
|
|
7
8
|
logger.info(optionToString(message));
|
|
8
9
|
},
|
|
9
10
|
});
|
package/dist/rate-limiter.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IRateLimiterOptions, IRateLimiterStoreOptions, RateLimiterAbstract } from 'rate-limiter-flexible';
|
|
2
|
+
import { RateLimiterRes } from 'rate-limiter-flexible';
|
|
2
3
|
type IRateLimiterOptionsOverrides = Partial<IRateLimiterOptions> | Partial<IRateLimiterStoreOptions>;
|
|
3
4
|
export declare function createRateLimiter(configPrefix?: string, configOverrides?: IRateLimiterOptionsOverrides): RateLimiterAbstract;
|
|
4
|
-
export {};
|
|
5
|
+
export { RateLimiterRes };
|
package/dist/rate-limiter.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { merge } from 'lodash-es';
|
|
2
|
-
import { RateLimiterMemory, RateLimiterRedis } from 'rate-limiter-flexible';
|
|
3
|
-
import env from './env.js';
|
|
3
|
+
import { RateLimiterMemory, RateLimiterRedis, RateLimiterRes } from 'rate-limiter-flexible';
|
|
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);
|
|
7
7
|
export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides) {
|
|
8
|
+
const env = useEnv();
|
|
8
9
|
switch (env['RATE_LIMITER_STORE']) {
|
|
9
10
|
case 'redis':
|
|
10
11
|
return new RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
|
|
@@ -13,10 +14,12 @@ export function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides
|
|
|
13
14
|
return new RateLimiterMemory(getConfig('memory', configPrefix, configOverrides));
|
|
14
15
|
}
|
|
15
16
|
}
|
|
17
|
+
export { RateLimiterRes };
|
|
16
18
|
function getConfig(store = 'memory', configPrefix = 'RATE_LIMITER', overrides) {
|
|
17
19
|
const config = getConfigFromEnv(`${configPrefix}_`, `${configPrefix}_${store}_`);
|
|
18
20
|
if (store === 'redis') {
|
|
19
21
|
const Redis = require('ioredis');
|
|
22
|
+
const env = useEnv();
|
|
20
23
|
config.storeClient = new Redis(env[`REDIS`] || getConfigFromEnv(`REDIS_`));
|
|
21
24
|
}
|
|
22
25
|
delete config.enabled;
|
package/dist/redis/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { createRedis } from './lib/create-redis.js';
|
|
2
|
+
export { useRedis } from './lib/use-redis.js';
|
|
3
|
+
export { redisConfigAvailable } from './utils/redis-config-available.js';
|
package/dist/redis/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { createRedis } from './lib/create-redis.js';
|
|
2
|
+
export { useRedis } from './lib/use-redis.js';
|
|
3
|
+
export { redisConfigAvailable } from './utils/redis-config-available.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { Redis } from 'ioredis';
|
|
2
|
-
import {
|
|
3
|
-
import { getConfigFromEnv } from '../utils/get-config-from-env.js';
|
|
3
|
+
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
4
4
|
/**
|
|
5
5
|
* Create a new Redis instance based on the global env configuration
|
|
6
6
|
*
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
/**
|
|
3
|
+
* Check if Redis configuration exists in the current project's environment configuration
|
|
4
|
+
*/
|
|
5
|
+
export const redisConfigAvailable = () => {
|
|
6
|
+
const env = useEnv();
|
|
7
|
+
return 'REDIS' in env || Object.keys(env).some((key) => key.startsWith('REDIS_'));
|
|
8
|
+
};
|
|
@@ -2,9 +2,10 @@ import axios from 'axios';
|
|
|
2
2
|
import { lookup } from 'node:dns/promises';
|
|
3
3
|
import { isIP } from 'node:net';
|
|
4
4
|
import { URL } from 'node:url';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
5
|
+
import { useLogger } from '../logger.js';
|
|
6
|
+
import { validateIp } from './validate-ip.js';
|
|
7
7
|
export const requestInterceptor = async (config) => {
|
|
8
|
+
const logger = useLogger();
|
|
8
9
|
const uri = axios.getUri(config);
|
|
9
10
|
const { hostname } = new URL(uri);
|
|
10
11
|
let ip;
|
|
@@ -13,14 +14,15 @@ export const requestInterceptor = async (config) => {
|
|
|
13
14
|
const dns = await lookup(hostname);
|
|
14
15
|
ip = dns.address;
|
|
15
16
|
}
|
|
16
|
-
catch (
|
|
17
|
-
logger.warn(
|
|
17
|
+
catch (error) {
|
|
18
|
+
logger.warn(`Couldn't lookup the DNS for URL "${uri}"`);
|
|
19
|
+
logger.warn(error);
|
|
18
20
|
throw new Error(`Requested URL "${uri}" resolves to a denied IP address`);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
else {
|
|
22
24
|
ip = hostname;
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
validateIp(ip, uri);
|
|
25
27
|
return config;
|
|
26
28
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateIp } from './validate-ip.js';
|
|
2
2
|
export const responseInterceptor = async (config) => {
|
|
3
|
-
|
|
3
|
+
validateIp(config.request.socket.remoteAddress, config.request.url);
|
|
4
4
|
return config;
|
|
5
5
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare function validateIp(ip: string, url: string): void;
|
|
@@ -1,19 +1,35 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import os from 'node:os';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { useLogger } from '../logger.js';
|
|
4
|
+
import { ipInNetworks } from '../utils/ip-in-networks.js';
|
|
5
|
+
const deniedError = (url) => new Error(`Requested URL "${url}" resolves to a denied IP address`);
|
|
6
|
+
export function validateIp(ip, url) {
|
|
7
|
+
const env = useEnv();
|
|
8
|
+
const logger = useLogger();
|
|
9
|
+
const ipDenyList = env['IMPORT_IP_DENY_LIST'];
|
|
10
|
+
if (ipDenyList.length === 0)
|
|
11
|
+
return;
|
|
12
|
+
let denied;
|
|
13
|
+
try {
|
|
14
|
+
denied = ipInNetworks(ip, ipDenyList);
|
|
6
15
|
}
|
|
7
|
-
|
|
16
|
+
catch (error) {
|
|
17
|
+
logger.warn(`Invalid "IMPORT_IP_DENY_LIST" configuration`);
|
|
18
|
+
logger.warn(error);
|
|
19
|
+
throw deniedError(url);
|
|
20
|
+
}
|
|
21
|
+
if (denied)
|
|
22
|
+
throw deniedError(url);
|
|
23
|
+
if (ipDenyList.includes('0.0.0.0')) {
|
|
8
24
|
const networkInterfaces = os.networkInterfaces();
|
|
9
25
|
for (const networkInfo of Object.values(networkInterfaces)) {
|
|
10
26
|
if (!networkInfo)
|
|
11
27
|
continue;
|
|
12
28
|
for (const info of networkInfo) {
|
|
13
29
|
if (info.address === ip) {
|
|
14
|
-
throw
|
|
30
|
+
throw deniedError(url);
|
|
15
31
|
}
|
|
16
32
|
}
|
|
17
33
|
}
|
|
18
34
|
}
|
|
19
|
-
}
|
|
35
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { toBoolean } from '@directus/utils';
|
|
3
|
+
import { getNodeEnv } from '@directus/utils/node';
|
|
1
4
|
import { createTerminus } from '@godaddy/terminus';
|
|
2
5
|
import * as http from 'http';
|
|
3
6
|
import * as https from 'https';
|
|
@@ -7,13 +10,14 @@ import url from 'url';
|
|
|
7
10
|
import createApp from './app.js';
|
|
8
11
|
import getDatabase from './database/index.js';
|
|
9
12
|
import emitter from './emitter.js';
|
|
10
|
-
import
|
|
11
|
-
import logger from './logger.js';
|
|
13
|
+
import { useLogger } from './logger.js';
|
|
12
14
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
13
|
-
import {
|
|
15
|
+
import { getIPFromReq } from './utils/get-ip-from-req.js';
|
|
14
16
|
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
15
17
|
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
16
18
|
export let SERVER_ONLINE = true;
|
|
19
|
+
const env = useEnv();
|
|
20
|
+
const logger = useLogger();
|
|
17
21
|
export async function createServer() {
|
|
18
22
|
const server = http.createServer(await createApp());
|
|
19
23
|
Object.assign(server, getConfigFromEnv('SERVER_'));
|
|
@@ -57,7 +61,7 @@ export async function createServer() {
|
|
|
57
61
|
size: metrics.out,
|
|
58
62
|
headers: res.getHeaders(),
|
|
59
63
|
},
|
|
60
|
-
ip: req
|
|
64
|
+
ip: getIPFromReq(req),
|
|
61
65
|
duration: elapsedMilliseconds.toFixed(),
|
|
62
66
|
};
|
|
63
67
|
emitter.emitAction('response', info, {
|
|
@@ -86,7 +90,7 @@ export async function createServer() {
|
|
|
86
90
|
createTerminus(server, terminusOptions);
|
|
87
91
|
return server;
|
|
88
92
|
async function beforeShutdown() {
|
|
89
|
-
if (
|
|
93
|
+
if (getNodeEnv() !== 'development') {
|
|
90
94
|
logger.info('Shutting down...');
|
|
91
95
|
}
|
|
92
96
|
SERVER_ONLINE = false;
|
|
@@ -104,7 +108,7 @@ export async function createServer() {
|
|
|
104
108
|
schema: null,
|
|
105
109
|
accountability: null,
|
|
106
110
|
});
|
|
107
|
-
if (
|
|
111
|
+
if (getNodeEnv() !== 'development') {
|
|
108
112
|
logger.info('Directus shut down OK. Bye bye!');
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -112,7 +116,7 @@ export async function createServer() {
|
|
|
112
116
|
export async function startServer() {
|
|
113
117
|
const server = await createServer();
|
|
114
118
|
const host = env['HOST'];
|
|
115
|
-
const port = env['PORT'];
|
|
119
|
+
const port = parseInt(env['PORT']);
|
|
116
120
|
server
|
|
117
121
|
.listen(port, host, () => {
|
|
118
122
|
logger.info(`Server started at http://${host}:${port}`);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
|
-
import {
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
3
|
+
import { ErrorCode, isDirectusError } from '@directus/errors';
|
|
3
4
|
import { uniq } from 'lodash-es';
|
|
4
5
|
import validateUUID from 'uuid-validate';
|
|
5
|
-
import
|
|
6
|
-
import { ErrorCode } from '@directus/errors';
|
|
7
|
-
import logger from '../logger.js';
|
|
6
|
+
import { useLogger } from '../logger.js';
|
|
8
7
|
import { getPermissions } from '../utils/get-permissions.js';
|
|
9
8
|
import { Url } from '../utils/url.js';
|
|
10
9
|
import { userName } from '../utils/user-name.js';
|
|
@@ -12,6 +11,8 @@ import { AuthorizationService } from './authorization.js';
|
|
|
12
11
|
import { ItemsService } from './items.js';
|
|
13
12
|
import { NotificationsService } from './notifications.js';
|
|
14
13
|
import { UsersService } from './users.js';
|
|
14
|
+
const env = useEnv();
|
|
15
|
+
const logger = useLogger();
|
|
15
16
|
export class ActivityService extends ItemsService {
|
|
16
17
|
notificationsService;
|
|
17
18
|
usersService;
|
|
@@ -5,10 +5,12 @@ import type { Knex } from 'knex';
|
|
|
5
5
|
import type { Readable } from 'node:stream';
|
|
6
6
|
import type { AbstractServiceOptions, TransformationSet } from '../types/index.js';
|
|
7
7
|
import { AuthorizationService } from './authorization.js';
|
|
8
|
+
import { FilesService } from './files.js';
|
|
8
9
|
export declare class AssetsService {
|
|
9
10
|
knex: Knex;
|
|
10
11
|
accountability: Accountability | null;
|
|
11
12
|
authorizationService: AuthorizationService;
|
|
13
|
+
filesService: FilesService;
|
|
12
14
|
constructor(options: AbstractServiceOptions);
|
|
13
15
|
getAsset(id: string, transformation?: TransformationSet, range?: Range): Promise<{
|
|
14
16
|
stream: Readable;
|
package/dist/services/assets.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
|
|
1
3
|
import { clamp } from 'lodash-es';
|
|
2
4
|
import { contentType } from 'mime-types';
|
|
3
5
|
import hash from 'object-hash';
|
|
@@ -6,20 +8,23 @@ import sharp from 'sharp';
|
|
|
6
8
|
import validateUUID from 'uuid-validate';
|
|
7
9
|
import { SUPPORTED_IMAGE_TRANSFORM_FORMATS } from '../constants.js';
|
|
8
10
|
import getDatabase from '../database/index.js';
|
|
9
|
-
import
|
|
10
|
-
import { ForbiddenError, IllegalAssetTransformationError, RangeNotSatisfiableError, ServiceUnavailableError, } from '@directus/errors';
|
|
11
|
-
import logger from '../logger.js';
|
|
11
|
+
import { useLogger } from '../logger.js';
|
|
12
12
|
import { getStorage } from '../storage/index.js';
|
|
13
13
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
14
14
|
import * as TransformationUtils from '../utils/transformations.js';
|
|
15
15
|
import { AuthorizationService } from './authorization.js';
|
|
16
|
+
import { FilesService } from './files.js';
|
|
17
|
+
const env = useEnv();
|
|
18
|
+
const logger = useLogger();
|
|
16
19
|
export class AssetsService {
|
|
17
20
|
knex;
|
|
18
21
|
accountability;
|
|
19
22
|
authorizationService;
|
|
23
|
+
filesService;
|
|
20
24
|
constructor(options) {
|
|
21
25
|
this.knex = options.knex || getDatabase();
|
|
22
26
|
this.accountability = options.accountability || null;
|
|
27
|
+
this.filesService = new FilesService(options);
|
|
23
28
|
this.authorizationService = new AuthorizationService(options);
|
|
24
29
|
}
|
|
25
30
|
async getAsset(id, transformation, range) {
|
|
@@ -40,7 +45,7 @@ export class AssetsService {
|
|
|
40
45
|
if (systemPublicKeys.includes(id) === false && this.accountability?.admin !== true) {
|
|
41
46
|
await this.authorizationService.checkAccess('read', 'directus_files', id);
|
|
42
47
|
}
|
|
43
|
-
const file = (await this.
|
|
48
|
+
const file = (await this.filesService.readOne(id, { limit: 1 }));
|
|
44
49
|
if (!file)
|
|
45
50
|
throw new ForbiddenError();
|
|
46
51
|
const exists = await storage.location(file.storage).exists(file.filename_disk);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Action } from '@directus/constants';
|
|
2
|
+
import { useEnv } from '@directus/env';
|
|
3
|
+
import { InvalidCredentialsError, InvalidOtpError, InvalidProviderError, ServiceUnavailableError, UserSuspendedError, } from '@directus/errors';
|
|
2
4
|
import jwt from 'jsonwebtoken';
|
|
3
5
|
import { clone, cloneDeep } from 'lodash-es';
|
|
4
6
|
import { performance } from 'perf_hooks';
|
|
@@ -6,15 +8,13 @@ import { getAuthProvider } from '../auth.js';
|
|
|
6
8
|
import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
|
|
7
9
|
import getDatabase from '../database/index.js';
|
|
8
10
|
import emitter from '../emitter.js';
|
|
9
|
-
import
|
|
10
|
-
import { InvalidCredentialsError, InvalidProviderError, UserSuspendedError } from '@directus/errors';
|
|
11
|
-
import { InvalidOtpError } from '@directus/errors';
|
|
12
|
-
import { createRateLimiter } from '../rate-limiter.js';
|
|
11
|
+
import { RateLimiterRes, createRateLimiter } from '../rate-limiter.js';
|
|
13
12
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
14
13
|
import { stall } from '../utils/stall.js';
|
|
15
14
|
import { ActivityService } from './activity.js';
|
|
16
15
|
import { SettingsService } from './settings.js';
|
|
17
16
|
import { TFAService } from './tfa.js';
|
|
17
|
+
const env = useEnv();
|
|
18
18
|
const loginAttemptsLimiter = createRateLimiter('RATE_LIMITER', { duration: 0 });
|
|
19
19
|
export class AuthenticationService {
|
|
20
20
|
knex;
|
|
@@ -100,11 +100,19 @@ export class AuthenticationService {
|
|
|
100
100
|
try {
|
|
101
101
|
await loginAttemptsLimiter.consume(user.id);
|
|
102
102
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error instanceof RateLimiterRes && error.remainingPoints === 0) {
|
|
105
|
+
await this.knex('directus_users').update({ status: 'suspended' }).where({ id: user.id });
|
|
106
|
+
user.status = 'suspended';
|
|
107
|
+
// This means that new attempts after the user has been re-activated will be accepted
|
|
108
|
+
await loginAttemptsLimiter.set(user.id, 0, 0);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw new ServiceUnavailableError({
|
|
112
|
+
service: 'authentication',
|
|
113
|
+
reason: 'Rate limiter unreachable',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
try {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
1
3
|
import { createInspector } from '@directus/schema';
|
|
2
4
|
import { addFieldFlag } from '@directus/utils';
|
|
3
5
|
import { chunk, groupBy, merge, omit } from 'lodash-es';
|
|
@@ -7,12 +9,10 @@ import { getHelpers } from '../database/helpers/index.js';
|
|
|
7
9
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
8
10
|
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
9
11
|
import emitter from '../emitter.js';
|
|
10
|
-
import env from '../env.js';
|
|
11
|
-
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
12
|
-
import { FieldsService } from './fields.js';
|
|
13
|
-
import { ItemsService } from './items.js';
|
|
14
12
|
import { getSchema } from '../utils/get-schema.js';
|
|
15
13
|
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
14
|
+
import { FieldsService } from './fields.js';
|
|
15
|
+
import { ItemsService } from './items.js';
|
|
16
16
|
export class CollectionsService {
|
|
17
17
|
knex;
|
|
18
18
|
helpers;
|
|
@@ -206,6 +206,7 @@ export class CollectionsService {
|
|
|
206
206
|
* Read all collections. Currently doesn't support any query.
|
|
207
207
|
*/
|
|
208
208
|
async readByQuery() {
|
|
209
|
+
const env = useEnv();
|
|
209
210
|
const collectionItemsService = new ItemsService('directus_collections', {
|
|
210
211
|
knex: this.knex,
|
|
211
212
|
schema: this.schema,
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import type { ApiOutput, ExtensionSettings } from '@directus/extensions';
|
|
2
|
-
import type { SchemaInspector } from '@directus/schema';
|
|
3
2
|
import type { Accountability, DeepPartial, SchemaOverview } from '@directus/types';
|
|
4
|
-
import type Keyv from 'keyv';
|
|
5
3
|
import type { Knex } from 'knex';
|
|
6
|
-
import type { Helpers } from '../database/helpers/index.js';
|
|
7
4
|
import type { ExtensionManager } from '../extensions/manager.js';
|
|
8
5
|
import type { AbstractServiceOptions } from '../types/index.js';
|
|
9
6
|
import { ItemsService } from './items.js';
|
|
10
|
-
|
|
7
|
+
export declare class ExtensionReadError extends Error {
|
|
8
|
+
originalError: unknown;
|
|
9
|
+
constructor(originalError: unknown);
|
|
10
|
+
}
|
|
11
11
|
export declare class ExtensionsService {
|
|
12
12
|
knex: Knex;
|
|
13
|
-
permissionsService: PermissionsService;
|
|
14
|
-
schemaInspector: SchemaInspector;
|
|
15
13
|
accountability: Accountability | null;
|
|
16
14
|
schema: SchemaOverview;
|
|
17
15
|
extensionsItemService: ItemsService<ExtensionSettings>;
|
|
18
|
-
systemCache: Keyv<any>;
|
|
19
|
-
helpers: Helpers;
|
|
20
16
|
extensionsManager: ExtensionManager;
|
|
21
17
|
constructor(options: AbstractServiceOptions);
|
|
22
18
|
readAll(): Promise<ApiOutput[]>;
|
|
23
19
|
readOne(bundle: string | null, name: string): Promise<ApiOutput>;
|
|
24
|
-
updateOne(bundle: string | null, name: string, data: DeepPartial<ApiOutput>): Promise<
|
|
20
|
+
updateOne(bundle: string | null, name: string, data: DeepPartial<ApiOutput>): Promise<ApiOutput>;
|
|
25
21
|
private getKey;
|
|
22
|
+
/**
|
|
23
|
+
* Sync a bundles enabled status
|
|
24
|
+
* - If the extension or extensions parent is not a bundle changes are skipped
|
|
25
|
+
* - If a bundles status is toggled, all children are set to that status
|
|
26
|
+
* - If an entries status is toggled, then if the:
|
|
27
|
+
* - Parent bundle is non-partial throws UnprocessableContentError
|
|
28
|
+
* - Entry status change resulted in all children being disabled then the parent bundle is disabled
|
|
29
|
+
* - Entry status change resulted in at least one child being enabled then the parent bundle is enabled
|
|
30
|
+
*/
|
|
31
|
+
private checkBundleAndSyncStatus;
|
|
26
32
|
/**
|
|
27
33
|
* Combine the settings stored in the database with the information available from the installed
|
|
28
34
|
* extensions into the standardized extensions api output
|
|
@@ -1,50 +1,39 @@
|
|
|
1
|
-
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
|
|
2
|
-
import {
|
|
3
|
-
import Joi from 'joi';
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError, UnprocessableContentError } from '@directus/errors';
|
|
2
|
+
import { isObject } from '@directus/utils';
|
|
4
3
|
import { omit, pick } from 'lodash-es';
|
|
5
|
-
import
|
|
6
|
-
import { getHelpers } from '../database/helpers/index.js';
|
|
7
|
-
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
4
|
+
import getDatabase from '../database/index.js';
|
|
8
5
|
import { getExtensionManager } from '../extensions/index.js';
|
|
9
6
|
import { ItemsService } from './items.js';
|
|
10
|
-
|
|
7
|
+
export class ExtensionReadError extends Error {
|
|
8
|
+
originalError;
|
|
9
|
+
constructor(originalError) {
|
|
10
|
+
super();
|
|
11
|
+
this.originalError = originalError;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
11
14
|
export class ExtensionsService {
|
|
12
15
|
knex;
|
|
13
|
-
permissionsService;
|
|
14
|
-
schemaInspector;
|
|
15
16
|
accountability;
|
|
16
17
|
schema;
|
|
17
18
|
extensionsItemService;
|
|
18
|
-
systemCache;
|
|
19
|
-
helpers;
|
|
20
19
|
extensionsManager;
|
|
21
20
|
constructor(options) {
|
|
22
21
|
this.knex = options.knex || getDatabase();
|
|
23
|
-
this.permissionsService = new PermissionsService(options);
|
|
24
|
-
this.schemaInspector = options.knex ? createInspector(options.knex) : getSchemaInspector();
|
|
25
22
|
this.schema = options.schema;
|
|
26
23
|
this.accountability = options.accountability || null;
|
|
27
24
|
this.extensionsManager = getExtensionManager();
|
|
28
25
|
this.extensionsItemService = new ItemsService('directus_extensions', {
|
|
29
26
|
knex: this.knex,
|
|
30
27
|
schema: this.schema,
|
|
31
|
-
|
|
28
|
+
accountability: this.accountability,
|
|
32
29
|
});
|
|
33
|
-
this.systemCache = getCache().systemCache;
|
|
34
|
-
this.helpers = getHelpers(this.knex);
|
|
35
30
|
}
|
|
36
31
|
async readAll() {
|
|
37
|
-
if (this.accountability?.admin !== true) {
|
|
38
|
-
throw new ForbiddenError();
|
|
39
|
-
}
|
|
40
32
|
const installedExtensions = this.extensionsManager.getExtensions();
|
|
41
33
|
const configuredExtensions = await this.extensionsItemService.readByQuery({ limit: -1 });
|
|
42
34
|
return this.stitch(installedExtensions, configuredExtensions);
|
|
43
35
|
}
|
|
44
36
|
async readOne(bundle, name) {
|
|
45
|
-
if (this.accountability?.admin !== true) {
|
|
46
|
-
throw new ForbiddenError();
|
|
47
|
-
}
|
|
48
37
|
const key = this.getKey(bundle, name);
|
|
49
38
|
const schema = this.extensionsManager.getExtensions().find((extension) => extension.name === (bundle ?? name));
|
|
50
39
|
const meta = await this.extensionsItemService.readOne(key);
|
|
@@ -54,27 +43,73 @@ export class ExtensionsService {
|
|
|
54
43
|
throw new ForbiddenError();
|
|
55
44
|
}
|
|
56
45
|
async updateOne(bundle, name, data) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
const result = await this.knex.transaction(async (trx) => {
|
|
47
|
+
if (!isObject(data.meta)) {
|
|
48
|
+
throw new InvalidPayloadError({ reason: `"meta" is required` });
|
|
49
|
+
}
|
|
50
|
+
const service = new ExtensionsService({
|
|
51
|
+
knex: trx,
|
|
52
|
+
accountability: this.accountability,
|
|
53
|
+
schema: this.schema,
|
|
54
|
+
});
|
|
55
|
+
const key = this.getKey(bundle, name);
|
|
56
|
+
await service.extensionsItemService.updateOne(key, data.meta);
|
|
57
|
+
let extension;
|
|
58
|
+
try {
|
|
59
|
+
extension = await service.readOne(bundle, name);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new ExtensionReadError(error);
|
|
63
|
+
}
|
|
64
|
+
if ('enabled' in data.meta) {
|
|
65
|
+
await service.checkBundleAndSyncStatus(trx, extension);
|
|
66
|
+
}
|
|
67
|
+
return extension;
|
|
65
68
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new InvalidPayloadError({ reason: error.message });
|
|
69
|
-
}
|
|
70
|
-
if ('meta' in data && 'enabled' in data.meta) {
|
|
71
|
-
await this.knex('directus_extensions').update({ enabled: data.meta.enabled }).where({ name: key });
|
|
72
|
-
this.extensionsManager.reload();
|
|
73
|
-
}
|
|
69
|
+
this.extensionsManager.reload();
|
|
70
|
+
return result;
|
|
74
71
|
}
|
|
75
72
|
getKey(bundle, name) {
|
|
76
73
|
return bundle ? `${bundle}/${name}` : name;
|
|
77
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Sync a bundles enabled status
|
|
77
|
+
* - If the extension or extensions parent is not a bundle changes are skipped
|
|
78
|
+
* - If a bundles status is toggled, all children are set to that status
|
|
79
|
+
* - If an entries status is toggled, then if the:
|
|
80
|
+
* - Parent bundle is non-partial throws UnprocessableContentError
|
|
81
|
+
* - Entry status change resulted in all children being disabled then the parent bundle is disabled
|
|
82
|
+
* - Entry status change resulted in at least one child being enabled then the parent bundle is enabled
|
|
83
|
+
*/
|
|
84
|
+
async checkBundleAndSyncStatus(trx, extension) {
|
|
85
|
+
if (extension.bundle === null) {
|
|
86
|
+
if (extension.schema?.type === 'bundle') {
|
|
87
|
+
await trx('directus_extensions')
|
|
88
|
+
.update({ enabled: extension.meta.enabled })
|
|
89
|
+
.where('name', 'LIKE', this.getKey(extension.name, '%'));
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const parent = await this.readOne(null, extension.bundle);
|
|
94
|
+
if (parent.schema?.type !== 'bundle') {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (parent.schema.partial === false) {
|
|
98
|
+
throw new UnprocessableContentError({
|
|
99
|
+
reason: 'Unable to toggle status of an entry for a bundle marked as non partial',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const child = await trx('directus_extensions')
|
|
103
|
+
.where('name', 'LIKE', this.getKey(extension.bundle, '%'))
|
|
104
|
+
.where({ enabled: true })
|
|
105
|
+
.first();
|
|
106
|
+
if (!child && parent.meta.enabled) {
|
|
107
|
+
await trx('directus_extensions').update({ enabled: false }).where({ name: parent.name });
|
|
108
|
+
}
|
|
109
|
+
else if (child && !parent.meta.enabled) {
|
|
110
|
+
await trx('directus_extensions').update({ enabled: true }).where({ name: parent.name });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
78
113
|
/**
|
|
79
114
|
* Combine the settings stored in the database with the information available from the installed
|
|
80
115
|
* extensions into the standardized extensions api output
|
|
@@ -128,7 +163,7 @@ export class ExtensionsService {
|
|
|
128
163
|
return {
|
|
129
164
|
name,
|
|
130
165
|
bundle: bundleName,
|
|
131
|
-
schema: schema ? pick(schema, 'type', 'local', 'version') : null,
|
|
166
|
+
schema: schema ? pick(schema, 'type', 'local', 'version', 'partial') : null,
|
|
132
167
|
meta: omit(meta, 'name'),
|
|
133
168
|
};
|
|
134
169
|
});
|
package/dist/services/fields.js
CHANGED
|
@@ -319,14 +319,19 @@ export class FieldsService {
|
|
|
319
319
|
}
|
|
320
320
|
if (hookAdjustedField.schema) {
|
|
321
321
|
const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
|
|
322
|
+
if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) {
|
|
323
|
+
throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
|
|
324
|
+
}
|
|
322
325
|
// Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
|
|
323
326
|
const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
|
|
324
327
|
if (!isEqual(columnToCompare, hookAdjustedField.schema)) {
|
|
325
328
|
try {
|
|
326
|
-
await this.knex.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
await this.knex.transaction(async (trx) => {
|
|
330
|
+
await trx.schema.alterTable(collection, async (table) => {
|
|
331
|
+
if (!hookAdjustedField.schema)
|
|
332
|
+
return;
|
|
333
|
+
this.addColumnToTable(table, field, existingColumn);
|
|
334
|
+
});
|
|
330
335
|
});
|
|
331
336
|
}
|
|
332
337
|
catch (err) {
|