@directus/api 22.1.1 → 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 (119) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/drivers/ldap.js +14 -3
  3. package/dist/auth/drivers/oauth2.js +13 -2
  4. package/dist/auth/drivers/openid.js +13 -2
  5. package/dist/cache.d.ts +2 -2
  6. package/dist/cache.js +6 -6
  7. package/dist/cli/commands/init/questions.d.ts +5 -5
  8. package/dist/cli/commands/schema/apply.d.ts +1 -0
  9. package/dist/cli/commands/schema/apply.js +20 -1
  10. package/dist/cli/index.js +1 -0
  11. package/dist/cli/utils/create-env/env-stub.liquid +1 -4
  12. package/dist/constants.d.ts +1 -0
  13. package/dist/constants.js +1 -0
  14. package/dist/database/helpers/index.d.ts +2 -0
  15. package/dist/database/helpers/index.js +2 -0
  16. package/dist/database/helpers/nullable-update/dialects/default.d.ts +3 -0
  17. package/dist/database/helpers/nullable-update/dialects/default.js +3 -0
  18. package/dist/database/helpers/nullable-update/dialects/oracle.d.ts +12 -0
  19. package/dist/database/helpers/nullable-update/dialects/oracle.js +16 -0
  20. package/dist/database/helpers/nullable-update/index.d.ts +7 -0
  21. package/dist/database/helpers/nullable-update/index.js +7 -0
  22. package/dist/database/helpers/nullable-update/types.d.ts +7 -0
  23. package/dist/database/helpers/nullable-update/types.js +12 -0
  24. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +3 -1
  25. package/dist/database/helpers/schema/dialects/cockroachdb.js +17 -0
  26. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  27. package/dist/database/helpers/schema/dialects/mssql.js +20 -0
  28. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  29. package/dist/database/helpers/schema/dialects/mysql.js +33 -0
  30. package/dist/database/helpers/schema/dialects/oracle.d.ts +3 -1
  31. package/dist/database/helpers/schema/dialects/oracle.js +21 -0
  32. package/dist/database/helpers/schema/dialects/postgres.d.ts +3 -1
  33. package/dist/database/helpers/schema/dialects/postgres.js +23 -0
  34. package/dist/database/helpers/schema/dialects/sqlite.d.ts +1 -0
  35. package/dist/database/helpers/schema/dialects/sqlite.js +3 -0
  36. package/dist/database/helpers/schema/types.d.ts +5 -0
  37. package/dist/database/helpers/schema/types.js +3 -0
  38. package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +5 -1
  39. package/dist/database/helpers/schema/utils/preprocess-bindings.js +23 -17
  40. package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
  41. package/dist/database/migrations/20240806A-permissions-policies.js +1 -1
  42. package/dist/database/migrations/20240817A-update-icon-fields-length.d.ts +3 -0
  43. package/dist/database/migrations/20240817A-update-icon-fields-length.js +55 -0
  44. package/dist/database/run-ast/lib/get-db-query.js +14 -8
  45. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +0 -3
  46. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -2
  47. package/dist/extensions/manager.js +2 -2
  48. package/dist/logger/index.d.ts +8 -3
  49. package/dist/logger/index.js +79 -28
  50. package/dist/logger/logs-stream.d.ts +10 -0
  51. package/dist/logger/logs-stream.js +41 -0
  52. package/dist/mailer.js +0 -6
  53. package/dist/middleware/authenticate.d.ts +1 -3
  54. package/dist/middleware/error-handler.d.ts +0 -1
  55. package/dist/middleware/respond.js +1 -0
  56. package/dist/middleware/validate-batch.d.ts +1 -4
  57. package/dist/permissions/lib/fetch-permissions.d.ts +11 -1
  58. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +2 -2
  59. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +1 -2
  60. package/dist/permissions/utils/fetch-dynamic-variable-context.js +14 -6
  61. package/dist/permissions/utils/process-permissions.d.ts +11 -1
  62. package/dist/permissions/utils/process-permissions.js +6 -4
  63. package/dist/request/agent-with-ip-validation.d.ts +0 -1
  64. package/dist/request/is-denied-ip.js +7 -1
  65. package/dist/server.d.ts +0 -3
  66. package/dist/server.js +4 -2
  67. package/dist/services/assets.d.ts +0 -1
  68. package/dist/services/fields.js +52 -20
  69. package/dist/services/files/utils/get-metadata.d.ts +0 -1
  70. package/dist/services/files/utils/parse-image-metadata.d.ts +0 -1
  71. package/dist/services/files.d.ts +0 -1
  72. package/dist/services/import-export.d.ts +0 -1
  73. package/dist/services/mail/index.js +1 -5
  74. package/dist/services/notifications.d.ts +0 -4
  75. package/dist/services/notifications.js +8 -6
  76. package/dist/services/server.js +8 -1
  77. package/dist/services/specifications.js +7 -7
  78. package/dist/services/tus/data-store.d.ts +0 -1
  79. package/dist/services/users.js +6 -3
  80. package/dist/types/graphql.d.ts +0 -1
  81. package/dist/utils/apply-query.d.ts +1 -1
  82. package/dist/utils/compress.d.ts +0 -1
  83. package/dist/utils/delete-from-require-cache.js +1 -1
  84. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +1 -2
  85. package/dist/utils/generate-hash.js +2 -2
  86. package/dist/utils/get-address.d.ts +1 -4
  87. package/dist/utils/get-address.js +6 -1
  88. package/dist/utils/get-allowed-log-levels.d.ts +3 -0
  89. package/dist/utils/get-allowed-log-levels.js +11 -0
  90. package/dist/utils/get-cache-headers.d.ts +0 -1
  91. package/dist/utils/get-cache-key.d.ts +0 -1
  92. package/dist/utils/get-column.d.ts +1 -1
  93. package/dist/utils/get-graphql-query-and-variables.d.ts +0 -1
  94. package/dist/utils/get-ip-from-req.d.ts +0 -1
  95. package/dist/utils/get-schema.js +19 -24
  96. package/dist/utils/get-snapshot.js +1 -1
  97. package/dist/utils/parse-filter-key.js +1 -5
  98. package/dist/utils/sanitize-query.js +1 -1
  99. package/dist/utils/sanitize-schema.d.ts +1 -1
  100. package/dist/utils/should-skip-cache.d.ts +0 -1
  101. package/dist/websocket/authenticate.js +1 -1
  102. package/dist/websocket/controllers/base.d.ts +6 -15
  103. package/dist/websocket/controllers/base.js +17 -4
  104. package/dist/websocket/controllers/graphql.d.ts +0 -3
  105. package/dist/websocket/controllers/graphql.js +3 -1
  106. package/dist/websocket/controllers/index.d.ts +4 -3
  107. package/dist/websocket/controllers/index.js +12 -0
  108. package/dist/websocket/controllers/logs.d.ts +17 -0
  109. package/dist/websocket/controllers/logs.js +54 -0
  110. package/dist/websocket/controllers/rest.d.ts +0 -3
  111. package/dist/websocket/controllers/rest.js +4 -2
  112. package/dist/websocket/handlers/index.d.ts +1 -0
  113. package/dist/websocket/handlers/index.js +21 -3
  114. package/dist/websocket/handlers/logs.d.ts +31 -0
  115. package/dist/websocket/handlers/logs.js +121 -0
  116. package/dist/websocket/messages.d.ts +26 -0
  117. package/dist/websocket/messages.js +9 -0
  118. package/dist/websocket/types.d.ts +6 -5
  119. package/package.json +48 -49
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>;
package/dist/server.js CHANGED
@@ -14,7 +14,7 @@ import { useLogger } from './logger/index.js';
14
14
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
15
15
  import { getIPFromReq } from './utils/get-ip-from-req.js';
