@directus/api 22.2.0 → 23.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.
Files changed (61) hide show
  1. package/dist/auth/drivers/ldap.js +14 -3
  2. package/dist/auth/drivers/oauth2.js +13 -2
  3. package/dist/auth/drivers/openid.js +13 -2
  4. package/dist/cache.js +4 -4
  5. package/dist/cli/commands/init/questions.d.ts +5 -5
  6. package/dist/cli/commands/schema/apply.d.ts +1 -0
  7. package/dist/cli/commands/schema/apply.js +20 -1
  8. package/dist/cli/index.js +1 -0
  9. package/dist/cli/utils/create-env/env-stub.liquid +1 -4
  10. package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
  11. package/dist/database/migrations/20240806A-permissions-policies.js +1 -1
  12. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +0 -3
  13. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -2
  14. package/dist/logger/index.d.ts +2 -3
  15. package/dist/logger/logs-stream.d.ts +0 -1
  16. package/dist/mailer.js +0 -6
  17. package/dist/middleware/authenticate.d.ts +1 -3
  18. package/dist/middleware/error-handler.d.ts +0 -1
  19. package/dist/middleware/validate-batch.d.ts +1 -4
  20. package/dist/permissions/lib/fetch-permissions.d.ts +11 -1
  21. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +2 -2
  22. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +1 -2
  23. package/dist/permissions/utils/fetch-dynamic-variable-context.js +14 -6
  24. package/dist/permissions/utils/process-permissions.d.ts +11 -1
  25. package/dist/permissions/utils/process-permissions.js +6 -4
  26. package/dist/request/agent-with-ip-validation.d.ts +0 -1
  27. package/dist/server.d.ts +0 -3
  28. package/dist/services/assets.d.ts +0 -1
  29. package/dist/services/fields.js +0 -6
  30. package/dist/services/files/utils/get-metadata.d.ts +0 -1
  31. package/dist/services/files/utils/parse-image-metadata.d.ts +0 -1
  32. package/dist/services/files.d.ts +0 -1
  33. package/dist/services/import-export.d.ts +0 -1
  34. package/dist/services/tus/data-store.d.ts +0 -1
  35. package/dist/services/users.js +2 -2
  36. package/dist/types/graphql.d.ts +0 -1
  37. package/dist/utils/apply-query.d.ts +1 -1
  38. package/dist/utils/compress.d.ts +0 -1
  39. package/dist/utils/delete-from-require-cache.js +1 -1
  40. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +1 -2
  41. package/dist/utils/generate-hash.js +2 -2
  42. package/dist/utils/get-address.d.ts +0 -3
  43. package/dist/utils/get-cache-headers.d.ts +0 -1
  44. package/dist/utils/get-cache-key.d.ts +0 -1
  45. package/dist/utils/get-column.d.ts +1 -1
  46. package/dist/utils/get-graphql-query-and-variables.d.ts +0 -1
  47. package/dist/utils/get-ip-from-req.d.ts +0 -1
  48. package/dist/utils/get-snapshot.js +1 -1
  49. package/dist/utils/sanitize-query.js +1 -1
  50. package/dist/utils/should-skip-cache.d.ts +0 -1
  51. package/dist/websocket/authenticate.js +1 -1
  52. package/dist/websocket/controllers/base.d.ts +1 -10
  53. package/dist/websocket/controllers/base.js +15 -21
  54. package/dist/websocket/controllers/graphql.d.ts +0 -3
  55. package/dist/websocket/controllers/index.d.ts +0 -3
  56. package/dist/websocket/controllers/logs.d.ts +4 -5
  57. package/dist/websocket/controllers/logs.js +7 -3
  58. package/dist/websocket/controllers/rest.d.ts +0 -3
  59. package/dist/websocket/controllers/rest.js +1 -1
  60. package/dist/websocket/types.d.ts +0 -6
  61. package/package.json +41 -42
@@ -172,7 +172,7 @@ export class LDAPAuthDriver extends AuthDriver {
172
172
  }
173
173
  const logger = useLogger();
174
174
  await this.validateBindClient();
