@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.
Files changed (192) hide show
  1. package/dist/ai/chat/models/chat-request.js +48 -48
  2. package/dist/ai/chat/models/object-request.js +6 -6
  3. package/dist/ai/chat/models/providers.js +14 -14
  4. package/dist/ai/chat/utils/parse-json-schema-7.js +22 -22
  5. package/dist/ai/mcp/server.js +44 -6
  6. package/dist/ai/mcp/utils.js +31 -0
  7. package/dist/ai/tools/assets/index.js +3 -3
  8. package/dist/ai/tools/collections/index.js +18 -18
  9. package/dist/ai/tools/fields/index.js +18 -18
  10. package/dist/ai/tools/files/index.js +18 -18
  11. package/dist/ai/tools/flows/index.js +16 -16
  12. package/dist/ai/tools/folders/index.js +18 -18
  13. package/dist/ai/tools/items/index.js +17 -17
  14. package/dist/ai/tools/operations/index.js +16 -16
  15. package/dist/ai/tools/relations/index.js +22 -22
  16. package/dist/ai/tools/schema/index.js +3 -3
  17. package/dist/ai/tools/schema.js +159 -159
  18. package/dist/ai/tools/system/index.js +3 -3
  19. package/dist/ai/tools/trigger-flow/index.js +3 -3
  20. package/dist/app.js +35 -11
  21. package/dist/auth/drivers/ldap.js +3 -1
  22. package/dist/auth/drivers/local.js +2 -0
  23. package/dist/auth/drivers/oauth2.js +3 -1
  24. package/dist/auth/drivers/openid.js +3 -1
  25. package/dist/auth/drivers/saml.js +2 -0
  26. package/dist/auth/utils/check-local-disabled.js +16 -0
  27. package/dist/auth/utils/check-sso-enabled.js +14 -0
  28. package/dist/auth.js +8 -5
  29. package/dist/cli/commands/bootstrap/index.js +3 -0
  30. package/dist/cli/commands/cache/clear.js +6 -1
  31. package/dist/cli/commands/roles/create.js +4 -1
  32. package/dist/cli/commands/users/create.js +3 -0
  33. package/dist/constants.js +8 -1
  34. package/dist/controllers/access.js +1 -1
  35. package/dist/controllers/activity.js +2 -1
  36. package/dist/controllers/assets.js +2 -0
  37. package/dist/controllers/auth.js +13 -5
  38. package/dist/controllers/collections.js +1 -1
  39. package/dist/controllers/comments.js +1 -1
  40. package/dist/controllers/dashboards.js +1 -1
  41. package/dist/controllers/fields.js +1 -1
  42. package/dist/controllers/files.js +3 -1
  43. package/dist/controllers/flows.js +6 -5
  44. package/dist/controllers/folders.js +1 -1
  45. package/dist/controllers/graphql.js +2 -0
  46. package/dist/controllers/items.js +3 -1
  47. package/dist/controllers/license.js +119 -0
  48. package/dist/controllers/mcp/index.js +38 -0
  49. package/dist/controllers/mcp/oauth-clients.js +68 -0
  50. package/dist/controllers/mcp/oauth-consent-page.js +316 -0
  51. package/dist/controllers/mcp/oauth.js +381 -0
  52. package/dist/controllers/mcp/templates/oauth-consent.liquid +62 -0
  53. package/dist/controllers/mcp/templates/oauth-error.liquid +28 -0
  54. package/dist/controllers/notifications.js +1 -1
  55. package/dist/controllers/operations.js +1 -1
  56. package/dist/controllers/panels.js +1 -1
  57. package/dist/controllers/permissions.js +1 -1
  58. package/dist/controllers/policies.js +1 -1
  59. package/dist/controllers/presets.js +1 -1
  60. package/dist/controllers/revisions.js +3 -2
  61. package/dist/controllers/roles.js +1 -1
  62. package/dist/controllers/server.js +38 -10
  63. package/dist/controllers/shares.js +1 -1
  64. package/dist/controllers/translations.js +1 -1
  65. package/dist/controllers/users.js +1 -1
  66. package/dist/controllers/utils.js +2 -2
  67. package/dist/controllers/versions.js +12 -5
  68. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +10 -1
  69. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -1
  70. package/dist/database/helpers/fn/dialects/mysql.js +7 -12
  71. package/dist/database/helpers/fn/dialects/oracle.js +3 -4
  72. package/dist/database/helpers/fn/dialects/postgres.js +4 -26
  73. package/dist/database/helpers/fn/json/mysql-json-path.js +22 -0
  74. package/dist/database/helpers/fn/json/parse-function.js +14 -6
  75. package/dist/database/helpers/fn/json/postgres-json-path.js +54 -0
  76. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +4 -4
  77. package/dist/database/migrations/20260217A-null-item-versions.js +14 -0
  78. package/dist/database/migrations/20260312A-add-ai-translation-settings.js +18 -0
  79. package/dist/database/migrations/20260507A-add-licensing.js +22 -0
  80. package/dist/database/migrations/20260512A-add-autosave-revision-interval.js +14 -0
  81. package/dist/database/migrations/20260512B-add-mcp-oauth.js +87 -0
  82. package/dist/database/run-ast/lib/apply-query/filter/operator.js +116 -33
  83. package/dist/database/run-ast/lib/apply-query/index.js +4 -1
  84. package/dist/database/run-ast/lib/apply-query/sort.js +17 -7
  85. package/dist/database/run-ast/lib/get-db-query.js +21 -9
  86. package/dist/database/run-ast/lib/parse-current-level.js +2 -1
  87. package/dist/database/run-ast/run-ast.js +2 -1
  88. package/dist/database/run-ast/utils/get-column.js +2 -1
  89. package/dist/database/run-ast/utils/merge-with-parent-items.js +5 -3
  90. package/dist/extensions/lib/installation/manager.js +1 -1
  91. package/dist/extensions/lib/sandbox/register/operation.js +1 -1
  92. package/dist/extensions/lib/sync/sync.js +1 -1
  93. package/dist/extensions/manager.js +3 -3
  94. package/dist/flows.js +5 -5
  95. package/dist/license/entitlements/lib/collections.js +37 -0
  96. package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
  97. package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
  98. package/dist/license/entitlements/lib/flows.js +29 -0
  99. package/dist/license/entitlements/lib/seats.js +103 -0
  100. package/dist/license/entitlements/lib/sso-enabled.js +45 -0
  101. package/dist/license/entitlements/manager.js +256 -0
  102. package/dist/license/index.js +4 -0
  103. package/dist/license/manager.js +505 -0
  104. package/dist/license/utils/compute-license-status.js +27 -0
  105. package/dist/license/utils/get-core-grace-expires-at.js +38 -0
  106. package/dist/license/utils/get-license-key.js +23 -0
  107. package/dist/license/utils/get-license-token.js +23 -0
  108. package/dist/license/utils/handle-license-error.js +41 -0
  109. package/dist/license/utils/is-in-core-grace-period.js +11 -0
  110. package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
  111. package/dist/license/utils/use-rpc.js +33 -0
  112. package/dist/middleware/cache.js +4 -1
  113. package/dist/middleware/error-handler.js +11 -0
  114. package/dist/middleware/extract-token.js +11 -2
  115. package/dist/middleware/is-admin.js +16 -0
  116. package/dist/middleware/is-locked.js +16 -0
  117. package/dist/middleware/mcp-oauth-guard.js +23 -0
  118. package/dist/middleware/request-counter.js +5 -2
  119. package/dist/packages/types/dist/index.js +117 -122
  120. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
  121. package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
  122. package/dist/request/is-denied-ip.js +2 -0
  123. package/dist/schedules/license.js +31 -0
  124. package/dist/schedules/oauth-cleanup.js +26 -0
  125. package/dist/schedules/retention.js +1 -1
  126. package/dist/schedules/telemetry.js +4 -1
  127. package/dist/schedules/tus.js +1 -1
  128. package/dist/schedules/utils/duration-to-cron.js +36 -0
  129. package/dist/services/activity.js +15 -0
  130. package/dist/services/authentication.js +12 -5
  131. package/dist/services/collections.js +40 -10
  132. package/dist/services/fields.js +6 -6
  133. package/dist/services/flows.js +12 -0
  134. package/dist/services/graphql/resolvers/system-admin.js +2 -2
  135. package/dist/services/graphql/resolvers/system-global.js +1 -1
  136. package/dist/services/graphql/resolvers/system.js +43 -27
  137. package/dist/services/graphql/schema/get-types.js +28 -7
  138. package/dist/services/graphql/schema/parse-query.js +8 -0
  139. package/dist/services/graphql/schema/read.js +12 -0
  140. package/dist/services/graphql/types/json-filter.js +30 -0
  141. package/dist/services/index.js +6 -6
  142. package/dist/services/items.js +32 -14
  143. package/dist/services/mcp-oauth/cimd.js +307 -0
  144. package/dist/services/mcp-oauth/index.js +1185 -0
  145. package/dist/services/mcp-oauth/types/error.js +22 -0
  146. package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
  147. package/dist/services/mcp-oauth/utils/domain.js +21 -0
  148. package/dist/services/mcp-oauth/utils/loopback.js +11 -0
  149. package/dist/services/mcp-oauth/utils/redirect.js +84 -0
  150. package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
  151. package/dist/services/payload.js +2 -1
  152. package/dist/services/permissions.js +31 -9
  153. package/dist/services/revisions.js +15 -0
  154. package/dist/services/server.js +66 -68
  155. package/dist/services/settings.js +37 -3
  156. package/dist/services/users.js +23 -6
  157. package/dist/services/utils.js +6 -1
  158. package/dist/services/versions.js +160 -70
  159. package/dist/utils/calculate-field-depth.js +1 -0
  160. package/dist/utils/create-admin.js +3 -3
  161. package/dist/utils/deep-freeze.js +24 -0
  162. package/dist/utils/extract-function-name.js +13 -0
  163. package/dist/utils/generate-translations.js +5 -5
  164. package/dist/utils/get-accountability-for-token.js +13 -1
  165. package/dist/utils/get-cache-key.js +1 -1
  166. package/dist/utils/get-history-filter-query.js +22 -0
  167. package/dist/utils/get-schema.js +2 -2
  168. package/dist/utils/get-service.js +3 -3
  169. package/dist/utils/is-admin.js +9 -0
  170. package/dist/utils/is-unauthenticated.js +15 -0
  171. package/dist/utils/parse-oauth-scope.js +12 -0
  172. package/dist/utils/sanitize-query.js +2 -2
  173. package/dist/utils/split-field-path.js +29 -0
  174. package/dist/utils/store.js +1 -1
  175. package/dist/utils/transaction.js +2 -2
  176. package/dist/utils/translations-validation.js +2 -2
  177. package/dist/utils/validate-query.js +35 -4
  178. package/dist/utils/validate-user-count-integrity.js +28 -5
  179. package/dist/utils/verify-session-jwt.js +5 -2
  180. package/dist/utils/versioning/handle-version.js +131 -48
  181. package/dist/utils/versioning/remove-circular.js +17 -0
  182. package/dist/websocket/authenticate.js +2 -1
  183. package/dist/websocket/collab/collab.js +1 -1
  184. package/dist/websocket/collab/room.js +1 -1
  185. package/dist/websocket/controllers/base.js +12 -0
  186. package/dist/websocket/controllers/graphql.js +1 -1
  187. package/dist/websocket/handlers/subscribe.js +1 -1
  188. package/dist/websocket/messages.js +64 -64
  189. package/dist/websocket/utils/items.js +2 -2
  190. package/license +90 -80
  191. package/package.json +33 -32
  192. package/dist/controllers/mcp.js +0 -31