16
16
  import { getAddress } from './utils/get-address.js';
17
- import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
17
+ import { createLogsController, createSubscriptionController, createWebSocketController, getLogsController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
18
18
  import { startWebSocketHandlers } from './websocket/handlers/index.js';
19
19
  export let SERVER_ONLINE = true;
20
20
  const env = useEnv();
@@ -77,6 +77,7 @@ export async function createServer() {
77
77
  if (toBoolean(env['WEBSOCKETS_ENABLED']) === true) {
78
78
  createSubscriptionController(server);
79
79
  createWebSocketController(server);
80
+ createLogsController(server);
80
81
  startWebSocketHandlers();
81
82
  }
82
83
  const terminusOptions = {
@@ -99,6 +100,7 @@ export async function createServer() {
99
100
  async function onSignal() {
100
101
  getSubscriptionController()?.terminate();
101
102
  getWebSocketController()?.terminate();
103
+ getLogsController()?.terminate();
102
104
  const database = getDatabase();
103
105
  await database.destroy();
104
106
  logger.info('Database connections destroyed');
@@ -126,7 +128,7 @@ export async function startServer() {
126
128
  else {
127
129
  listenOptions = {
128
130
  host,
129
- port: parseInt(port || '8055'),
131
+ port: parseInt(port),
130
132
  };
131
133
  }
132
134
  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';
@@ -5,7 +5,7 @@ import { createInspector } from '@directus/schema';
5
5
  import { addFieldFlag, toArray } from '@directus/utils';
6
6
  import { isEqual, isNil, merge } from 'lodash-es';
7
7
  import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
8
- import { ALIAS_TYPES } from '../constants.js';
8
+ import { ALIAS_TYPES, ALLOWED_DB_DEFAULT_FUNCTIONS } from '../constants.js';
9
9
  import { translateDatabaseError } from '../database/errors/translate.js';
10
10
  import { getHelpers } from '../database/helpers/index.js';
11
11
  import getDatabase, { getSchemaInspector } from '../database/index.js';
@@ -384,8 +384,10 @@ export class FieldsService {
384
384
  }
385
385
  if (hookAdjustedField.schema) {
386
386
  const existingColumn = await this.columnInfo(collection, hookAdjustedField.field);
387
- if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) {
388
- throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
387
+ if (existingColumn.is_primary_key) {
388
+ if (hookAdjustedField.schema?.is_nullable === true) {
389
+ throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
390
+ }
389
391
  }
390
392
  // Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
391
393
  const columnToCompare = opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
@@ -662,12 +664,12 @@ export class FieldsService {
662
664
  else {
663
665
  throw new InvalidPayloadError({ reason: `Illegal type passed: "${field.type}"` });
664
666
  }
665
- const defaultValue = field.schema?.default_value !== undefined ? field.schema?.default_value : existing?.default_value;
666
- if (defaultValue) {
667
+ const setDefaultValue = (defaultValue) => {
667
668
  const newDefaultValueIsString = typeof defaultValue === 'string';
668
669
  const newDefaultIsNowFunction = newDefaultValueIsString && defaultValue.toLowerCase() === 'now()';
669
670
  const newDefaultIsCurrentTimestamp = newDefaultValueIsString && defaultValue === 'CURRENT_TIMESTAMP';
670
671
  const newDefaultIsSetToCurrentTime = newDefaultIsNowFunction || newDefaultIsCurrentTimestamp;
672
+ const newDefaultIsAFunction = newDefaultValueIsString && ALLOWED_DB_DEFAULT_FUNCTIONS.includes(defaultValue);
671
673
  const newDefaultIsTimestampWithPrecision = newDefaultValueIsString && defaultValue.includes('CURRENT_TIMESTAMP(') && defaultValue.includes(')');
672
674
  if (newDefaultIsSetToCurrentTime) {
673
675
  column.defaultTo(this.knex.fn.now());
@@ -676,31 +678,61 @@ export class FieldsService {
676
678
  const precision = defaultValue.match(REGEX_BETWEEN_PARENS)[1];
677
679
  column.defaultTo(this.knex.fn.now(Number(precision)));
678
680
  }
681
+ else if (newDefaultIsAFunction) {
682
+ column.defaultTo(this.knex.raw(defaultValue));
683
+ }
679
684
  else {
680
685
  column.defaultTo(defaultValue);
681
686
  }
687
+ };
688
+ // for a new item, set the default value and nullable as provided without any further considerations
689
+ if (!existing) {
690
+ if (field.schema?.default_value !== undefined) {
691
+ setDefaultValue(field.schema.default_value);
692
+ }
693
+ if (field.schema?.is_nullable || field.schema?.is_nullable === undefined) {
694
+ column.nullable();
695
+ }
696
+ else {
697
+ column.notNullable();
698
+ }
682
699
  }
683
700
  else {
684
- column.defaultTo(null);
685
- }
686
- const isNullable = field.schema?.is_nullable ?? existing?.is_nullable ?? true;
687
- if (isNullable) {
688
- column.nullable();
689
- }
690
- else {
691
- column.notNullable();
701
+ // for an existing item: if nullable option changed, we have to provide the default values as well and actually vice versa
702
+ // see https://knexjs.org/guide/schema-builder.html#alter
703
+ // To overwrite a nullable option with the same value this is not possible for Oracle though, hence the DB helper
704
+ if (field.schema?.default_value !== undefined || field.schema?.is_nullable !== undefined) {
705
+ this.helpers.nullableUpdate.updateNullableValue(column, field, existing);
706
+ let defaultValue = null;
707
+ if (field.schema?.default_value !== undefined) {
708
+ defaultValue = field.schema.default_value;
709
+ }
710
+ else if (existing.default_value !== undefined) {
711
+ defaultValue = existing.default_value;
712
+ }
713
+ setDefaultValue(defaultValue);
714
+ }
692
715
  }
693
716
  if (field.schema?.is_primary_key) {
694
717
  column.primary().notNullable();
695
718
  }
696
- else if (field.schema?.is_unique === true) {
697
- if (!existing || existing.is_unique === false) {
698
- column.unique();
719
+ else if (!existing?.is_primary_key) {
720
+ // primary key will already have unique/index constraints
721
+ if (field.schema?.is_unique === true) {
722
+ if (!existing || existing.is_unique === false) {
723
+ column.unique();
724
+ }
699
725
  }
700
- }
701
- else if (field.schema?.is_unique === false) {
702
- if (existing && existing.is_unique === true) {
703
- table.dropUnique([field.field]);
726
+ else if (field.schema?.is_unique === false) {
727
+ if (existing && existing.is_unique === true) {
728
+ table.dropUnique([field.field]);
729
+ }
730
+ }
731
+ if (field.schema?.is_indexed === true && !existing?.is_indexed) {
732
+ column.index();
733
+ }
734
+ else if (field.schema?.is_indexed === false && existing?.is_indexed) {
735
+ table.dropIndex([field.field]);
704
736
  }
705
737
  }
706
738
  if (existing) {
@@ -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';
@@ -36,11 +36,7 @@ export class MailService {
36
36
  }
37
37
  }
38
38
  async send(options) {
39
- const payload = await emitter.emitFilter(`email.send`, options, {
40
- database: getDatabase(),
41
- schema: null,
42
- accountability: null,
43
- });
39
+ const payload = await emitter.emitFilter(`email.send`, options, {});
44
40
  if (!payload)
45
41
  return null;
46
42
  const { template, ...emailOptions } = payload;
@@ -1,11 +1,7 @@
1
1
  import type { Notification, PrimaryKey } from '@directus/types';
2
2
  import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
3
  import { ItemsService } from './items.js';
4
- import { MailService } from './mail/index.js';
5
- import { UsersService } from './users.js';
6
4
  export declare class NotificationsService extends ItemsService {
7
- usersService: UsersService;
8
- mailService: MailService;
9
5
  constructor(options: AbstractServiceOptions);
10
6
  createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey>;
11
7
  sendEmail(data: Partial<Notification>): Promise<void>;
@@ -10,12 +10,8 @@ import { UsersService } from './users.js';
10
10
  const env = useEnv();
11
11
  const logger = useLogger();
12
12
  export class NotificationsService extends ItemsService {
13
- usersService;
14
- mailService;
15
13
  constructor(options) {
16
14
  super('directus_notifications', options);
17
- this.usersService = new UsersService({ schema: this.schema });
18
- this.mailService = new MailService({ schema: this.schema, accountability: this.accountability });
19
15
  }
20
16
  async createOne(data, opts) {
21
17
  const response = await super.createOne(data, opts);
@@ -24,7 +20,8 @@ export class NotificationsService extends ItemsService {
24
20
  }
25
21
  async sendEmail(data) {
26
22
  if (data.recipient) {
27
- const user = await this.usersService.readOne(data.recipient, {
23
+ const usersService = new UsersService({ schema: this.schema, knex: this.knex });
24
+ const user = await usersService.readOne(data.recipient, {
28
25
  fields: ['id', 'email', 'email_notifications', 'role'],
29
26
  });
30
27
  if (user['email'] && user['email_notifications'] === true) {
@@ -38,7 +35,12 @@ export class NotificationsService extends ItemsService {
38
35
  roles,
39
36
  ip: null,
40
37
  }, this.knex);
41
- this.mailService
38
+ const mailService = new MailService({
39
+ schema: this.schema,
40
+ knex: this.knex,
41
+ accountability: this.accountability,
42
+ });
43
+ mailService
42
44
  .send({
43
45
  template: {
44
46
  name: 'base',
@@ -5,6 +5,7 @@ import { merge } from 'lodash-es';
5
5
  import { Readable } from 'node:stream';
6
6
  import { performance } from 'perf_hooks';
7
7
  import { getCache } from '../cache.js';
8
+ import { RESUMABLE_UPLOADS } from '../constants.js';
8
9
  import getDatabase, { hasDatabaseConnection } from '../database/index.js';
9
10
  import { useLogger } from '../logger/index.js';
10
11
  import getMailer from '../mailer.js';
@@ -12,8 +13,8 @@ import { rateLimiterGlobal } from '../middleware/rate-limiter-global.js';
12
13
  import { rateLimiter } from '../middleware/rate-limiter-ip.js';
13
14
  import { SERVER_ONLINE } from '../server.js';
14
15
  import { getStorage } from '../storage/index.js';
16
+ import { getAllowedLogLevels } from '../utils/get-allowed-log-levels.js';
15
17
  import { SettingsService } from './settings.js';
16
- import { RESUMABLE_UPLOADS } from '../constants.js';
17
18
  const env = useEnv();
18
19
  const logger = useLogger();
19
20
  export class ServerService {
@@ -95,6 +96,12 @@ export class ServerService {
95
96
  info['websocket'].heartbeat = toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED'])
96
97
  ? env['WEBSOCKETS_HEARTBEAT_PERIOD']
97
98
  : false;
99
+ info['websocket'].logs =
100
+ toBoolean(env['WEBSOCKETS_LOGS_ENABLED']) && this.accountability.admin
101
+ ? {
102
+ allowedLogLevels: getAllowedLogLevels(env['WEBSOCKETS_LOGS_LEVEL'] || 'info'),
103
+ }
104
+ : false;
98
105
  }
99
106
  else {
100
107
  info['websocket'] = false;
@@ -37,20 +37,20 @@ class OASSpecsService {
37
37
  this.schema = options.schema;
38
38
  }
39
39
  async generate(host) {
40
- let schema = this.schema;
40
+ let schemaForSpec = this.schema;
41
41
  let permissions = [];
42
42
  if (this.accountability && this.accountability.admin !== true) {
43
43
  const allowedFields = await fetchAllowedFieldMap({
44
44
  accountability: this.accountability,
45
45
  action: 'read',
46
- }, { schema, knex: this.knex });
47
- schema = reduceSchema(schema, allowedFields);
48
- const policies = await fetchPolicies(this.accountability, { schema, knex: this.knex });
49
- permissions = await fetchPermissions({ action: 'read', policies, accountability: this.accountability }, { schema, knex: this.knex });
46
+ }, { schema: this.schema, knex: this.knex });
47
+ schemaForSpec = reduceSchema(this.schema, allowedFields);
48
+ const policies = await fetchPolicies(this.accountability, { schema: this.schema, knex: this.knex });
49
+ permissions = await fetchPermissions({ policies, accountability: this.accountability }, { schema: this.schema, knex: this.knex });
50
50
  }
51
- const tags = await this.generateTags(schema);
51
+ const tags = await this.generateTags(schemaForSpec);
52
52
  const paths = await this.generatePaths(permissions, tags);
53
- const components = await this.generateComponents(schema, tags);
53
+ const components = await this.generateComponents(schemaForSpec, tags);
54
54
  const isDefaultPublicUrl = env['PUBLIC_URL'] === '/';
55
55
  const url = isDefaultPublicUrl && host ? host : env['PUBLIC_URL'];
56
56
  const spec = {
@@ -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';
@@ -102,7 +102,10 @@ export class UsersService extends ItemsService {
102
102
  */
103
103
  inviteUrl(email, url) {
104
104
  const payload = { email, scope: 'invite' };
105
- const token = jwt.sign(payload, getSecret(), { expiresIn: '7d', issuer: 'directus' });
105
+ const token = jwt.sign(payload, getSecret(), {
106
+ expiresIn: env['USER_INVITE_TOKEN_TTL'],
107
+ issuer: 'directus',
108
+ });
106
109
  return (url ? new Url(url) : new Url(env['PUBLIC_URL']).addPath('admin', 'accept-invite'))
107
110
  .setQuery('token', token)
108
111
  .toString();
@@ -370,7 +373,7 @@ export class UsersService extends ItemsService {
370
373
  await this.createOne(partialUser);
371
374
  }
372
375
  // We want to be able to re-send the verification email
373
- else if (user.status !== ('unverified')) {
376
+ else if (user.status !== 'unverified') {
374
377
  // To avoid giving attackers infos about registered emails we dont fail for violated unique constraints
375
378
  await stall(STALL_TIME, timeStart);
376
379
  return;
@@ -412,7 +415,7 @@ export class UsersService extends ItemsService {
412
415
  if (scope !== 'pending-registration')
413
416
  throw new ForbiddenError();
414
417
  const user = await this.getUserByEmail(email);
415
- if (user?.status !== ('unverified')) {
418
+ if (user?.status !== 'unverified') {
416
419
  throw new InvalidPayloadError({ reason: 'Invalid verification code' });
417
420
  }
418
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
- export declare function getAddress(server: http.Server): string | undefined;
2
+ export declare function getAddress(server: http.Server): {};
@@ -1,9 +1,14 @@
1
1
  import * as http from 'http';
2
+ import { useEnv } from '@directus/env';
2
3
  export function getAddress(server) {
4
+ const env = useEnv();
3
5
  const address = server.address();
4
6
  if (address === null) {
5
7
  // Before the 'listening' event has been emitted or after calling server.close()
6
- return;
8
+ if (env['UNIX_SOCKET_PATH']) {
9
+ return env['UNIX_SOCKET_PATH'];
10
+ }
11
+ return `${env['HOST']}:${env['PORT']}`;
7
12
  }
8
13
  if (typeof address === 'string') {
9
14
  // unix path
@@ -0,0 +1,3 @@
1
+ export declare const getAllowedLogLevels: (level: string) => {
2
+ [k: string]: number;
3
+ };
@@ -0,0 +1,11 @@
1
+ import { useLogger } from '../logger/index.js';
2
+ const logger = useLogger();
3
+ export const getAllowedLogLevels = (level) => {
4
+ const levelValue = logger.levels.values[level];
5
+ if (levelValue === undefined) {
6
+ throw new Error(`Invalid "${level}" log level`);
7
+ }
8
+ return Object.fromEntries(Object.entries(logger.levels.values)
9
+ .filter(([_, value]) => value >= levelValue)
10
+ .sort((a, b) => a[1] - b[1]));
11
+ };
@@ -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;
@@ -4,7 +4,7 @@ import { systemCollectionRows } from '@directus/system-data';
4
4
  import { parseJSON, toArray } from '@directus/utils';
5
5
  import { mapValues } from 'lodash-es';
6
6
  import { useBus } from '../bus/index.js';
7
- import { getSchemaCache, setSchemaCache } from '../cache.js';
7
+ import { getLocalSchemaCache, setLocalSchemaCache } from '../cache.js';
8
8
  import { ALIAS_TYPES } from '../constants.js';
9
9
  import getDatabase from '../database/index.js';
10
10
  import { useLock } from '../lock/index.js';
@@ -22,7 +22,7 @@ export async function getSchema(options, attempt = 0) {
22
22
  const schemaInspector = createInspector(database);
23
23
  return await getDatabaseSchema(database, schemaInspector);
24
24
  }
25
- const cached = await getSchemaCache();
25
+ const cached = await getLocalSchemaCache();
26
26
  if (cached) {
27
27
  return cached;
28
28
  }
@@ -40,39 +40,34 @@ export async function getSchema(options, attempt = 0) {
40
40
  const currentProcessShouldHandleOperation = processId === 1;
41
41
  if (currentProcessShouldHandleOperation === false) {
42
42
  logger.trace('Schema cache is prepared in another process, waiting for result.');
43
- return new Promise((resolve, reject) => {
44
- const TIMEOUT = 10000;
45
- const timeout = setTimeout(() => {
46
- logger.trace('Did not receive schema callback message in time. Pulling schema...');
47
- callback().catch(reject);
48
- }, TIMEOUT);
49
- bus.subscribe(messageKey, callback);
50
- async function callback() {
51
- try {
52
- if (timeout)
53
- clearTimeout(timeout);
54
- const schema = await getSchema(options, attempt + 1);
55
- resolve(schema);
56
- }
57
- catch (error) {
58
- reject(error);
59
- }
60
- finally {
61
- bus.unsubscribe(messageKey, callback);
43
+ const timeout = new Promise((_, reject) => setTimeout(reject, env['CACHE_SCHEMA_SYNC_TIMEOUT']));
44
+ const subscription = new Promise((resolve, reject) => {
45
+ bus.subscribe(messageKey, busListener).catch(reject);
46
+ function busListener(options) {
47
+ if (options.schema === null) {
48
+ return reject();
62
49
  }
50
+ cleanup();
51
+ setLocalSchemaCache(options.schema).catch(reject);
52
+ resolve(options.schema);
53
+ }
54
+ function cleanup() {
55
+ bus.unsubscribe(messageKey, busListener).catch(reject);
63
56
  }
64
57
  });
58
+ return Promise.race([timeout, subscription]).catch(() => getSchema(options, attempt + 1));
65
59
  }
60
+ let schema = null;
66
61
  try {
67
62
  const database = options?.database || getDatabase();
68
63
  const schemaInspector = createInspector(database);
69
- const schema = await getDatabaseSchema(database, schemaInspector);
70
- await setSchemaCache(schema);
64
+ schema = await getDatabaseSchema(database, schemaInspector);
65
+ await setLocalSchemaCache(schema);
71
66
  return schema;
72
67
  }
73
68
  finally {
69
+ await bus.publish(messageKey, { schema });
74
70
  await lock.delete(lockKey);
75
- bus.publish(messageKey, { ready: true });
76
71
  }
77
72
  }
78
73
  async function getDatabaseSchema(database, schemaInspector) {
@@ -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
  }
@@ -13,10 +13,6 @@ export function parseFilterKey(key) {
13
13
  const match = key.match(FILTER_KEY_REGEX);
14
14
  const fieldNameWithFunction = match?.[3]?.trim();
15
15
  const fieldName = fieldNameWithFunction || key.trim();
16
- let functionName;
17
- if (fieldNameWithFunction) {
18
- functionName = match?.[1]?.trim();
19
- return { fieldName, functionName };
20
- }
16
+ const functionName = fieldNameWithFunction ? match?.[1]?.trim() : undefined;
21
17
  return { fieldName, functionName };
22
18
  }
@@ -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
  }
@@ -16,7 +16,7 @@ export declare function sanitizeCollection(collection: Collection | undefined):
16
16
  * @returns sanitized field
17
17
  */
18
18
  export declare function sanitizeField(field: Field | undefined, sanitizeAllSchema?: boolean): Partial<Field> | undefined;
19
- export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "numeric_scale" | "data_type" | "default_value" | "max_length" | "is_nullable" | "is_unique" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
19
+ export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "data_type" | "default_value" | "max_length" | "numeric_scale" | "is_nullable" | "is_unique" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
20
20
  /**
21
21
  * Pick certain database vendor specific relation properties that should be compared when performing diff
22
22
  *
@@ -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
  }