@directus/api 35.2.0 → 36.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/chat/models/chat-request.js +48 -48
- package/dist/ai/chat/models/object-request.js +6 -6
- package/dist/ai/chat/models/providers.js +14 -14
- package/dist/ai/chat/utils/parse-json-schema-7.js +22 -22
- package/dist/ai/mcp/server.js +44 -6
- package/dist/ai/mcp/utils.js +31 -0
- package/dist/ai/tools/assets/index.js +3 -3
- package/dist/ai/tools/collections/index.js +18 -18
- package/dist/ai/tools/fields/index.js +18 -18
- package/dist/ai/tools/files/index.js +18 -18
- package/dist/ai/tools/flows/index.js +16 -16
- package/dist/ai/tools/folders/index.js +18 -18
- package/dist/ai/tools/items/index.js +17 -17
- package/dist/ai/tools/operations/index.js +16 -16
- package/dist/ai/tools/relations/index.js +22 -22
- package/dist/ai/tools/schema/index.js +3 -3
- package/dist/ai/tools/schema.js +159 -159
- package/dist/ai/tools/system/index.js +3 -3
- package/dist/ai/tools/trigger-flow/index.js +3 -3
- package/dist/app.js +35 -11
- package/dist/auth/drivers/ldap.js +3 -1
- package/dist/auth/drivers/local.js +2 -0
- package/dist/auth/drivers/oauth2.js +3 -1
- package/dist/auth/drivers/openid.js +3 -1
- package/dist/auth/drivers/saml.js +2 -0
- package/dist/auth/utils/check-local-disabled.js +16 -0
- package/dist/auth/utils/check-sso-enabled.js +14 -0
- package/dist/auth.js +8 -5
- package/dist/cli/commands/bootstrap/index.js +3 -0
- package/dist/cli/commands/cache/clear.js +6 -1
- package/dist/cli/commands/roles/create.js +4 -1
- package/dist/cli/commands/users/create.js +3 -0
- package/dist/constants.js +8 -1
- package/dist/controllers/access.js +1 -1
- package/dist/controllers/activity.js +2 -1
- package/dist/controllers/assets.js +2 -0
- package/dist/controllers/auth.js +13 -5
- package/dist/controllers/collections.js +1 -1
- package/dist/controllers/comments.js +1 -1
- package/dist/controllers/dashboards.js +1 -1
- package/dist/controllers/fields.js +1 -1
- package/dist/controllers/files.js +3 -1
- package/dist/controllers/flows.js +6 -5
- package/dist/controllers/folders.js +1 -1
- package/dist/controllers/graphql.js +2 -0
- package/dist/controllers/items.js +3 -1
- package/dist/controllers/license.js +119 -0
- package/dist/controllers/mcp/index.js +38 -0
- package/dist/controllers/mcp/oauth-clients.js +68 -0
- package/dist/controllers/mcp/oauth-consent-page.js +316 -0
- package/dist/controllers/mcp/oauth.js +381 -0
- package/dist/controllers/mcp/templates/oauth-consent.liquid +62 -0
- package/dist/controllers/mcp/templates/oauth-error.liquid +28 -0
- package/dist/controllers/notifications.js +1 -1
- package/dist/controllers/operations.js +1 -1
- package/dist/controllers/panels.js +1 -1
- package/dist/controllers/permissions.js +1 -1
- package/dist/controllers/policies.js +1 -1
- package/dist/controllers/presets.js +1 -1
- package/dist/controllers/revisions.js +3 -2
- package/dist/controllers/roles.js +1 -1
- package/dist/controllers/server.js +38 -10
- package/dist/controllers/shares.js +1 -1
- package/dist/controllers/translations.js +1 -1
- package/dist/controllers/users.js +1 -1
- package/dist/controllers/utils.js +2 -2
- package/dist/controllers/versions.js +12 -5
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +10 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -1
- package/dist/database/helpers/fn/dialects/mysql.js +7 -12
- package/dist/database/helpers/fn/dialects/oracle.js +3 -4
- package/dist/database/helpers/fn/dialects/postgres.js +4 -26
- package/dist/database/helpers/fn/json/mysql-json-path.js +22 -0
- package/dist/database/helpers/fn/json/parse-function.js +14 -6
- package/dist/database/helpers/fn/json/postgres-json-path.js +54 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.js +4 -4
- package/dist/database/migrations/20260217A-null-item-versions.js +14 -0
- package/dist/database/migrations/20260312A-add-ai-translation-settings.js +18 -0
- package/dist/database/migrations/20260507A-add-licensing.js +22 -0
- package/dist/database/migrations/20260512A-add-autosave-revision-interval.js +14 -0
- package/dist/database/migrations/20260512B-add-mcp-oauth.js +87 -0
- package/dist/database/run-ast/lib/apply-query/filter/operator.js +116 -33
- package/dist/database/run-ast/lib/apply-query/index.js +4 -1
- package/dist/database/run-ast/lib/apply-query/sort.js +17 -7
- package/dist/database/run-ast/lib/get-db-query.js +21 -9
- package/dist/database/run-ast/lib/parse-current-level.js +2 -1
- package/dist/database/run-ast/run-ast.js +2 -1
- package/dist/database/run-ast/utils/get-column.js +2 -1
- package/dist/database/run-ast/utils/merge-with-parent-items.js +5 -3
- package/dist/extensions/lib/installation/manager.js +1 -1
- package/dist/extensions/lib/sandbox/register/operation.js +1 -1
- package/dist/extensions/lib/sync/sync.js +1 -1
- package/dist/extensions/manager.js +3 -3
- package/dist/flows.js +5 -5
- package/dist/license/entitlements/lib/collections.js +37 -0
- package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
- package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
- package/dist/license/entitlements/lib/flows.js +29 -0
- package/dist/license/entitlements/lib/seats.js +103 -0
- package/dist/license/entitlements/lib/sso-enabled.js +45 -0
- package/dist/license/entitlements/manager.js +256 -0
- package/dist/license/index.js +4 -0
- package/dist/license/manager.js +505 -0
- package/dist/license/utils/compute-license-status.js +27 -0
- package/dist/license/utils/get-core-grace-expires-at.js +38 -0
- package/dist/license/utils/get-license-key.js +23 -0
- package/dist/license/utils/get-license-token.js +23 -0
- package/dist/license/utils/handle-license-error.js +41 -0
- package/dist/license/utils/is-in-core-grace-period.js +11 -0
- package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
- package/dist/license/utils/use-rpc.js +33 -0
- package/dist/middleware/cache.js +4 -1
- package/dist/middleware/error-handler.js +11 -0
- package/dist/middleware/extract-token.js +11 -2
- package/dist/middleware/is-admin.js +16 -0
- package/dist/middleware/is-locked.js +16 -0
- package/dist/middleware/mcp-oauth-guard.js +23 -0
- package/dist/middleware/request-counter.js +5 -2
- package/dist/packages/types/dist/index.js +117 -122
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
- package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
- package/dist/request/is-denied-ip.js +2 -0
- package/dist/schedules/license.js +31 -0
- package/dist/schedules/oauth-cleanup.js +26 -0
- package/dist/schedules/retention.js +1 -1
- package/dist/schedules/telemetry.js +4 -1
- package/dist/schedules/tus.js +1 -1
- package/dist/schedules/utils/duration-to-cron.js +36 -0
- package/dist/services/activity.js +15 -0
- package/dist/services/authentication.js +12 -5
- package/dist/services/collections.js +40 -10
- package/dist/services/fields.js +6 -6
- package/dist/services/flows.js +12 -0
- package/dist/services/graphql/resolvers/system-admin.js +2 -2
- package/dist/services/graphql/resolvers/system-global.js +1 -1
- package/dist/services/graphql/resolvers/system.js +43 -27
- package/dist/services/graphql/schema/get-types.js +28 -7
- package/dist/services/graphql/schema/parse-query.js +8 -0
- package/dist/services/graphql/schema/read.js +12 -0
- package/dist/services/graphql/types/json-filter.js +30 -0
- package/dist/services/index.js +6 -6
- package/dist/services/items.js +32 -14
- package/dist/services/mcp-oauth/cimd.js +307 -0
- package/dist/services/mcp-oauth/index.js +1185 -0
- package/dist/services/mcp-oauth/types/error.js +22 -0
- package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
- package/dist/services/mcp-oauth/utils/domain.js +21 -0
- package/dist/services/mcp-oauth/utils/loopback.js +11 -0
- package/dist/services/mcp-oauth/utils/redirect.js +84 -0
- package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
- package/dist/services/payload.js +2 -1
- package/dist/services/permissions.js +31 -9
- package/dist/services/revisions.js +15 -0
- package/dist/services/server.js +66 -68
- package/dist/services/settings.js +37 -3
- package/dist/services/users.js +23 -6
- package/dist/services/utils.js +6 -1
- package/dist/services/versions.js +160 -70
- package/dist/utils/calculate-field-depth.js +1 -0
- package/dist/utils/create-admin.js +3 -3
- package/dist/utils/deep-freeze.js +24 -0
- package/dist/utils/extract-function-name.js +13 -0
- package/dist/utils/generate-translations.js +5 -5
- package/dist/utils/get-accountability-for-token.js +13 -1
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-history-filter-query.js +22 -0
- package/dist/utils/get-schema.js +2 -2
- package/dist/utils/get-service.js +3 -3
- package/dist/utils/is-admin.js +9 -0
- package/dist/utils/is-unauthenticated.js +15 -0
- package/dist/utils/parse-oauth-scope.js +12 -0
- package/dist/utils/sanitize-query.js +2 -2
- package/dist/utils/split-field-path.js +29 -0
- package/dist/utils/store.js +1 -1
- package/dist/utils/transaction.js +2 -2
- package/dist/utils/translations-validation.js +2 -2
- package/dist/utils/validate-query.js +35 -4
- package/dist/utils/validate-user-count-integrity.js +28 -5
- package/dist/utils/verify-session-jwt.js +5 -2
- package/dist/utils/versioning/handle-version.js +131 -48
- package/dist/utils/versioning/remove-circular.js +17 -0
- package/dist/websocket/authenticate.js +2 -1
- package/dist/websocket/collab/collab.js +1 -1
- package/dist/websocket/collab/room.js +1 -1
- package/dist/websocket/controllers/base.js +12 -0
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/handlers/subscribe.js +1 -1
- package/dist/websocket/messages.js +64 -64
- package/dist/websocket/utils/items.js +2 -2
- package/license +90 -80
- package/package.json +33 -32
- package/dist/controllers/mcp.js +0 -31
package/dist/services/server.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import { useRedis } from "../redis/lib/use-redis.js";
|
|
2
|
+
import { redisConfigAvailable } from "../redis/utils/redis-config-available.js";
|
|
3
|
+
import "../redis/index.js";
|
|
1
4
|
import { useLogger } from "../logger/index.js";
|
|
5
|
+
import { getMilliseconds } from "../utils/get-milliseconds.js";
|
|
2
6
|
import { FILE_UPLOADS, RESUMABLE_UPLOADS } from "../constants.js";
|
|
3
|
-
import { getCache } from "../cache.js";
|
|
4
7
|
import { getStorage } from "../storage/index.js";
|
|
5
8
|
import database_default, { hasDatabaseConnection } from "../database/index.js";
|
|
9
|
+
import { useStore } from "../utils/store.js";
|
|
6
10
|
import getMailer from "../mailer.js";
|
|
11
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
7
12
|
import { SettingsService } from "./settings.js";
|
|
8
|
-
import { rateLimiterGlobal } from "../middleware/rate-limiter-global.js";
|
|
9
|
-
import { rateLimiter } from "../middleware/rate-limiter-ip.js";
|
|
10
13
|
import { getAllowedLogLevels } from "../utils/get-allowed-log-levels.js";
|
|
11
14
|
import { SERVER_ONLINE } from "../server.js";
|
|
15
|
+
import { isUnauthenticated } from "../utils/is-unauthenticated.js";
|
|
16
|
+
import { getLicenseManager } from "../license/manager.js";
|
|
17
|
+
import "../license/index.js";
|
|
12
18
|
import { useEnv } from "@directus/env";
|
|
19
|
+
import { ForbiddenError } from "@directus/errors";
|
|
13
20
|
import { toArray, toBoolean } from "@directus/utils";
|
|
14
21
|
import { merge } from "lodash-es";
|
|
22
|
+
import { createKv } from "@directus/memory";
|
|
15
23
|
import { performance } from "perf_hooks";
|
|
16
24
|
import { Readable } from "node:stream";
|
|
17
25
|
import { version } from "directus/version";
|
|
@@ -19,6 +27,8 @@ import { version } from "directus/version";
|
|
|
19
27
|
//#region src/services/server.ts
|
|
20
28
|
const env = useEnv();
|
|
21
29
|
const logger = useLogger();
|
|
30
|
+
const HEALTHCHECK_CACHE_TTL = getMilliseconds(env["HEALTHCHECK_CACHE_TTL"], 3e5);
|
|
31
|
+
const store = useStore(env["HEALTHCHECK_NAMESPACE"] ?? "directus:healthcheck", { ttl: HEALTHCHECK_CACHE_TTL });
|
|
22
32
|
var ServerService = class {
|
|
23
33
|
knex;
|
|
24
34
|
accountability;
|
|
@@ -38,9 +48,11 @@ var ServerService = class {
|
|
|
38
48
|
}
|
|
39
49
|
async serverInfo() {
|
|
40
50
|
const info = {};
|
|
41
|
-
const
|
|
42
|
-
|
|
51
|
+
const licenseManager = getLicenseManager();
|
|
52
|
+
const isSetupCompleted = await this.isSetupCompleted();
|
|
53
|
+
const { project_owner,...projectInfo } = await this.settingsService.readSingleton({ fields: [
|
|
43
54
|
"project_name",
|
|
55
|
+
"project_owner",
|
|
44
56
|
"project_descriptor",
|
|
45
57
|
"project_logo",
|
|
46
58
|
"project_color",
|
|
@@ -59,10 +71,18 @@ var ServerService = class {
|
|
|
59
71
|
"public_registration",
|
|
60
72
|
"public_registration_verify_email"
|
|
61
73
|
] });
|
|
62
|
-
info["
|
|
74
|
+
info["project"] = projectInfo;
|
|
75
|
+
if (!isSetupCompleted) info["setup"] = {
|
|
76
|
+
license_complete: licenseManager.getSource() !== null,
|
|
77
|
+
owner_complete: Boolean(project_owner)
|
|
78
|
+
};
|
|
63
79
|
if (this.accountability?.user) {
|
|
64
80
|
info["mcp_enabled"] = toBoolean(env["MCP_ENABLED"] ?? true);
|
|
65
81
|
info["ai_enabled"] = toBoolean(env["AI_ENABLED"] ?? true);
|
|
82
|
+
info["mcp_oauth_enabled"] = toBoolean(env["MCP_OAUTH_ENABLED"] ?? false);
|
|
83
|
+
info["mcp_oauth_dcr_enabled"] = toBoolean(env["MCP_OAUTH_DCR_ENABLED"] ?? false);
|
|
84
|
+
info["mcp_oauth_cimd_enabled"] = toBoolean(env["MCP_OAUTH_CIMD_ENABLED"] ?? false);
|
|
85
|
+
info["autoSave"] = { revisionInterval: Number(env["AUTOSAVE_REVISION_INTERVAL"]) };
|
|
66
86
|
info["files"] = { mimeTypeAllowList: toArray(env["FILES_MIME_TYPE_ALLOW_LIST"]) };
|
|
67
87
|
if (env["RATE_LIMITER_ENABLED"]) info["rateLimit"] = {
|
|
68
88
|
points: env["RATE_LIMITER_POINTS"],
|
|
@@ -100,21 +120,33 @@ var ServerService = class {
|
|
|
100
120
|
chunkSize: RESUMABLE_UPLOADS.CHUNK_SIZE
|
|
101
121
|
};
|
|
102
122
|
}
|
|
103
|
-
if (this.accountability?.user || !
|
|
123
|
+
if (this.accountability?.user || !isSetupCompleted) info["version"] = version;
|
|
124
|
+
info["license"] = {
|
|
125
|
+
source: licenseManager.getSource(),
|
|
126
|
+
entitlements: getEntitlementManager().getAppEntitlements()
|
|
127
|
+
};
|
|
104
128
|
return info;
|
|
105
129
|
}
|
|
106
130
|
async health() {
|
|
131
|
+
if (isUnauthenticated(this.accountability)) throw new ForbiddenError();
|
|
132
|
+
const healthResult = await store(async (store$1) => {
|
|
133
|
+
try {
|
|
134
|
+
return await store$1.get("health");
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.warn(err, "Failed to read health check cache");
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (healthResult) return this.accountability?.admin === true ? healthResult : { status: healthResult["status"] };
|
|
107
140
|
const { nanoid } = await import("nanoid");
|
|
108
141
|
const checkID = nanoid(5);
|
|
142
|
+
const enabledServices = toArray(env["HEALTHCHECK_SERVICES"]);
|
|
109
143
|
const data = {
|
|
110
144
|
status: "ok",
|
|
111
145
|
releaseId: version,
|
|
112
146
|
serviceId: env["PUBLIC_URL"],
|
|
113
147
|
checks: merge(...await Promise.all([
|
|
114
148
|
testDatabase(),
|
|
115
|
-
|
|
116
|
-
testRateLimiter(),
|
|
117
|
-
testRateLimiterGlobal(),
|
|
149
|
+
testRedis(),
|
|
118
150
|
testStorage(),
|
|
119
151
|
testEmail()
|
|
120
152
|
]))
|
|
@@ -135,9 +167,14 @@ var ServerService = class {
|
|
|
135
167
|
}
|
|
136
168
|
if (data.status === "error") break;
|
|
137
169
|
}
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
await store(async (store$1) => {
|
|
171
|
+
await store$1.set("health", data).catch((err) => {
|
|
172
|
+
logger.warn(err, "Failed to write health check cache");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return this.accountability?.admin === true ? data : { status: data.status };
|
|
140
176
|
async function testDatabase() {
|
|
177
|
+
if (enabledServices.includes("database") === false) return {};
|
|
141
178
|
const database = database_default();
|
|
142
179
|
const client = env["DB_CLIENT"];
|
|
143
180
|
const checks = {};
|
|
@@ -169,10 +206,15 @@ var ServerService = class {
|
|
|
169
206
|
}];
|
|
170
207
|
return checks;
|
|
171
208
|
}
|
|
172
|
-
async function
|
|
173
|
-
if (
|
|
174
|
-
const
|
|
175
|
-
|
|
209
|
+
async function testRedis() {
|
|
210
|
+
if (enabledServices.includes("redis") === false || redisConfigAvailable() !== true) return {};
|
|
211
|
+
const redis = createKv({
|
|
212
|
+
type: "redis",
|
|
213
|
+
redis: useRedis(),
|
|
214
|
+
namespace: env["HEALTHCHECK_NAMESPACE"] ?? "directus:healthcheck",
|
|
215
|
+
ttl: HEALTHCHECK_CACHE_TTL
|
|
216
|
+
});
|
|
217
|
+
const checks = { "redis:responseTime": [{
|
|
176
218
|
status: "ok",
|
|
177
219
|
componentType: "cache",
|
|
178
220
|
observedValue: 0,
|
|
@@ -181,65 +223,20 @@ var ServerService = class {
|
|
|
181
223
|
}] };
|
|
182
224
|
const startTime = performance.now();
|
|
183
225
|
try {
|
|
184
|
-
await
|
|
185
|
-
await
|
|
186
|
-
} catch (err) {
|
|
187
|
-
checks["cache:responseTime"][0].status = "error";
|
|
188
|
-
checks["cache:responseTime"][0].output = err;
|
|
189
|
-
} finally {
|
|
190
|
-
const endTime = performance.now();
|
|
191
|
-
checks["cache:responseTime"][0].observedValue = +(endTime - startTime).toFixed(3);
|
|
192
|
-
if (checks["cache:responseTime"][0].observedValue > checks["cache:responseTime"][0].threshold && checks["cache:responseTime"][0].status !== "error") checks["cache:responseTime"][0].status = "warn";
|
|
193
|
-
}
|
|
194
|
-
return checks;
|
|
195
|
-
}
|
|
196
|
-
async function testRateLimiter() {
|
|
197
|
-
if (env["RATE_LIMITER_ENABLED"] !== true) return {};
|
|
198
|
-
const checks = { "rateLimiter:responseTime": [{
|
|
199
|
-
status: "ok",
|
|
200
|
-
componentType: "ratelimiter",
|
|
201
|
-
observedValue: 0,
|
|
202
|
-
observedUnit: "ms",
|
|
203
|
-
threshold: env["RATE_LIMITER_HEALTHCHECK_THRESHOLD"] ? +env["RATE_LIMITER_HEALTHCHECK_THRESHOLD"] : 150
|
|
204
|
-
}] };
|
|
205
|
-
const startTime = performance.now();
|
|
206
|
-
try {
|
|
207
|
-
await rateLimiter.consume(`directus-health-${checkID}`, 1);
|
|
208
|
-
await rateLimiter.delete(`directus-health-${checkID}`);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
checks["rateLimiter:responseTime"][0].status = "error";
|
|
211
|
-
checks["rateLimiter:responseTime"][0].output = err;
|
|
212
|
-
} finally {
|
|
213
|
-
const endTime = performance.now();
|
|
214
|
-
checks["rateLimiter:responseTime"][0].observedValue = +(endTime - startTime).toFixed(3);
|
|
215
|
-
if (checks["rateLimiter:responseTime"][0].observedValue > checks["rateLimiter:responseTime"][0].threshold && checks["rateLimiter:responseTime"][0].status !== "error") checks["rateLimiter:responseTime"][0].status = "warn";
|
|
216
|
-
}
|
|
217
|
-
return checks;
|
|
218
|
-
}
|
|
219
|
-
async function testRateLimiterGlobal() {
|
|
220
|
-
if (env["RATE_LIMITER_GLOBAL_ENABLED"] !== true) return {};
|
|
221
|
-
const checks = { "rateLimiterGlobal:responseTime": [{
|
|
222
|
-
status: "ok",
|
|
223
|
-
componentType: "ratelimiter",
|
|
224
|
-
observedValue: 0,
|
|
225
|
-
observedUnit: "ms",
|
|
226
|
-
threshold: env["RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD"] ? +env["RATE_LIMITER_GLOBAL_HEALTHCHECK_THRESHOLD"] : 150
|
|
227
|
-
}] };
|
|
228
|
-
const startTime = performance.now();
|
|
229
|
-
try {
|
|
230
|
-
await rateLimiterGlobal.consume(`directus-health-${checkID}`, 1);
|
|
231
|
-
await rateLimiterGlobal.delete(`directus-health-${checkID}`);
|
|
226
|
+
await redis.set(`directus-health-${checkID}`, 1);
|
|
227
|
+
await redis.delete(`directus-health-${checkID}`);
|
|
232
228
|
} catch (err) {
|
|
233
|
-
checks["
|
|
234
|
-
checks["
|
|
229
|
+
checks["redis:responseTime"][0].status = "error";
|
|
230
|
+
checks["redis:responseTime"][0].output = err;
|
|
235
231
|
} finally {
|
|
236
232
|
const endTime = performance.now();
|
|
237
|
-
checks["
|
|
238
|
-
if (checks["
|
|
233
|
+
checks["redis:responseTime"][0].observedValue = +(endTime - startTime).toFixed(3);
|
|
234
|
+
if (checks["redis:responseTime"][0].observedValue > checks["redis:responseTime"][0].threshold && checks["redis:responseTime"][0].status !== "error") checks["redis:responseTime"][0].status = "warn";
|
|
239
235
|
}
|
|
240
236
|
return checks;
|
|
241
237
|
}
|
|
242
238
|
async function testStorage() {
|
|
239
|
+
if (enabledServices.includes("storage") === false) return {};
|
|
243
240
|
const storage = await getStorage();
|
|
244
241
|
const checks = {};
|
|
245
242
|
for (const location of toArray(env["STORAGE_LOCATIONS"])) {
|
|
@@ -267,6 +264,7 @@ var ServerService = class {
|
|
|
267
264
|
return checks;
|
|
268
265
|
}
|
|
269
266
|
async function testEmail() {
|
|
267
|
+
if (enabledServices.includes("email") === false || toBoolean(env["EMAIL_VERIFY_SETUP"]) === false) return {};
|
|
270
268
|
const checks = { "email:connection": [{
|
|
271
269
|
status: "ok",
|
|
272
270
|
componentType: "email"
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { CUSTOM_LLM_FIELDS } from "../constants.js";
|
|
1
2
|
import { ItemsService } from "./items.js";
|
|
3
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
2
4
|
import { sendReport } from "../telemetry/lib/send-report.js";
|
|
3
5
|
import "../telemetry/index.js";
|
|
6
|
+
import "../license/index.js";
|
|
7
|
+
import { InvalidPayloadError, ResourceRestrictedError } from "@directus/errors";
|
|
4
8
|
import { version } from "directus/version";
|
|
5
9
|
|
|
6
10
|
//#region src/services/settings.ts
|
|
@@ -8,6 +12,37 @@ var SettingsService = class extends ItemsService {
|
|
|
8
12
|
constructor(options) {
|
|
9
13
|
super("directus_settings", options);
|
|
10
14
|
}
|
|
15
|
+
async createOne(data, opts) {
|
|
16
|
+
if (this.accountability !== null) {
|
|
17
|
+
if ("license_key" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_key" value manually` });
|
|
18
|
+
if ("license_token" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_token" value manually` });
|
|
19
|
+
}
|
|
20
|
+
const entitlementManager = getEntitlementManager();
|
|
21
|
+
const changesLLM = CUSTOM_LLM_FIELDS.some((field) => field in data && data[field] !== null);
|
|
22
|
+
if (!entitlementManager.isEntitled("custom_llms_enabled") && changesLLM) throw new ResourceRestrictedError({ category: "custom_llms_enabled" });
|
|
23
|
+
const result = await super.createOne(data, opts);
|
|
24
|
+
if (changesLLM) await getEntitlementManager().clearCache("custom_llms_enabled");
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
async updateMany(keys, data, opts) {
|
|
28
|
+
if (this.accountability !== null) {
|
|
29
|
+
if ("license_key" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_key" value manually` });
|
|
30
|
+
if ("license_token" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_token" value manually` });
|
|
31
|
+
}
|
|
32
|
+
const entitlementManager = getEntitlementManager();
|
|
33
|
+
const changesLLM = CUSTOM_LLM_FIELDS.some((field) => field in data && data[field] !== null);
|
|
34
|
+
if (!entitlementManager.isEntitled("custom_llms_enabled") && changesLLM) throw new ResourceRestrictedError({ category: "custom_llms_enabled" });
|
|
35
|
+
const result = await super.updateMany(keys, data, opts);
|
|
36
|
+
if (changesLLM) await entitlementManager.clearCache("custom_llms_enabled");
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
async readByQuery(query, opts) {
|
|
40
|
+
const data = await super.readByQuery(query, opts);
|
|
41
|
+
if (!getEntitlementManager().isEntitled("custom_llms_enabled") && this.accountability !== null) {
|
|
42
|
+
for (const record of data) for (const field of CUSTOM_LLM_FIELDS) if (record[field]) record[field] = null;
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
11
46
|
async setOwner(data) {
|
|
12
47
|
const { project_id } = await this.knex.select("project_id").from("directus_settings").first();
|
|
13
48
|
sendReport({
|
|
@@ -19,10 +54,9 @@ var SettingsService = class extends ItemsService {
|
|
|
19
54
|
});
|
|
20
55
|
return await this.upsertSingleton({
|
|
21
56
|
project_owner: data.project_owner,
|
|
22
|
-
project_usage: data.project_usage,
|
|
23
|
-
org_name: data.org_name,
|
|
24
57
|
product_updates: data.product_updates,
|
|
25
|
-
|
|
58
|
+
project_usage: data.project_usage,
|
|
59
|
+
org_name: data.org_name
|
|
26
60
|
});
|
|
27
61
|
}
|
|
28
62
|
};
|
package/dist/services/users.js
CHANGED
|
@@ -12,11 +12,14 @@ import { createDefaultAccountability } from "../permissions/utils/create-default
|
|
|
12
12
|
import isUrlAllowed from "../utils/is-url-allowed.js";
|
|
13
13
|
import { verifyJWT } from "../utils/jwt.js";
|
|
14
14
|
import { stall } from "../utils/stall.js";
|
|
15
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
15
16
|
import { SettingsService } from "./settings.js";
|
|
17
|
+
import "../license/index.js";
|
|
16
18
|
import { useEnv } from "@directus/env";
|
|
17
19
|
import { ForbiddenError, InvalidInviteError, InvalidPayloadError, RecordNotUniqueError } from "@directus/errors";
|
|
18
20
|
import { getSimpleHash, toArray, validatePayload } from "@directus/utils";
|
|
19
21
|
import { isEmpty } from "lodash-es";
|
|
22
|
+
import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
|
|
20
23
|
import { performance } from "perf_hooks";
|
|
21
24
|
import Joi from "joi";
|
|
22
25
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from "@directus/validation";
|
|
@@ -114,6 +117,12 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
/**
|
|
120
|
+
* Block setting a non-default auth provider when the current license isn't entitled to SSO
|
|
121
|
+
*/
|
|
122
|
+
checkProviderEntitlement(input) {
|
|
123
|
+
if ((Array.isArray(input) ? input : [input]).some((provider) => provider && provider !== DEFAULT_AUTH_PROVIDER) && !getEntitlementManager().isEntitled("sso_enabled")) throw new InvalidPayloadError({ reason: `Setting a custom "provider" isn't included in the current license` });
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
117
126
|
* Create a new user
|
|
118
127
|
*/
|
|
119
128
|
async createOne(data, opts = {}) {
|
|
@@ -123,6 +132,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
123
132
|
await this.checkUniqueEmails([data["email"]]);
|
|
124
133
|
}
|
|
125
134
|
if ("password" in data) await this.checkPasswordPolicy([data["password"]]);
|
|
135
|
+
if ("provider" in data) this.checkProviderEntitlement(data["provider"]);
|
|
126
136
|
} catch (err) {
|
|
127
137
|
opts.preMutationError = err;
|
|
128
138
|
}
|
|
@@ -138,6 +148,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
138
148
|
async createMany(data, opts = {}) {
|
|
139
149
|
const emails = data.map((payload) => payload["email"]).filter((email) => email);
|
|
140
150
|
const passwords = data.map((payload) => payload["password"]).filter((password) => password);
|
|
151
|
+
const providers = data.map((payload) => payload["provider"]).filter((provider) => provider);
|
|
141
152
|
const someActive = data.some((payload) => !("status" in payload) || payload["status"] === "active");
|
|
142
153
|
try {
|
|
143
154
|
if (emails.length) {
|
|
@@ -145,6 +156,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
145
156
|
await this.checkUniqueEmails(emails);
|
|
146
157
|
}
|
|
147
158
|
if (passwords.length) await this.checkPasswordPolicy(passwords);
|
|
159
|
+
if (providers.length) this.checkProviderEntitlement(providers);
|
|
148
160
|
} catch (err) {
|
|
149
161
|
opts.preMutationError = err;
|
|
150
162
|
}
|
|
@@ -176,6 +188,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
176
188
|
if (data["tfa_secret"] !== void 0) throw new InvalidPayloadError({ reason: `You can't change the "tfa_secret" value manually` });
|
|
177
189
|
if (data["provider"] !== void 0) {
|
|
178
190
|
if (this.accountability && this.accountability.admin !== true) throw new InvalidPayloadError({ reason: `You can't change the "provider" value manually` });
|
|
191
|
+
this.checkProviderEntitlement(data["provider"]);
|
|
179
192
|
data["auth_data"] = null;
|
|
180
193
|
}
|
|
181
194
|
if (data["external_identifier"] !== void 0) {
|
|
@@ -187,7 +200,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
187
200
|
}
|
|
188
201
|
if ("role" in data) opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
|
|
189
202
|
if ("status" in data) if (data["status"] === "active") opts.userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
|
|
190
|
-
else opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.
|
|
203
|
+
else opts.userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.RemainingAdmins;
|
|
191
204
|
if (opts.userIntegrityCheckFlags) opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
|
|
192
205
|
const result = await super.updateMany(keys, data, opts);
|
|
193
206
|
if (data["status"] !== void 0 && data["status"] !== "active") await this.clearUserSessions(keys);
|
|
@@ -213,6 +226,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
213
226
|
await this.knex("directus_versions").update({ user_updated: null }).whereIn("user_updated", keys);
|
|
214
227
|
await super.deleteMany(keys, opts);
|
|
215
228
|
await this.clearUserSessions(keys);
|
|
229
|
+
await getEntitlementManager().clearCache("seats", "sso_enabled");
|
|
216
230
|
return keys;
|
|
217
231
|
}
|
|
218
232
|
async inviteUser(email, role, url, subject) {
|
|
@@ -258,12 +272,15 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
258
272
|
if (scope !== "invite") throw new ForbiddenError();
|
|
259
273
|
const user = await this.getUserByEmail(email);
|
|
260
274
|
if (user?.status !== "invited") throw new InvalidInviteError();
|
|
261
|
-
|
|
275
|
+
const service = new UsersService({
|
|
262
276
|
knex: this.knex,
|
|
263
277
|
schema: this.schema
|
|
264
|
-
})
|
|
278
|
+
});
|
|
279
|
+
const { allowed: isWithinLicenseLimits } = await getEntitlementManager().check("seats");
|
|
280
|
+
const status = isWithinLicenseLimits ? "active" : USER_INACTIVE_LICENSE_STATUS;
|
|
281
|
+
await service.updateOne(user.id, {
|
|
265
282
|
password,
|
|
266
|
-
status
|
|
283
|
+
status
|
|
267
284
|
});
|
|
268
285
|
}
|
|
269
286
|
async registerUser(input) {
|
|
@@ -310,7 +327,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
310
327
|
email: input.email,
|
|
311
328
|
scope: "pending-registration"
|
|
312
329
|
};
|
|
313
|
-
const token = jwt.sign(payload,
|
|
330
|
+
const token = jwt.sign(payload, getSecret(), {
|
|
314
331
|
expiresIn: env["EMAIL_VERIFICATION_TOKEN_TTL"],
|
|
315
332
|
issuer: "directus"
|
|
316
333
|
});
|
|
@@ -334,7 +351,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
334
351
|
await stall(STALL_TIME, timeStart);
|
|
335
352
|
}
|
|
336
353
|
async verifyRegistration(token) {
|
|
337
|
-
const { email, scope } = verifyJWT(token,
|
|
354
|
+
const { email, scope } = verifyJWT(token, getSecret());
|
|
338
355
|
if (scope !== "pending-registration") throw new ForbiddenError();
|
|
339
356
|
const user = await this.getUserByEmail(email);
|
|
340
357
|
if (user?.status !== "unverified") throw new InvalidPayloadError({ reason: "Invalid verification code" });
|
package/dist/services/utils.js
CHANGED
|
@@ -4,6 +4,8 @@ import { fetchAllowedFields } from "../permissions/modules/fetch-allowed-fields/
|
|
|
4
4
|
import emitter_default from "../emitter.js";
|
|
5
5
|
import { validateAccess } from "../permissions/modules/validate-access/validate-access.js";
|
|
6
6
|
import { shouldClearCache } from "../utils/should-clear-cache.js";
|
|
7
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
8
|
+
import "../license/index.js";
|
|
7
9
|
import { ForbiddenError, InvalidPayloadError } from "@directus/errors";
|
|
8
10
|
import { systemCollectionRows } from "@directus/system-data";
|
|
9
11
|
|
|
@@ -74,7 +76,10 @@ var UtilsService = class {
|
|
|
74
76
|
async clearCache({ system }) {
|
|
75
77
|
if (this.accountability?.admin !== true) throw new ForbiddenError();
|
|
76
78
|
const { cache } = getCache();
|
|
77
|
-
if (system)
|
|
79
|
+
if (system) {
|
|
80
|
+
await clearSystemCache({ forced: true });
|
|
81
|
+
await getEntitlementManager().clearCache();
|
|
82
|
+
}
|
|
78
83
|
return cache?.clear();
|
|
79
84
|
}
|
|
80
85
|
};
|