@@ -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 setupComplete = await this.isSetupCompleted();
42
- info["project"] = await this.settingsService.readSingleton({ fields: [
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["setupCompleted"] = setupComplete;
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 || !setupComplete) info["version"] = version;
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
- testCache(),
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
- if (this.accountability?.admin !== true) return { status: data.status };
139
- else return data;
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 testCache() {
173
- if (env["CACHE_ENABLED"] !== true) return {};
174
- const { cache } = getCache();
175
- const checks = { "cache:responseTime": [{
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 cache.set(`directus-health-${checkID}`, true, 5);
185
- await cache.delete(`directus-health-${checkID}`);
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["rateLimiterGlobal:responseTime"][0].status = "error";
234
- checks["rateLimiterGlobal:responseTime"][0].output = err;
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["rateLimiterGlobal:responseTime"][0].observedValue = +(endTime - startTime).toFixed(3);
238
- if (checks["rateLimiterGlobal:responseTime"][0].observedValue > checks["rateLimiterGlobal:responseTime"][0].threshold && checks["rateLimiterGlobal:responseTime"][0].status !== "error") checks["rateLimiterGlobal:responseTime"][0].status = "warn";
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
- project_status: null
58
+ project_usage: data.project_usage,
59
+ org_name: data.org_name
26
60
  });
27
61
  }
28
62
  };
@@ -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.All;
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
- await new UsersService({
275
+ const service = new UsersService({
262
276
  knex: this.knex,
263
277
  schema: this.schema
264
- }).updateOne(user.id, {
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: "active"
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, env["SECRET"], {
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, env["SECRET"]);
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" });
@@ -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) await clearSystemCache({ forced: true });
79
+ if (system) {
80
+ await clearSystemCache({ forced: true });
81
+ await getEntitlementManager().clearCache();
82
+ }
78
83
  return cache?.clear();
79
84
  }
80
85
  };