175
- const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute, defaultRoleId } = this.config;
175
+ const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute, defaultRoleId, syncUserInfo } = this.config;
176
176
  const userInfo = await this.fetchUserInfo(userDn, new ldap.EqualityFilter({
177
177
  attribute: userAttribute ?? 'cn',
178
178
  value: payload['identifier'],
@@ -201,11 +201,22 @@ export class LDAPAuthDriver extends AuthDriver {
201
201
  if (userId) {
202
202
  // Run hook so the end user has the chance to augment the
203
203
  // user that is about to be updated
204
- let updatedUserPayload = await emitter.emitFilter(`auth.update`, {}, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema: this.schema, accountability: null });
204
+ let emitPayload = {};
205
205
  // Only sync roles if the AD groups are configured
206
206
  if (groupDn) {
207
- updatedUserPayload = { role: userRole?.id ?? defaultRoleId ?? null, ...updatedUserPayload };
207
+ emitPayload = {
208
+ role: userRole?.id ?? defaultRoleId ?? null,
209
+ };
208
210
  }
211
+ if (syncUserInfo) {
212
+ emitPayload = {
213
+ ...emitPayload,
214
+ first_name: userInfo.firstName,
215
+ last_name: userInfo.lastName,
216
+ email: userInfo.email,
217
+ };
218
+ }
219
+ const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, { identifier: userInfo.dn, provider: this.config['provider'], providerPayload: { userInfo, userRole } }, { database: getDatabase(), schema: this.schema, accountability: null });
209
220
  // Update user to update properties that might have changed
210
221
  await this.usersService.updateOne(userId, updatedUserPayload);
211
222
  return userId;
@@ -106,7 +106,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
106
106
  }
107
107
  // Flatten response to support dot indexes
108
108
  userInfo = flatten(userInfo);
109
- const { provider, emailKey, identifierKey, allowPublicRegistration } = this.config;
109
+ const { provider, emailKey, identifierKey, allowPublicRegistration, syncUserInfo } = this.config;
110
110
  const email = userInfo[emailKey ?? 'email'] ? String(userInfo[emailKey ?? 'email']) : undefined;
111
111
  // Fallback to email if explicit identifier not found
112
112
  const identifier = userInfo[identifierKey] ? String(userInfo[identifierKey]) : email;
@@ -127,7 +127,18 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
127
127
  if (userId) {
128
128
  // Run hook so the end user has the chance to augment the
129
129
  // user that is about to be updated
130
- const updatedUserPayload = await emitter.emitFilter(`auth.update`, { auth_data: userPayload.auth_data }, {
130
+ let emitPayload = {
131
+ auth_data: userPayload.auth_data,
132
+ };
133
+ if (syncUserInfo) {
134
+ emitPayload = {
135
+ ...emitPayload,
136
+ first_name: userPayload.first_name,
137
+ last_name: userPayload.last_name,
138
+ email: userPayload.email,
139
+ };
140
+ }
141
+ const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
131
142
  identifier,
132
143
  provider: this.config['provider'],
133
144
  providerPayload: { accessToken: tokenSet.access_token, userInfo },
@@ -125,7 +125,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
125
125
  }
126
126
  // Flatten response to support dot indexes
127
127
  userInfo = flatten(userInfo);
128
- const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
128
+ const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail, syncUserInfo } = this.config;
129
129
  const email = userInfo['email'] ? String(userInfo['email']) : undefined;
130
130
  // Fallback to email if explicit identifier not found
131
131
  const identifier = userInfo[identifierKey ?? 'sub'] ? String(userInfo[identifierKey ?? 'sub']) : email;
@@ -146,7 +146,18 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
146
146
  if (userId) {
147
147
  // Run hook so the end user has the chance to augment the
148
148
  // user that is about to be updated
149
- const updatedUserPayload = await emitter.emitFilter(`auth.update`, { auth_data: userPayload.auth_data }, {
149
+ let emitPayload = {
150
+ auth_data: userPayload.auth_data,
151
+ };
152
+ if (syncUserInfo) {
153
+ emitPayload = {
154
+ ...emitPayload,
155
+ first_name: userPayload.first_name,
156
+ last_name: userPayload.last_name,
157
+ email: userPayload.email,
158
+ };
159
+ }
160
+ const updatedUserPayload = await emitter.emitFilter(`auth.update`, emitPayload, {
150
161
  identifier,
151
162
  provider: this.config['provider'],
152
163
  providerPayload: { accessToken: tokenSet.access_token, userInfo },
package/dist/cache.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { useEnv } from '@directus/env';
2
- import Keyv from 'keyv';
2
+ import Keyv, {} from 'keyv';
3
3
  import { useBus } from './bus/index.js';
4
4
  import { useLogger } from './logger/index.js';
5
+ import { clearCache as clearPermissionCache } from './permissions/cache.js';
5
6
  import { redisConfigAvailable } from './redis/index.js';
6
7
  import { compress, decompress } from './utils/compress.js';
7
8
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
8
9
  import { getMilliseconds } from './utils/get-milliseconds.js';
9
10
  import { validateEnv } from './utils/validate-env.js';
10
- import { clearCache as clearPermissionCache } from './permissions/cache.js';
11
11
  import { createRequire } from 'node:module';
12
12
  const logger = useLogger();
13
13
  const env = useEnv();
@@ -106,10 +106,10 @@ function getKeyvInstance(store, ttl, namespaceSuffix) {
106
106
  function getConfig(store = 'memory', ttl, namespaceSuffix = '') {
107
107
  const config = {
108
108
  namespace: `${env['CACHE_NAMESPACE']}${namespaceSuffix}`,
109
- ttl,
109
+ ...(ttl && { ttl }),
110
110
  };
111
111
  if (store === 'redis') {
112
- const KeyvRedis = require('@keyv/redis');
112
+ const { default: KeyvRedis } = require('@keyv/redis');
113
113
  config.store = new KeyvRedis(env['REDIS'] || getConfigFromEnv('REDIS'), { useRedisSets: false });
114
114
  }
115
115
  return config;
@@ -4,18 +4,18 @@ export declare const databaseQuestions: {
4
4
  filepath: string;
5
5
  }) => Record<string, string>)[];
6
6
  mysql2: (({ client }: {
7
- client: Exclude<Driver, 'sqlite3'>;
7
+ client: Exclude<Driver, "sqlite3">;
8
8
  }) => Record<string, any>)[];
9
9
  pg: (({ client }: {
10
- client: Exclude<Driver, 'sqlite3'>;
10
+ client: Exclude<Driver, "sqlite3">;
11
11
  }) => Record<string, any>)[];
12
12
  cockroachdb: (({ client }: {
13
- client: Exclude<Driver, 'sqlite3'>;
13
+ client: Exclude<Driver, "sqlite3">;
14
14
  }) => Record<string, any>)[];
15
15
  oracledb: (({ client }: {
16
- client: Exclude<Driver, 'sqlite3'>;
16
+ client: Exclude<Driver, "sqlite3">;
17
17
  }) => Record<string, any>)[];
18
18
  mssql: (({ client }: {
19
- client: Exclude<Driver, 'sqlite3'>;
19
+ client: Exclude<Driver, "sqlite3">;
20
20
  }) => Record<string, any>)[];
21
21
  };
@@ -1,4 +1,5 @@
1
1
  export declare function apply(snapshotPath: string, options?: {
2
2
  yes: boolean;
3
3
  dryRun: boolean;
4
+ ignoreRules: string;
4
5
  }): Promise<void>;
@@ -11,6 +11,22 @@ import { isNestedMetaUpdate } from '../../../utils/apply-diff.js';
11
11
  import { applySnapshot } from '../../../utils/apply-snapshot.js';
12
12
  import { getSnapshotDiff } from '../../../utils/get-snapshot-diff.js';
13
13
  import { getSnapshot } from '../../../utils/get-snapshot.js';
14
+ function filterSnapshotDiff(snapshot, filters) {
15
+ const filterSet = new Set(filters);
16
+ function shouldKeep(item) {
17
+ if (filterSet.has(item.collection))
18
+ return false;
19
+ if (item.field && filterSet.has(`${item.collection}.${item.field}`))
20
+ return false;
21
+ return true;
22
+ }
23
+ const filteredDiff = {
24
+ collections: snapshot.collections.filter((item) => shouldKeep(item)),
25
+ fields: snapshot.fields.filter((item) => shouldKeep(item)),
26
+ relations: snapshot.relations.filter((item) => shouldKeep(item)),
27
+ };
28
+ return filteredDiff;
29
+ }
14
30
  export async function apply(snapshotPath, options) {
15
31
  const logger = useLogger();
16
32
  const filename = path.resolve(process.cwd(), snapshotPath);
@@ -31,7 +47,10 @@ export async function apply(snapshotPath, options) {
31
47
  snapshot = parseJSON(fileContents);
32
48
  }
33
49
  const currentSnapshot = await getSnapshot({ database });
34
- const snapshotDiff = getSnapshotDiff(currentSnapshot, snapshot);
50
+ let snapshotDiff = getSnapshotDiff(currentSnapshot, snapshot);
51
+ if (options?.ignoreRules) {
52
+ snapshotDiff = filterSnapshotDiff(snapshotDiff, options.ignoreRules.split(','));
53
+ }
35
54
  if (snapshotDiff.collections.length === 0 &&
36
55
  snapshotDiff.fields.length === 0 &&
37
56
  snapshotDiff.relations.length === 0) {
package/dist/cli/index.js CHANGED
@@ -81,6 +81,7 @@ export async function createCli() {
81
81
  .description('Apply a snapshot file to the current database')
82
82
  .option('-y, --yes', `Assume "yes" as answer to all prompts and run non-interactively`)
83
83
  .option('-d, --dry-run', 'Plan and log changes to be applied', false)
84
+ .option('--ignoreRules <value>', `Comma-separated list of collections and or fields to ignore. Format: "products.title,reviews" this will ignore applying changes to the title field in the products collection and the entire reviews collection`)
84
85
  .argument('<path>', 'Path to snapshot file')
85
86
  .action(apply);
86
87
  await emitter.emitInit('cli.after', { program });
@@ -313,7 +313,7 @@ EXTENSIONS_AUTO_RELOAD=false
313
313
  EMAIL_FROM="no-reply@example.com"
314
314
 
315
315
  # What to use to send emails. One of
316
- # sendmail, smtp, mailgun, sendgrid, ses.
316
+ # sendmail, smtp, mailgun, ses.
317
317
  EMAIL_TRANSPORT="sendmail"
318
318
  EMAIL_SENDMAIL_NEW_LINE="unix"
319
319
  EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
@@ -340,6 +340,3 @@ EMAIL_SENDMAIL_PATH="/usr/sbin/sendmail"
340
340
  ## Email (Mailgun Transport)
341
341
  # EMAIL_MAILGUN_API_KEY="key-1234123412341234"
342
342
  # EMAIL_MAILGUN_DOMAIN="a domain name from https://app.mailgun.com/app/sending/domains"
343
-
344
- ## Email (SendGrid Transport)
345
- # EMAIL_SENDGRID_API_KEY="key-1234123412341234"
@@ -43,7 +43,7 @@ export async function up(knex) {
43
43
  .update({ [constraint.many_field]: null })
44
44
  .whereIn(currentPrimaryKeyField, ids);
45
45
  }
46
- catch (err) {
46
+ catch {
47
47
  logger.error(`${constraint.many_collection}.${constraint.many_field} contains illegal foreign keys which couldn't be set to NULL. Please fix these references and rerun this migration to complete the upgrade.`);
48
48
  if (ids.length < 25) {
49
49
  logger.error(`Items with illegal foreign keys: ${ids.join(', ')}`);
@@ -198,7 +198,7 @@ export async function up(knex) {
198
198
  table.dropForeign('role', foreignConstraint);
199
199
  });
200
200
  }
201
- catch (err) {
201
+ catch {
202
202
  logger.warn('Failed to drop foreign key constraint on `role` column in `directus_permissions` table');
203
203
  }
204
204
  await knex('directus_permissions')
@@ -1,6 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import type { ApiExtensionType, HybridExtensionType } from '@directus/extensions';
5
2
  import type { Router } from 'express';
6
3
  /**
@@ -1,9 +1,8 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { Router } from 'express';
3
2
  import type { Reference } from 'isolated-vm';
4
3
  import type { IncomingHttpHeaders } from 'node:http';
5
4
  export declare function registerRouteGenerator(endpointName: string, endpointRouter: Router): {
6
- register: (path: Reference<string>, method: Reference<'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'>, cb: Reference<(req: {
5
+ register: (path: Reference<string>, method: Reference<"GET" | "POST" | "PUT" | "PATCH" | "DELETE">, cb: Reference<(req: {
7
6
  url: string;
8
7
  headers: IncomingHttpHeaders;
9
8
  body: string;
@@ -1,4 +1,3 @@
1
- /// <reference types="qs" />
2
1
  import type { RequestHandler } from 'express';
3
2
  import { type Logger } from 'pino';
4
3
  import { LogsStream } from './logs-stream.js';
@@ -11,5 +10,5 @@ export declare const useLogger: () => Logger<never>;
11
10
  export declare const getLogsStream: (pretty: boolean) => LogsStream;
12
11
  export declare const getHttpLogsStream: (pretty: boolean) => LogsStream;
13
12
  export declare const getLoggerLevelValue: (level: string) => number;
14
- export declare const createLogger: () => Logger<never>;
15
- export declare const createExpressLogger: () => RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
13
+ export declare const createLogger: () => Logger<never, boolean>;
14
+ export declare const createExpressLogger: () => RequestHandler;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { Bus } from '@directus/memory';
3
2
  import { Writable } from 'stream';
4
3
  type PrettyType = 'basic' | 'http' | false;
package/dist/mailer.js CHANGED
@@ -56,12 +56,6 @@ export default function getMailer() {
56
56
  host: env['EMAIL_MAILGUN_HOST'] || 'api.mailgun.net',
57
57
  }));
58
58
  }
59
- else if (transportName === 'sendgrid') {
60
- const sg = require('nodemailer-sendgrid');
61
- transporter = nodemailer.createTransport(sg({
62
- apiKey: env['EMAIL_SENDGRID_API_KEY'],
63
- }));
64
- }
65
59
  else {
66
60
  logger.warn('Illegal transport given for email. Check the EMAIL_TRANSPORT env var.');
67
61
  }
@@ -1,9 +1,7 @@
1
- /// <reference types="qs" />
2
- /// <reference types="cookie-parser" />
3
1
  import type { NextFunction, Request, Response } from 'express';
4
2
  /**
5
3
  * Verify the passed JWT and assign the user ID and role to `req`
6
4
  */
7
5
  export declare const handler: (req: Request, res: Response, next: NextFunction) => Promise<void>;
8
- declare const _default: (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: NextFunction) => Promise<void>;
6
+ declare const _default: (req: Request, res: Response, next: NextFunction) => Promise<void>;
9
7
  export default _default;
@@ -1,3 +1,2 @@
1
- /// <reference types="qs" />
2
1
  import type { ErrorRequestHandler } from 'express';
3
2
  export declare const errorHandler: (err: any, req: import("express-serve-static-core").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: import("express-serve-static-core").Response<any, Record<string, any>, number>, next: import("express-serve-static-core").NextFunction) => Promise<ReturnType<ErrorRequestHandler>>;
@@ -1,4 +1 @@
1
- /// <reference types="qs" />
2
- /// <reference types="express" />
3
- /// <reference types="cookie-parser" />
4
- export declare const validateBatch: (scope: 'read' | 'update' | 'delete') => (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: import("express").Response<any, Record<string, any>>, next: import("express").NextFunction) => Promise<void>;
1
+ export declare const validateBatch: (scope: "read" | "update" | "delete") => (req: import("express").Request, res: import("express").Response, next: import("express").NextFunction) => Promise<void>;
@@ -7,4 +7,14 @@ export interface FetchPermissionsOptions {
7
7
  accountability?: Pick<Accountability, 'user' | 'role' | 'roles' | 'app'>;
8
8
  bypassDynamicVariableProcessing?: boolean;
9
9
  }
10
- export declare function fetchPermissions(options: FetchPermissionsOptions, context: Context): Promise<import("@directus/types").Permission[]>;
10
+ export declare function fetchPermissions(options: FetchPermissionsOptions, context: Context): Promise<{
11
+ permissions: import("@directus/types").Filter | null;
12
+ validation: import("@directus/types").Filter | null;
13
+ presets: any;
14
+ id?: number;
15
+ policy: string | null;
16
+ collection: string;
17
+ action: PermissionsAction;
18
+ fields: string[] | null;
19
+ system?: true;
20
+ }[]>;
@@ -1,5 +1,5 @@
1
1
  import type { CollectionKey, FieldMap, QueryPath } from '../types.js';
2
2
  export declare function getInfoForPath(fieldMap: FieldMap, group: keyof FieldMap, path: QueryPath, collection: CollectionKey): {
3
- collection: string;
4
- fields: Set<string>;
3
+ collection: CollectionKey;
4
+ fields: Set<import("../types.js").FieldKey>;
5
5
  };
@@ -1,5 +1,4 @@
1
1
  import { type FetchUserCountOptions } from '../../../utils/fetch-user-count/fetch-user-count.js';
2
2
  import type { Context } from '../../types.js';
3
- export interface ValidateRemainingAdminUsersOptions extends Pick<FetchUserCountOptions, 'excludeAccessRows' | 'excludePolicies' | 'excludeUsers' | 'excludeRoles'> {
4
- }
3
+ export type ValidateRemainingAdminUsersOptions = Pick<FetchUserCountOptions, 'excludeAccessRows' | 'excludePolicies' | 'excludeUsers' | 'excludeRoles'>;
5
4
  export declare function validateRemainingAdminUsers(options: ValidateRemainingAdminUsersOptions, context: Context): Promise<void>;
@@ -32,13 +32,21 @@ export async function fetchDynamicVariableContext(options, context) {
32
32
  });
33
33
  });
34
34
  }
35
- if (options.policies.length > 0 && (permissionContext.$CURRENT_POLICIES?.size ?? 0) > 0) {
36
- contextData['$CURRENT_POLICIES'] = await fetchContextData('$CURRENT_POLICIES', permissionContext, { policies: options.policies }, async (fields) => {
37
- const policiesService = new PoliciesService(context);
38
- return await policiesService.readMany(options.policies, {
39
- fields,
35
+ if (options.policies.length > 0) {
36
+ if ((permissionContext.$CURRENT_POLICIES?.size ?? 0) > 0) {
37
+ // Always add the id field
38
+ permissionContext.$CURRENT_POLICIES.add('id');
39
+ contextData['$CURRENT_POLICIES'] = await fetchContextData('$CURRENT_POLICIES', permissionContext, { policies: options.policies }, async (fields) => {
40
+ const policiesService = new PoliciesService(context);
41
+ return await policiesService.readMany(options.policies, {
42
+ fields,
43
+ });
40
44
  });
41
- });
45
+ }
46
+ else {
47
+ // Always create entries for the policies with the `id` field present
48
+ contextData['$CURRENT_POLICIES'] = options.policies.map((id) => ({ id }));
49
+ }
42
50
  }
43
51
  return contextData;
44
52
  }
@@ -4,4 +4,14 @@ export interface ProcessPermissionsOptions {
4
4
  accountability: Pick<Accountability, 'user' | 'role' | 'roles'>;
5
5
  permissionsContext: Record<string, any>;
6
6
  }
7
- export declare function processPermissions({ permissions, accountability, permissionsContext }: ProcessPermissionsOptions): Permission[];
7
+ export declare function processPermissions({ permissions, accountability, permissionsContext }: ProcessPermissionsOptions): {
8
+ permissions: import("@directus/types").Filter | null;
9
+ validation: import("@directus/types").Filter | null;
10
+ presets: any;
11
+ id?: number;
12
+ policy: string | null;
13
+ collection: string;
14
+ action: import("@directus/types").PermissionsAction;
15
+ fields: string[] | null;
16
+ system?: true;
17
+ }[];
@@ -1,9 +1,11 @@
1
1
  import { parseFilter, parsePreset } from '@directus/utils';
2
2
  export function processPermissions({ permissions, accountability, permissionsContext }) {
3
3
  return permissions.map((permission) => {
4
- permission.permissions = parseFilter(permission.permissions, accountability, permissionsContext);
5
- permission.validation = parseFilter(permission.validation, accountability, permissionsContext);
6
- permission.presets = parsePreset(permission.presets, accountability, permissionsContext);
7
- return permission;
4
+ return {
5
+ ...permission,
6
+ permissions: parseFilter(permission.permissions, accountability, permissionsContext),
7
+ validation: parseFilter(permission.validation, accountability, permissionsContext),
8
+ presets: parsePreset(permission.presets, accountability, permissionsContext),
9
+ };
8
10
  });
9
11
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { Agent, ClientRequestArgs } from 'node:http';
3
2
  /**
4
3
  * 'createConnection' is missing in 'Agent' type, but assigned in actual implementation:
package/dist/server.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import * as http from 'http';
5
2
  export declare let SERVER_ONLINE: boolean;
6
3
  export declare function createServer(): Promise<http.Server>;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { Range, Stat } from '@directus/storage';
3
2
  import type { Accountability, SchemaOverview } from '@directus/types';
4
3
  import type { Knex } from 'knex';
@@ -388,12 +388,6 @@ export class FieldsService {
388
388
  if (hookAdjustedField.schema?.is_nullable === true) {
389
389
  throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
390
390
  }
391
- if (hookAdjustedField.schema?.is_unique === false) {
392
- throw new InvalidPayloadError({ reason: 'Primary key must be unique' });
393
- }
394
- if (hookAdjustedField.schema?.is_indexed === true) {
395
- throw new InvalidPayloadError({ reason: 'Primary key cannot be indexed' });
396
- }
397
391
  }
398
392
  // Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
399
393
  const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { File } from '@directus/types';
3
2
  import type { Readable } from 'node:stream';
4
3
  export type Metadata = Partial<Pick<File, 'height' | 'width' | 'description' | 'title' | 'tags' | 'metadata'>>;
@@ -1,3 +1,2 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  export declare function parseIptc(buffer: Buffer): Record<string, unknown>;
3
2
  export declare function parseXmp(buffer: Buffer): Record<string, unknown>;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { BusboyFileStream, File, PrimaryKey, Query } from '@directus/types';
3
2
  import type { Readable } from 'node:stream';
4
3
  import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { Accountability, File, Query, SchemaOverview } from '@directus/types';
3
2
  import type { Knex } from 'knex';
4
3
  import type { Readable } from 'node:stream';
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import type { TusDriver } from '@directus/storage';
3
2
  import type { Accountability, File, SchemaOverview } from '@directus/types';
4
3
  import stream from 'node:stream';
@@ -373,7 +373,7 @@ export class UsersService extends ItemsService {
373
373
  await this.createOne(partialUser);
374
374
  }
375
375
  // We want to be able to re-send the verification email
376
- else if (user.status !== ('unverified')) {
376
+ else if (user.status !== 'unverified') {
377
377
  // To avoid giving attackers infos about registered emails we dont fail for violated unique constraints
378
378
  await stall(STALL_TIME, timeStart);
379
379
  return;
@@ -415,7 +415,7 @@ export class UsersService extends ItemsService {
415
415
  if (scope !== 'pending-registration')
416
416
  throw new ForbiddenError();
417
417
  const user = await this.getUserByEmail(email);
418
- if (user?.status !== ('unverified')) {
418
+ if (user?.status !== 'unverified') {
419
419
  throw new InvalidPayloadError({ reason: 'Invalid verification code' });
420
420
  }
421
421
  await this.updateOne(user.id, { status: 'active' });
@@ -1,4 +1,3 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request, Response } from 'express';
3
2
  import type { DocumentNode } from 'graphql';
4
3
  export interface GraphQLParams {
@@ -1,7 +1,7 @@
1
1
  import type { Aggregate, Filter, Permission, Query, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { AliasMap } from './get-column-path.js';
4
- export declare const generateAlias: (size?: number | undefined) => string;
4
+ export declare const generateAlias: (size?: number) => string;
5
5
  type ApplyQueryOptions = {
6
6
  aliasMap?: AliasMap;
7
7
  isInnerQuery?: boolean;
@@ -1,3 +1,2 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  export declare function compress(raw: Record<string, any> | Record<string, any>[]): Promise<Buffer>;
3
2
  export declare function decompress(compressed: Buffer): Promise<any>;
@@ -7,7 +7,7 @@ export function deleteFromRequireCache(modulePath) {
7
7
  const moduleCachePath = require.resolve(modulePath);
8
8
  delete require.cache[moduleCachePath];
9
9
  }
10
- catch (error) {
10
+ catch {
11
11
  logger.trace(`Module cache not found for ${modulePath}, skipped cache delete.`);
12
12
  }
13
13
  }
@@ -1,6 +1,5 @@
1
1
  import { type FetchAccessLookupOptions } from './fetch-access-lookup.js';
2
- export interface FetchUserCountOptions extends FetchAccessLookupOptions {
3
- }
2
+ export type FetchUserCountOptions = FetchAccessLookupOptions;
4
3
  export interface UserCount {
5
4
  admin: number;
6
5
  app: number;
@@ -3,7 +3,7 @@ import { getConfigFromEnv } from './get-config-from-env.js';
3
3
  export function generateHash(stringToHash) {
4
4
  const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805
5
5
  // associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
6
- 'associatedData' in argon2HashConfigOptions &&
7
- (argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']));
6
+ if ('associatedData' in argon2HashConfigOptions)
7
+ argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']);
8
8
  return argon2.hash(stringToHash, argon2HashConfigOptions);
9
9
  }
@@ -1,5 +1,2 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import * as http from 'http';
5
2
  export declare function getAddress(server: http.Server): {};
@@ -1,4 +1,3 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request } from 'express';
3
2
  /**
4
3
  * Returns the Cache-Control header for the current request
@@ -1,3 +1,2 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request } from 'express';
3
2
  export declare function getCacheKey(req: Request): Promise<string>;
@@ -21,5 +21,5 @@ type GetColumnOptions = OriginalCollectionName | (FunctionColumnOptions & Origin
21
21
  * @param options Optional parameters
22
22
  * @returns Knex raw instance
23
23
  */
24
- export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
24
+ export declare function getColumn(knex: Knex, table: string, column: string, alias: (string | false) | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
25
25
  export {};
@@ -1,3 +1,2 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request } from 'express';
3
2
  export declare function getGraphqlQueryAndVariables(req: Request): Pick<any, "query" | "variables">;
@@ -1,3 +1,2 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request } from 'express';
3
2
  export declare function getIPFromReq(req: Request): string | null;
@@ -49,7 +49,7 @@ function sortDeep(raw) {
49
49
  return fromPairs(sorted);
50
50
  }
51
51
  if (isArray(raw)) {
52
- return sortBy(raw);
52
+ return raw.map((raw) => sortDeep(raw));
53
53
  }
54
54
  return raw;
55
55
  }
@@ -192,7 +192,7 @@ function sanitizeAlias(rawAlias) {
192
192
  try {
193
193
  alias = parseJSON(rawAlias);
194
194
  }
195
- catch (err) {
195
+ catch {
196
196
  logger.warn('Invalid value passed for alias query parameter.');
197
197
  }
198
198
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="cookie-parser" />
2
1
  import type { Request } from 'express';
3
2
  /**
4
3
  * Whether to skip caching for the current request
@@ -28,7 +28,7 @@ export async function authenticateConnection(message) {
28
28
  const expires_at = getExpiresAtForToken(access_token);
29
29
  return { accountability, expires_at, refresh_token };
30
30
  }
31
- catch (error) {
31
+ catch {
32
32
  throw new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
33
33
  }
34
34
  }
@@ -1,8 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
3
- /// <reference types="node" resolution-mode="require"/>
4
- /// <reference types="node/http.js" />
5
- /// <reference types="pino-http" />
6
1
  import type { Accountability } from '@directus/types';
7
2
  import type { IncomingMessage, Server as httpServer } from 'http';
8
3
  import type { RateLimiterAbstract } from 'rate-limiter-flexible';
@@ -32,12 +27,8 @@ export default abstract class SocketController {
32
27
  createClient(ws: WebSocket, { accountability, expires_at }: AuthenticationState): WebSocketClient;
33
28
  protected parseMessage(data: string): WebSocketMessage;
34
29
  protected handleAuthRequest(client: WebSocketClient, message: WebSocketAuthMessage): Promise<void>;
30
+ protected checkUserRequirements(_accountability: Accountability | null): void;
35
31
  setTokenExpireTimer(client: WebSocketClient): void;
36
32
  checkClientTokens(): void;
37
- meetsAdminRequirement({ socket, client, accountability, }: {
38
- socket?: UpgradeContext['socket'];
39
- client?: WebSocketClient | WebSocket;
40
- accountability: Accountability | null;
41
- }): boolean;
42
33
  terminate(): void;
43
34
  }
@@ -60,7 +60,6 @@ export default class SocketController {
60
60
  authentication: {
61
61
  mode: authMode.data,
62
62
  timeout: authTimeout,
63
- requireAdmin: false,
64
63
  },
65
64
  };
66
65
  }
@@ -140,8 +139,15 @@ export default class SocketController {
140
139
  socket.destroy();
141
140
  return;
142
141
  }
143
- if (!this.meetsAdminRequirement({ socket, accountability }))
142
+ try {
143
+ this.checkUserRequirements(accountability);
144
+ }
145
+ catch {
146
+ logger.debug('WebSocket upgrade denied - ' + JSON.stringify(accountability || 'invalid'));
147
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
148
+ socket.destroy();
144
149
  return;
150
+ }
145
151
  this.server.handleUpgrade(request, socket, head, async (ws) => {
146
152
  this.catchInvalidMessages(ws);
147
153
  const state = { accountability, expires_at };
@@ -156,8 +162,7 @@ export default class SocketController {
156
162
  if (getMessageType(payload) !== 'auth')
157
163
  throw new Error();
158
164
  const state = await authenticateConnection(WebSocketAuthMessage.parse(payload));
159
- if (this.meetsAdminRequirement({ client: ws, accountability: state.accountability }))
160
- return;
165
+ this.checkUserRequirements(state.accountability);
161
166
  ws.send(authenticationSuccess(payload['uid'], state.refresh_token));
162
167
  this.server.emit('connection', ws, state);
163
168
  }
@@ -238,7 +243,7 @@ export default class SocketController {
238
243
  try {
239
244
  message = WebSocketMessage.parse(parseJSON(data));
240
245
  }
241
- catch (err) {
246
+ catch {
242
247
  throw new WebSocketError('server', 'INVALID_PAYLOAD', 'Unable to parse the incoming message.');
243
248
  }
244
249
  return message;
@@ -246,8 +251,7 @@ export default class SocketController {
246
251
  async handleAuthRequest(client, message) {
247
252
  try {
248
253
  const { accountability, expires_at, refresh_token } = await authenticateConnection(message);
249
- if (!this.meetsAdminRequirement({ client, accountability }))
250
- return;
254
+ this.checkUserRequirements(accountability);
251
255
  client.accountability = accountability;
252
256
  client.expires_at = expires_at;
253
257
  this.setTokenExpireTimer(client);
@@ -269,6 +273,10 @@ export default class SocketController {
269
273
  }
270
274
  }
271
275
  }
276
+ checkUserRequirements(_accountability) {
277
+ // there are no requirements in the abstract class
278
+ return;
279
+ }
272
280
  setTokenExpireTimer(client) {
273
281
  if (client.auth_timer !== null) {
274
282
  // clear up old timeouts if needed
@@ -305,20 +313,6 @@ export default class SocketController {
305
313
  }
306
314
  }, TOKEN_CHECK_INTERVAL);
307
315
  }
308
- meetsAdminRequirement({ socket, client, accountability, }) {
309
- if (!this.authentication.requireAdmin || accountability?.admin)
310
- return true;
311
- logger.debug('WebSocket connection denied - ' + JSON.stringify(accountability || 'invalid'));
312
- if (socket) {
313
- socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
314
- socket.destroy();
315
- }
316
- else if (client) {
317
- handleWebSocketError(client, new WebSocketError('auth', 'UNAUTHORIZED', 'Unauthorized.'), 'auth');
318
- client.close();
319
- }
320
- return false;
321
- }
322
316
  terminate() {
323
317
  if (this.authInterval)
324
318
  clearInterval(this.authInterval);
@@ -1,6 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import type { Server } from 'graphql-ws';
5
2
  import type { Server as httpServer } from 'http';
6
3
  import type { GraphQLSocket, UpgradeContext, WebSocketClient } from '../types.js';
@@ -1,6 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import type { Server as httpServer } from 'http';
5
2
  import { GraphQLSubscriptionController } from './graphql.js';
6
3
  import { LogsController } from './logs.js';
@@ -1,18 +1,17 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import type { Server as httpServer } from 'http';
2
+ import { AuthMode } from '../messages.js';
5
3
  import SocketController from './base.js';
4
+ import type { Accountability } from '@directus/types';
6
5
  export declare class LogsController extends SocketController {
7
6
  constructor(httpServer: httpServer);
8
7
  getEnvironmentConfig(configPrefix: string): {
9
8
  endpoint: string;
10
9
  maxConnections: number;
11
10
  authentication: {
12
- mode: "strict" | "public" | "handshake";
11
+ mode: AuthMode;
13
12
  timeout: number;
14
- requireAdmin: boolean;
15
13
  };
16
14
  };
17
15
  private bindEvents;
16
+ protected checkUserRequirements(accountability: Accountability | null): void;
18
17
  }
@@ -1,7 +1,7 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import emitter from '../../emitter.js';
3
3
  import { useLogger } from '../../logger/index.js';
4
- import { handleWebSocketError } from '../errors.js';
4
+ import { handleWebSocketError, WebSocketError } from '../errors.js';
5
5
  import { AuthMode, WebSocketMessage } from '../messages.js';
6
6
  import SocketController from './base.js';
7
7
  const logger = useLogger();
@@ -21,11 +21,9 @@ export class LogsController extends SocketController {
21
21
  return {
22
22
  endpoint,
23
23
  maxConnections,
24
- // require strict auth
25
24
  authentication: {
26
25
  mode: 'strict',
27
26
  timeout: 0,
28
- requireAdmin: true,
29
27
  },
30
28
  };
31
29
  }
@@ -47,4 +45,10 @@ export class LogsController extends SocketController {
47
45
  });
48
46
  emitter.emitAction('websocket.connect', { client });
49
47
  }
48
+ checkUserRequirements(accountability) {
49
+ // enforce admin only access for the logs streaming websocket
50
+ if (!accountability?.admin) {
51
+ throw new WebSocketError('auth', 'AUTH_FAILED', 'Unauthorized access.');
52
+ }
53
+ }
50
54
  }
@@ -1,6 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node/http.js" />
3
- /// <reference types="pino-http" />
4
1
  import type { Server as httpServer } from 'http';
5
2
  import { WebSocketMessage } from '../messages.js';
6
3
  import SocketController from './base.js';
@@ -40,7 +40,7 @@ export class WebSocketController extends SocketController {
40
40
  try {
41
41
  message = parseJSON(data);
42
42
  }
43
- catch (err) {
43
+ catch {
44
44
  throw new WebSocketError('server', 'INVALID_PAYLOAD', 'Unable to parse the incoming message.');
45
45
  }
46
46
  return message;
@@ -1,8 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference types="node" resolution-mode="require"/>
3
- /// <reference types="node" resolution-mode="require"/>
4
- /// <reference types="node/http.js" />
5
- /// <reference types="pino-http" />
6
1
  import type { Accountability, Query } from '@directus/types';
7
2
  import type { IncomingMessage } from 'http';
8
3
  import type internal from 'stream';
@@ -21,7 +16,6 @@ export type UpgradeRequest = IncomingMessage & AuthenticationState;
21
16
  export type WebSocketAuthentication = {
22
17
  mode: AuthMode;
23
18
  timeout: number;
24
- requireAdmin: boolean;
25
19
  };
26
20
  export type SubscriptionEvent = 'create' | 'update' | 'delete';
27
21
  export type Subscription = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "22.2.0",
3
+ "version": "23.0.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -68,9 +68,9 @@
68
68
  "@tus/server": "1.6.0",
69
69
  "@tus/utils": "0.2.0",
70
70
  "@types/cookie": "0.6.0",
71
- "argon2": "0.40.3",
71
+ "argon2": "0.41.1",
72
72
  "async": "3.2.5",
73
- "axios": "1.7.3",
73
+ "axios": "1.7.7",
74
74
  "busboy": "1.6.0",
75
75
  "bytes": "3.1.2",
76
76
  "camelcase": "8.0.0",
@@ -82,7 +82,7 @@
82
82
  "cookie-parser": "1.4.6",
83
83
  "cors": "2.8.5",
84
84
  "cron-parser": "4.9.0",
85
- "date-fns": "3.6.0",
85
+ "date-fns": "4.1.0",
86
86
  "deep-diff": "1.0.2",
87
87
  "destroy": "1.2.0",
88
88
  "dotenv": "16.4.5",
@@ -102,18 +102,18 @@
102
102
  "inquirer": "9.3.6",
103
103
  "ioredis": "5.4.1",
104
104
  "ip-matching": "2.1.2",
105
- "isolated-vm": "4.7.2",
105
+ "isolated-vm": "5.0.1",
106
106
  "joi": "17.13.3",
107
107
  "js-yaml": "4.1.0",
108
108
  "js2xmlparser": "5.0.0",
109
109
  "json2csv": "5.0.7",
110
110
  "jsonwebtoken": "9.0.2",
111
- "keyv": "4.5.4",
111
+ "keyv": "5.1.0",
112
112
  "knex": "3.1.0",
113
113
  "ldapjs": "2.3.3",
114
114
  "liquidjs": "10.15.0",
115
115
  "lodash-es": "4.17.21",
116
- "marked": "12.0.2",
116
+ "marked": "14.1.2",
117
117
  "micromustache": "8.0.3",
118
118
  "mime-types": "2.1.35",
119
119
  "minimatch": "9.0.5",
@@ -124,17 +124,17 @@
124
124
  "node-schedule": "2.1.1",
125
125
  "nodemailer": "6.9.14",
126
126
  "object-hash": "3.0.0",
127
- "openapi3-ts": "4.3.3",
127
+ "openapi3-ts": "4.4.0",
128
128
  "openid-client": "5.6.5",
129
129
  "ora": "8.0.1",
130
130
  "otplib": "12.0.1",
131
- "p-limit": "5.0.0",
131
+ "p-limit": "6.1.0",
132
132
  "p-queue": "8.0.1",
133
133
  "papaparse": "5.4.1",
134
- "pino": "9.2.0",
135
- "pino-http": "9.0.0",
134
+ "pino": "9.4.0",
135
+ "pino-http": "10.3.0",
136
136
  "pino-http-print": "3.1.0",
137
- "pino-pretty": "11.2.1",
137
+ "pino-pretty": "11.2.2",
138
138
  "qs": "6.13.0",
139
139
  "rate-limiter-flexible": "5.0.3",
140
140
  "rollup": "4.17.2",
@@ -148,30 +148,30 @@
148
148
  "wellknown": "0.5.0",
149
149
  "ws": "8.18.0",
150
150
  "zod": "3.23.8",
151
- "zod-validation-error": "3.3.1",
152
- "@directus/constants": "12.0.0",
153
- "@directus/env": "3.1.0",
154
- "@directus/app": "13.2.0",
151
+ "zod-validation-error": "3.4.0",
152
+ "@directus/app": "13.2.1",
153
+ "@directus/env": "3.1.1",
155
154
  "@directus/errors": "1.0.0",
156
- "@directus/extensions": "2.0.1",
157
- "@directus/extensions-registry": "2.0.1",
158
- "@directus/memory": "2.0.1",
159
- "@directus/extensions-sdk": "12.0.2",
155
+ "@directus/extensions": "2.0.2",
156
+ "@directus/extensions-sdk": "12.1.0",
157
+ "@directus/extensions-registry": "2.0.2",
160
158
  "@directus/format-title": "11.0.0",
161
- "@directus/pressure": "2.0.0",
162
- "@directus/schema": "12.1.0",
159
+ "@directus/memory": "2.0.2",
160
+ "@directus/schema": "12.1.1",
163
161
  "@directus/storage": "11.0.0",
162
+ "@directus/pressure": "2.0.1",
163
+ "@directus/storage-driver-azure": "11.0.1",
164
164
  "@directus/specs": "11.0.1",
165
- "@directus/storage-driver-azure": "11.0.0",
166
- "@directus/storage-driver-cloudinary": "11.0.1",
167
- "@directus/storage-driver-gcs": "11.0.0",
165
+ "@directus/constants": "12.0.0",
166
+ "@directus/storage-driver-gcs": "11.0.1",
168
167
  "@directus/storage-driver-local": "11.0.0",
169
- "@directus/storage-driver-s3": "11.0.0",
168
+ "@directus/storage-driver-cloudinary": "11.0.2",
169
+ "@directus/storage-driver-s3": "11.0.1",
170
+ "@directus/storage-driver-supabase": "2.0.1",
170
171
  "@directus/system-data": "2.0.0",
171
- "@directus/storage-driver-supabase": "2.0.0",
172
- "@directus/validation": "1.0.0",
173
- "@directus/utils": "12.0.0",
174
- "directus": "11.1.0"
172
+ "@directus/validation": "1.0.1",
173
+ "directus": "11.1.1",
174
+ "@directus/utils": "12.0.1"
175
175
  },
176
176
  "devDependencies": {
177
177
  "@ngneat/falso": "7.2.0",
@@ -196,36 +196,35 @@
196
196
  "@types/lodash-es": "4.17.12",
197
197
  "@types/mime-types": "2.1.4",
198
198
  "@types/ms": "0.7.34",
199
- "@types/node": "18.19.45",
199
+ "@types/node": "18.19.50",
200
200
  "@types/node-schedule": "2.1.7",
201
201
  "@types/nodemailer": "6.4.15",
202
202
  "@types/object-hash": "3.0.6",
203
203
  "@types/papaparse": "5.3.14",
204
204
  "@types/qs": "6.9.15",
205
- "@types/sanitize-html": "2.11.0",
205
+ "@types/sanitize-html": "2.13.0",
206
206
  "@types/stream-json": "1.7.7",
207
207
  "@types/wellknown": "0.5.8",
208
208
  "@types/ws": "8.5.12",
209
- "@vitest/coverage-v8": "1.5.3",
209
+ "@vitest/coverage-v8": "2.1.2",
210
210
  "copyfiles": "2.4.1",
211
211
  "form-data": "4.0.0",
212
212
  "get-port": "7.1.0",
213
- "knex-mock-client": "2.0.1",
214
- "typescript": "5.4.5",
215
- "vitest": "1.5.3",
213
+ "knex-mock-client": "3.0.2",
214
+ "typescript": "5.6.2",
215
+ "vitest": "2.1.2",
216
216
  "@directus/random": "1.0.0",
217
- "@directus/tsconfig": "2.0.0",
218
- "@directus/types": "12.0.1"
217
+ "@directus/types": "12.1.0",
218
+ "@directus/tsconfig": "2.0.0"
219
219
  },
220
220
  "optionalDependencies": {
221
- "@keyv/redis": "2.8.5",
222
- "mysql2": "3.11.0",
221
+ "@keyv/redis": "3.0.1",
222
+ "mysql2": "3.11.2",
223
223
  "nodemailer-mailgun-transport": "2.1.5",
224
- "nodemailer-sendgrid": "1.0.3",
225
224
  "oracledb": "6.6.0",
226
225
  "pg": "8.12.0",
227
226
  "sqlite3": "5.1.7",
228
- "tedious": "18.2.0"
227
+ "tedious": "18.6.1"
229
228
  },
230
229
  "engines": {
231
230
  "node": ">=18.17.0"