@directus/api 28.0.3 → 29.1.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 (141) hide show
  1. package/dist/auth/drivers/openid.js +12 -6
  2. package/dist/cli/commands/init/index.js +2 -2
  3. package/dist/cli/commands/init/questions.d.ts +1 -1
  4. package/dist/cli/commands/schema/apply.js +1 -1
  5. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  6. package/dist/cli/utils/create-env/index.d.ts +1 -1
  7. package/dist/cli/utils/drivers.d.ts +1 -1
  8. package/dist/constants.d.ts +1 -1
  9. package/dist/controllers/assets.js +1 -1
  10. package/dist/controllers/extensions.js +4 -4
  11. package/dist/database/get-ast-from-query/lib/parse-fields.js +3 -1
  12. package/dist/database/helpers/index.d.ts +4 -4
  13. package/dist/database/helpers/schema/types.d.ts +1 -2
  14. package/dist/database/index.d.ts +1 -1
  15. package/dist/database/migrations/20210225A-add-relations-sort-field.js +1 -1
  16. package/dist/database/migrations/20240924B-populate-versioning-deltas.js +1 -1
  17. package/dist/database/migrations/20250718A-add-direction.d.ts +3 -0
  18. package/dist/database/migrations/20250718A-add-direction.js +10 -0
  19. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  20. package/dist/database/run-ast/utils/apply-parent-filters.js +2 -0
  21. package/dist/database/run-ast/utils/get-field-alias.js +2 -0
  22. package/dist/database/run-ast/utils/remove-temporary-fields.js +8 -2
  23. package/dist/extensions/lib/get-extensions-settings.d.ts +1 -1
  24. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
  25. package/dist/extensions/manager.d.ts +5 -13
  26. package/dist/extensions/manager.js +39 -28
  27. package/dist/flows.d.ts +1 -2
  28. package/dist/operations/throw-error/index.d.ts +7 -0
  29. package/dist/operations/throw-error/index.js +11 -0
  30. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +2 -1
  31. package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
  32. package/dist/services/access.d.ts +1 -2
  33. package/dist/services/access.js +1 -1
  34. package/dist/services/activity.d.ts +1 -1
  35. package/dist/services/assets.d.ts +1 -3
  36. package/dist/services/authentication.d.ts +1 -2
  37. package/dist/services/collections.d.ts +3 -10
  38. package/dist/services/comments.d.ts +1 -2
  39. package/dist/services/dashboards.d.ts +1 -1
  40. package/dist/services/extensions.d.ts +2 -4
  41. package/dist/services/fields.d.ts +1 -2
  42. package/dist/services/fields.js +2 -8
  43. package/dist/services/files.d.ts +2 -3
  44. package/dist/services/flows.d.ts +1 -2
  45. package/dist/services/folders.d.ts +1 -1
  46. package/dist/services/graphql/errors/format.d.ts +1 -1
  47. package/dist/services/graphql/errors/format.js +0 -1
  48. package/dist/services/graphql/index.d.ts +1 -3
  49. package/dist/services/graphql/resolvers/system-admin.d.ts +1 -1
  50. package/dist/services/graphql/resolvers/system-global.d.ts +1 -1
  51. package/dist/services/graphql/resolvers/system.d.ts +1 -1
  52. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  53. package/dist/services/graphql/schema/get-types.js +0 -1
  54. package/dist/services/import-export.d.ts +2 -4
  55. package/dist/services/import-export.js +5 -3
  56. package/dist/services/items.d.ts +2 -12
  57. package/dist/services/items.js +2 -1
  58. package/dist/services/mail/index.d.ts +2 -4
  59. package/dist/services/mail/index.js +11 -5
  60. package/dist/services/meta.d.ts +1 -2
  61. package/dist/services/notifications.d.ts +1 -2
  62. package/dist/services/operations.d.ts +1 -2
  63. package/dist/services/panels.d.ts +1 -1
  64. package/dist/services/payload.d.ts +9 -17
  65. package/dist/services/payload.js +1 -1
  66. package/dist/services/permissions.d.ts +1 -3
  67. package/dist/services/policies.d.ts +1 -2
  68. package/dist/services/policies.js +1 -1
  69. package/dist/services/presets.d.ts +1 -1
  70. package/dist/services/relations.d.ts +2 -3
  71. package/dist/services/revisions.d.ts +1 -2
  72. package/dist/services/roles.d.ts +1 -2
  73. package/dist/services/roles.js +1 -1
  74. package/dist/services/schema.d.ts +1 -2
  75. package/dist/services/server.d.ts +1 -2
  76. package/dist/services/settings.d.ts +1 -1
  77. package/dist/services/shares.d.ts +1 -2
  78. package/dist/services/specifications.d.ts +1 -2
  79. package/dist/services/tfa.d.ts +1 -2
  80. package/dist/services/translations.d.ts +1 -3
  81. package/dist/services/users.d.ts +1 -2
  82. package/dist/services/users.js +1 -1
  83. package/dist/services/utils.d.ts +1 -2
  84. package/dist/services/versions.d.ts +1 -2
  85. package/dist/services/webhooks.d.ts +1 -2
  86. package/dist/services/websocket.d.ts +1 -3
  87. package/dist/types/ast.d.ts +2 -0
  88. package/dist/types/auth.d.ts +0 -6
  89. package/dist/types/index.d.ts +0 -7
  90. package/dist/types/index.js +0 -7
  91. package/dist/utils/apply-diff.d.ts +1 -2
  92. package/dist/utils/apply-diff.js +1 -1
  93. package/dist/utils/apply-snapshot.d.ts +1 -2
  94. package/dist/utils/get-ip-from-req.js +1 -1
  95. package/dist/utils/get-service.d.ts +1 -1
  96. package/dist/utils/get-snapshot-diff.d.ts +1 -1
  97. package/dist/utils/get-snapshot-diff.js +1 -1
  98. package/dist/utils/get-snapshot.d.ts +1 -2
  99. package/dist/utils/get-snapshot.js +8 -3
  100. package/dist/utils/schedule.js +4 -1
  101. package/dist/utils/should-clear-cache.d.ts +1 -1
  102. package/dist/utils/transformations.d.ts +1 -2
  103. package/dist/utils/validate-diff.d.ts +1 -1
  104. package/dist/utils/validate-diff.js +1 -1
  105. package/dist/utils/validate-snapshot.d.ts +1 -1
  106. package/dist/utils/validate-snapshot.js +1 -1
  107. package/dist/utils/validate-user-count-integrity.d.ts +1 -8
  108. package/dist/utils/validate-user-count-integrity.js +1 -9
  109. package/dist/websocket/authenticate.d.ts +2 -1
  110. package/dist/websocket/authenticate.js +25 -4
  111. package/dist/websocket/controllers/base.d.ts +2 -1
  112. package/dist/websocket/controllers/base.js +12 -15
  113. package/dist/websocket/controllers/graphql.js +5 -3
  114. package/dist/websocket/controllers/logs.js +1 -1
  115. package/dist/websocket/controllers/rest.d.ts +1 -1
  116. package/dist/websocket/controllers/rest.js +1 -1
  117. package/dist/websocket/errors.d.ts +1 -1
  118. package/dist/websocket/handlers/heartbeat.d.ts +1 -1
  119. package/dist/websocket/handlers/heartbeat.js +1 -1
  120. package/dist/websocket/messages.d.ts +57 -308
  121. package/dist/websocket/messages.js +5 -10
  122. package/dist/websocket/utils/items.d.ts +1 -1
  123. package/dist/websocket/utils/wait-for-message.d.ts +1 -1
  124. package/dist/websocket/utils/wait-for-message.js +1 -1
  125. package/package.json +86 -85
  126. package/dist/extensions/types.d.ts +0 -19
  127. package/dist/extensions/types.js +0 -1
  128. package/dist/types/assets.d.ts +0 -22
  129. package/dist/types/assets.js +0 -51
  130. package/dist/types/database.d.ts +0 -3
  131. package/dist/types/database.js +0 -1
  132. package/dist/types/graphql.d.ts +0 -14
  133. package/dist/types/graphql.js +0 -1
  134. package/dist/types/items.d.ts +0 -52
  135. package/dist/types/items.js +0 -1
  136. package/dist/types/services.d.ts +0 -22
  137. package/dist/types/services.js +0 -1
  138. package/dist/types/snapshot.d.ts +0 -55
  139. package/dist/types/snapshot.js +0 -13
  140. package/dist/types/webhooks.d.ts +0 -15
  141. package/dist/types/webhooks.js +0 -1
@@ -1,7 +1,6 @@
1
1
  import { type DirectusError } from '@directus/errors';
2
2
  import type { Bus } from '@directus/memory';
3
- import type { PrimaryKey } from '@directus/types';
4
- import type { AbstractServiceOptions, MutationOptions, Webhook } from '../types/index.js';
3
+ import type { AbstractServiceOptions, MutationOptions, PrimaryKey, Webhook } from '@directus/types';
5
4
  import { ItemsService } from './items.js';
6
5
  export declare class WebhooksService extends ItemsService<Webhook> {
7
6
  messenger: Bus;
@@ -1,6 +1,4 @@
1
- import type { ActionHandler } from '@directus/types';
2
- import type { WebSocketMessage } from '../websocket/messages.js';
3
- import type { WebSocketClient } from '../websocket/types.js';
1
+ import type { ActionHandler, WebSocketClient, WebSocketMessage } from '@directus/types';
4
2
  export declare class WebSocketService {
5
3
  private controller;
6
4
  constructor();
@@ -66,6 +66,8 @@ export type FieldNode = {
66
66
  type: 'field';
67
67
  name: string;
68
68
  fieldKey: string;
69
+ /** If the field was created through alias query parameters */
70
+ alias: boolean;
69
71
  /**
70
72
  * Which permission cases have to be met on the current item for this field to return a value
71
73
  */
@@ -40,10 +40,4 @@ export type ShareData = {
40
40
  share_max_uses?: number;
41
41
  share_password?: string;
42
42
  };
43
- export type LoginResult = {
44
- accessToken: string;
45
- refreshToken: string;
46
- expires: number;
47
- id?: string;
48
- };
49
43
  export type AuthenticationMode = 'json' | 'cookie' | 'session';
@@ -1,14 +1,7 @@
1
- export * from './assets.js';
2
1
  export * from './ast.js';
3
2
  export * from './auth.js';
4
3
  export * from './collection.js';
5
- export * from './database.js';
6
4
  export * from './events.js';
7
- export * from './graphql.js';
8
- export * from './items.js';
9
5
  export * from './meta.js';
10
6
  export * from './migration.js';
11
7
  export * from './revision.js';
12
- export * from './services.js';
13
- export * from './snapshot.js';
14
- export * from './webhooks.js';
@@ -1,14 +1,7 @@
1
- export * from './assets.js';
2
1
  export * from './ast.js';
3
2
  export * from './auth.js';
4
3
  export * from './collection.js';
5
- export * from './database.js';
6
4
  export * from './events.js';
7
- export * from './graphql.js';
8
- export * from './items.js';
9
5
  export * from './meta.js';
10
6
  export * from './migration.js';
11
7
  export * from './revision.js';
12
- export * from './services.js';
13
- export * from './snapshot.js';
14
- export * from './webhooks.js';
@@ -1,7 +1,6 @@
1
- import type { SchemaOverview } from '@directus/types';
1
+ import type { SchemaOverview, Snapshot, SnapshotDiff, SnapshotField } from '@directus/types';
2
2
  import type { Diff } from 'deep-diff';
3
3
  import type { Knex } from 'knex';
4
- import type { Snapshot, SnapshotDiff, SnapshotField } from '../types/index.js';
5
4
  export declare function applyDiff(currentSnapshot: Snapshot, snapshotDiff: SnapshotDiff, options?: {
6
5
  database?: Knex;
7
6
  schema?: SchemaOverview;
@@ -1,3 +1,4 @@
1
+ import { DiffKind } from '@directus/types';
1
2
  import deepDiff from 'deep-diff';
2
3
  import { cloneDeep, merge, set } from 'lodash-es';
3
4
  import { flushCaches } from '../cache.js';
@@ -8,7 +9,6 @@ import { useLogger } from '../logger/index.js';
8
9
  import { CollectionsService } from '../services/collections.js';
9
10
  import { FieldsService } from '../services/fields.js';
10
11
  import { RelationsService } from '../services/relations.js';
11
- import { DiffKind } from '../types/index.js';
12
12
  import { transaction } from '../utils/transaction.js';
13
13
  import { getSchema } from './get-schema.js';
14
14
  const logger = useLogger();
@@ -1,6 +1,5 @@
1
- import type { SchemaOverview } from '@directus/types';
1
+ import type { SchemaOverview, Snapshot, SnapshotDiff } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { Snapshot, SnapshotDiff } from '../types/index.js';
4
3
  export declare function applySnapshot(snapshot: Snapshot, options?: {
5
4
  database?: Knex;
6
5
  schema?: SchemaOverview;
@@ -42,5 +42,5 @@ export function getIPFromReq(req) {
42
42
  }
43
43
  }
44
44
  // IP addresses starting with ::ffff: are IPv4 addresses in IPv6 format. We can strip the prefix to get back to IPv4
45
- return ip?.startsWith('::ffff:') ? ip.substring(7) : ip ?? null;
45
+ return ip?.startsWith('::ffff:') ? ip.substring(7) : (ip ?? null);
46
46
  }
@@ -1,5 +1,5 @@
1
+ import type { AbstractServiceOptions } from '@directus/types';
1
2
  import { ItemsService } from '../services/index.js';
2
- import type { AbstractServiceOptions } from '../types/services.js';
3
3
  /**
4
4
  * Select the correct service for the given collection. This allows the individual services to run
5
5
  * their custom checks (f.e. it allows `UsersService` to prevent updating TFA secret from outside).
@@ -1,2 +1,2 @@
1
- import type { Snapshot, SnapshotDiff } from '../types/index.js';
1
+ import type { Snapshot, SnapshotDiff } from '@directus/types';
2
2
  export declare function getSnapshotDiff(current: Snapshot, after: Snapshot): SnapshotDiff;
@@ -1,5 +1,5 @@
1
1
  import deepDiff from 'deep-diff';
2
- import { DiffKind } from '../types/index.js';
2
+ import { DiffKind } from '@directus/types';
3
3
  import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
4
4
  export function getSnapshotDiff(current, after) {
5
5
  const diffedSnapshot = {
@@ -1,6 +1,5 @@
1
- import type { SchemaOverview } from '@directus/types';
1
+ import type { SchemaOverview, Snapshot } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { Snapshot } from '../types/index.js';
4
3
  export declare function getSnapshot(options?: {
5
4
  database?: Knex;
6
5
  schema?: SchemaOverview;
@@ -18,9 +18,9 @@ export async function getSnapshot(options) {
18
18
  fieldsService.readAll(),
19
19
  relationsService.readAll(),
20
20
  ]);
21
- const collectionsFiltered = collectionsRaw.filter((item) => excludeSystem(item));
22
- const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item));
23
- const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item));
21
+ const collectionsFiltered = collectionsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
22
+ const fieldsFiltered = fieldsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
23
+ const relationsFiltered = relationsRaw.filter((item) => excludeSystem(item) && excludeUntracked(item));
24
24
  const collectionsSorted = sortBy(mapValues(collectionsFiltered, sortDeep), ['collection']);
25
25
  const fieldsSorted = sortBy(mapValues(fieldsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
26
26
  const relationsSorted = sortBy(mapValues(relationsFiltered, sortDeep), ['collection', 'meta.id']).map(omitID);
@@ -38,6 +38,11 @@ function excludeSystem(item) {
38
38
  return false;
39
39
  return true;
40
40
  }
41
+ function excludeUntracked(item) {
42
+ if (item?.meta === null)
43
+ return false;
44
+ return true;
45
+ }
41
46
  function omitID(item) {
42
47
  return omit(item, 'meta.id');
43
48
  }
@@ -13,7 +13,10 @@ export function validateCron(rule) {
13
13
  export function scheduleSynchronizedJob(id, rule, cb) {
14
14
  const clock = new SynchronizedClock(`${id}:${rule}`);
15
15
  const job = schedule.scheduleJob(rule, async (fireDate) => {
16
- const nextTimestamp = job.nextInvocation().getTime();
16
+ const nextInvocation = job.nextInvocation();
17
+ if (!nextInvocation)
18
+ return;
19
+ const nextTimestamp = nextInvocation.getTime();
17
20
  const wasSet = await clock.set(nextTimestamp);
18
21
  if (wasSet) {
19
22
  await cb(fireDate);
@@ -1,5 +1,5 @@
1
1
  import type Keyv from 'keyv';
2
- import type { MutationOptions } from '../types/items.js';
2
+ import type { MutationOptions } from '@directus/types';
3
3
  /**
4
4
  * Check whether cache should be cleared
5
5
  *
@@ -1,5 +1,4 @@
1
- import type { File } from '@directus/types';
2
- import type { Transformation, TransformationSet } from '../types/index.js';
1
+ import type { File, Transformation, TransformationSet } from '@directus/types';
3
2
  export declare function resolvePreset({ transformationParams, acceptFormat }: TransformationSet, file: File): Transformation[];
4
3
  /**
5
4
  * Try to extract a file format from an array of `Transformation`'s.
@@ -1,4 +1,4 @@
1
- import type { SnapshotDiffWithHash, SnapshotWithHash } from '../types/snapshot.js';
1
+ import type { SnapshotDiffWithHash, SnapshotWithHash } from '@directus/types';
2
2
  /**
3
3
  * Validates the diff against the current schema snapshot.
4
4
  *
@@ -1,6 +1,6 @@
1
1
  import Joi from 'joi';
2
2
  import { InvalidPayloadError } from '@directus/errors';
3
- import { DiffKind } from '../types/snapshot.js';
3
+ import { DiffKind } from '@directus/types';
4
4
  const deepDiffSchema = Joi.object({
5
5
  kind: Joi.string()
6
6
  .valid(...Object.values(DiffKind))
@@ -1,4 +1,4 @@
1
- import type { Snapshot } from '../types/index.js';
1
+ import type { Snapshot } from '@directus/types';
2
2
  /**
3
3
  * Validates the snapshot against the current instance.
4
4
  **/
@@ -3,7 +3,7 @@ import Joi from 'joi';
3
3
  import { ALIAS_TYPES } from '../constants.js';
4
4
  import { getDatabaseClient } from '../database/index.js';
5
5
  import { InvalidPayloadError } from '@directus/errors';
6
- import { DatabaseClients } from '../types/index.js';
6
+ import { DatabaseClients } from '@directus/types';
7
7
  import { version } from 'directus/version';
8
8
  const snapshotJoiSchema = Joi.object({
9
9
  version: Joi.number().valid(1).required(),
@@ -1,12 +1,5 @@
1
+ import { UserIntegrityCheckFlag } from '@directus/types';
1
2
  import { type FetchUserCountOptions } from './fetch-user-count/fetch-user-count.js';
2
- export declare enum UserIntegrityCheckFlag {
3
- None = 0,
4
- /** Check if the number of remaining admin users is greater than 0 */
5
- RemainingAdmins = 1,
6
- /** Check if the number of users is within the limits */
7
- UserLimits = 2,
8
- All = 3
9
- }
10
3
  export interface ValidateUserCountIntegrityOptions extends Omit<FetchUserCountOptions, 'adminOnly'> {
11
4
  flags: UserIntegrityCheckFlag;
12
5
  }
@@ -1,16 +1,8 @@
1
+ import { UserIntegrityCheckFlag } from '@directus/types';
1
2
  import { validateRemainingAdminCount } from '../permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js';
2
3
  import { checkUserLimits } from '../telemetry/utils/check-user-limits.js';
3
4
  import { shouldCheckUserLimits } from '../telemetry/utils/should-check-user-limits.js';
4
5
  import { fetchUserCount } from './fetch-user-count/fetch-user-count.js';
5
- export var UserIntegrityCheckFlag;
6
- (function (UserIntegrityCheckFlag) {
7
- UserIntegrityCheckFlag[UserIntegrityCheckFlag["None"] = 0] = "None";
8
- /** Check if the number of remaining admin users is greater than 0 */
9
- UserIntegrityCheckFlag[UserIntegrityCheckFlag["RemainingAdmins"] = 1] = "RemainingAdmins";
10
- /** Check if the number of users is within the limits */
11
- UserIntegrityCheckFlag[UserIntegrityCheckFlag["UserLimits"] = 2] = "UserLimits";
12
- UserIntegrityCheckFlag[UserIntegrityCheckFlag["All"] = 3] = "All";
13
- })(UserIntegrityCheckFlag || (UserIntegrityCheckFlag = {}));
14
6
  export async function validateUserCountIntegrity(options) {
15
7
  const validateUserLimits = (options.flags & UserIntegrityCheckFlag.UserLimits) !== 0;
16
8
  const validateRemainingAdminUsers = (options.flags & UserIntegrityCheckFlag.RemainingAdmins) !== 0;
@@ -1,4 +1,5 @@
1
+ import type { Accountability } from '@directus/types';
1
2
  import type { BasicAuthMessage } from './messages.js';
2
3
  import type { AuthenticationState } from './types.js';
3
- export declare function authenticateConnection(message: BasicAuthMessage & Record<string, any>): Promise<AuthenticationState>;
4
+ export declare function authenticateConnection(message: BasicAuthMessage & Record<string, any>, accountabilityOverrides?: Partial<Accountability>): Promise<AuthenticationState>;
4
5
  export declare function authenticationSuccess(uid?: string | number, refresh_token?: string): string;
@@ -1,10 +1,14 @@
1
+ import { isEqual } from 'lodash-es';
1
2
  import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
3
+ import getDatabase from '../database/index.js';
4
+ import emitter from '../emitter.js';
5
+ import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
2
6
  import { AuthenticationService } from '../services/index.js';
3
7
  import { getAccountabilityForToken } from '../utils/get-accountability-for-token.js';
4
8
  import { getSchema } from '../utils/get-schema.js';
5
9
  import { WebSocketError } from './errors.js';
6
10
  import { getExpiresAtForToken } from './utils/get-expires-at-for-token.js';
7
- export async function authenticateConnection(message) {
11
+ export async function authenticateConnection(message, accountabilityOverrides) {
8
12
  let access_token, refresh_token;
9
13
  try {
10
14
  if ('email' in message && 'password' in message) {
@@ -24,9 +28,26 @@ export async function authenticateConnection(message) {
24
28
  }
25
29
  if (!access_token)
26
30
  throw new Error();
27
- const accountability = await getAccountabilityForToken(access_token);
28
- const expires_at = getExpiresAtForToken(access_token);
29
- return { accountability, expires_at, refresh_token };
31
+ const defaultAccountability = createDefaultAccountability(accountabilityOverrides);
32
+ const authenticationState = {
33
+ accountability: defaultAccountability,
34
+ expires_at: getExpiresAtForToken(access_token),
35
+ refresh_token,
36
+ };
37
+ const customAccountability = await emitter.emitFilter('websocket.authenticate', defaultAccountability, {
38
+ message,
39
+ }, {
40
+ database: getDatabase(),
41
+ schema: null,
42
+ accountability: null,
43
+ });
44
+ if (customAccountability && isEqual(customAccountability, defaultAccountability) === false) {
45
+ authenticationState.accountability = customAccountability;
46
+ }
47
+ else {
48
+ authenticationState.accountability = await getAccountabilityForToken(access_token, defaultAccountability);
49
+ }
50
+ return authenticationState;
30
51
  }
31
52
  catch {
32
53
  throw new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message['uid']);
@@ -1,9 +1,10 @@
1
1
  import type { Accountability } from '@directus/types';
2
+ import { WebSocketMessage } from '@directus/types';
2
3
  import type { IncomingMessage, Server as httpServer } from 'http';
3
4
  import type { RateLimiterAbstract } from 'rate-limiter-flexible';
4
5
  import type internal from 'stream';
5
6
  import WebSocket, { type Server } from 'ws';
6
- import { WebSocketAuthMessage, WebSocketMessage } from '../messages.js';
7
+ import { WebSocketAuthMessage } from '../messages.js';
7
8
  import type { AuthenticationState, UpgradeContext, WebSocketAuthentication, WebSocketClient } from '../types.js';
8
9
  export default abstract class SocketController {
9
10
  server: Server;
@@ -1,5 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { InvalidProviderConfigError, TokenExpiredError } from '@directus/errors';
3
+ import { WebSocketMessage } from '@directus/types';
3
4
  import { parseJSON, toBoolean } from '@directus/utils';
4
5
  import cookie from 'cookie';
5
6
  import { randomUUID } from 'node:crypto';
@@ -10,12 +11,10 @@ import emitter from '../../emitter.js';
10
11
  import { useLogger } from '../../logger/index.js';
11
12
  import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
12
13
  import { createRateLimiter } from '../../rate-limiter.js';
13
- import { getAccountabilityForToken } from '../../utils/get-accountability-for-token.js';
14
14
  import { getIPFromReq } from '../../utils/get-ip-from-req.js';
15
15
  import { authenticateConnection, authenticationSuccess } from '../authenticate.js';
16
16
  import { WebSocketError, handleWebSocketError } from '../errors.js';
17
- import { AuthMode, WebSocketAuthMessage, WebSocketMessage } from '../messages.js';
18
- import { getExpiresAtForToken } from '../utils/get-expires-at-for-token.js';
17
+ import { AuthMode, WebSocketAuthMessage } from '../messages.js';
19
18
  import { getMessageType } from '../utils/message.js';
20
19
  import { waitForAnyMessage, waitForMessageType } from '../utils/wait-for-message.js';
21
20
  const TOKEN_CHECK_INTERVAL = 15 * 60 * 1000; // 15 minutes
@@ -139,8 +138,9 @@ export default class SocketController {
139
138
  let expires_at = null;
140
139
  if (token) {
141
140
  try {
142
- accountability = await getAccountabilityForToken(token);
143
- expires_at = getExpiresAtForToken(token);
141
+ const state = await authenticateConnection({ access_token: token }, accountabilityOverrides);
142
+ accountability = state.accountability;
143
+ expires_at = state.expires_at;
144
144
  }
145
145
  catch {
146
146
  accountability = null;
@@ -162,7 +162,6 @@ export default class SocketController {
162
162
  socket.destroy();
163
163
  return;
164
164
  }
165
- Object.assign(accountability, accountabilityOverrides);
166
165
  this.server.handleUpgrade(request, socket, head, async (ws) => {
167
166
  this.catchInvalidMessages(ws);
168
167
  const state = { accountability, expires_at };
@@ -176,10 +175,7 @@ export default class SocketController {
176
175
  const payload = await waitForAnyMessage(ws, this.authentication.timeout);
177
176
  if (getMessageType(payload) !== 'auth')
178
177
  throw new Error();
179
- const state = await authenticateConnection(WebSocketAuthMessage.parse(payload));
180
- if (state.accountability) {
181
- Object.assign(state.accountability, accountabilityOverrides);
182
- }
178
+ const state = await authenticateConnection(WebSocketAuthMessage.parse(payload), accountabilityOverrides);
183
179
  this.checkUserRequirements(state.accountability);
184
180
  ws.send(authenticationSuccess(payload['uid'], state.refresh_token));
185
181
  this.server.emit('connection', ws, state);
@@ -268,19 +264,20 @@ export default class SocketController {
268
264
  }
269
265
  async handleAuthRequest(client, message) {
270
266
  try {
271
- const { accountability, expires_at, refresh_token } = await authenticateConnection(message);
272
- this.checkUserRequirements(accountability);
267
+ let accountabilityOverrides = {};
273
268
  /**
274
269
  * Re-use the existing ip, userAgent and origin accountability properties.
275
270
  * They are only sent in the original connection request
276
271
  */
277
- if (accountability && client.accountability) {
278
- Object.assign(accountability, {
272
+ if (client.accountability) {
273
+ accountabilityOverrides = {
279
274
  ip: client.accountability.ip,
280
275
  userAgent: client.accountability.userAgent,
281
276
  origin: client.accountability.origin,
282
- });
277
+ };
283
278
  }
279
+ const { accountability, expires_at, refresh_token } = await authenticateConnection(message, accountabilityOverrides);
280
+ this.checkUserRequirements(accountability);
284
281
  client.accountability = accountability;
285
282
  client.expires_at = expires_at;
286
283
  this.setTokenExpireTimer(client);
@@ -1,16 +1,16 @@
1
1
  import { CloseCode, MessageType, makeServer } from 'graphql-ws';
2
2
  import { useLogger } from '../../logger/index.js';
3
+ import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
3
4
  import { bindPubSub } from '../../services/graphql/subscription.js';
4
5
  import { GraphQLService } from '../../services/index.js';
5
- import { getSchema } from '../../utils/get-schema.js';
6
6
  import { getAddress } from '../../utils/get-address.js';
7
+ import { getSchema } from '../../utils/get-schema.js';
7
8
  import { authenticateConnection } from '../authenticate.js';
8
9
  import { handleWebSocketError } from '../errors.js';
9
- import { ConnectionParams, WebSocketMessage } from '../messages.js';
10
+ import { ConnectionParams } from '../messages.js';
10
11
  import { getMessageType } from '../utils/message.js';
11
12
  import SocketController from './base.js';
12
13
  import { registerWebSocketEvents } from './hooks.js';
13
- import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
14
14
  const logger = useLogger();
15
15
  export class GraphQLSubscriptionController extends SocketController {
16
16
  gql;
@@ -51,6 +51,8 @@ export class GraphQLSubscriptionController extends SocketController {
51
51
  if (typeof params.access_token === 'string') {
52
52
  const { accountability, expires_at } = await authenticateConnection({
53
53
  access_token: params.access_token,
54
+ }, {
55
+ ip: client.accountability?.ip ?? null,
54
56
  });
55
57
  client.accountability = accountability;
56
58
  client.expires_at = expires_at;
@@ -2,7 +2,7 @@ import { useEnv } from '@directus/env';
2
2
  import emitter from '../../emitter.js';
3
3
  import { useLogger } from '../../logger/index.js';
4
4
  import { handleWebSocketError, WebSocketError } from '../errors.js';
5
- import { AuthMode, WebSocketMessage } from '../messages.js';
5
+ import { AuthMode } from '../messages.js';
6
6
  import SocketController from './base.js';
7
7
  const logger = useLogger();
8
8
  export class LogsController extends SocketController {
@@ -1,5 +1,5 @@
1
+ import { WebSocketMessage } from '@directus/types';
1
2
  import type { Server as httpServer } from 'http';
2
- import { WebSocketMessage } from '../messages.js';
3
3
  import SocketController from './base.js';
4
4
  export declare class WebSocketController extends SocketController {
5
5
  constructor(httpServer: httpServer);
@@ -1,9 +1,9 @@
1
1
  import { parseJSON } from '@directus/utils';
2
+ import { WebSocketMessage } from '@directus/types';
2
3
  import emitter from '../../emitter.js';
3
4
  import { useLogger } from '../../logger/index.js';
4
5
  import { getAddress } from '../../utils/get-address.js';
5
6
  import { WebSocketError, handleWebSocketError } from '../errors.js';
6
- import { WebSocketMessage } from '../messages.js';
7
7
  import SocketController from './base.js';
8
8
  import { registerWebSocketEvents } from './hooks.js';
9
9
  const logger = useLogger();
@@ -1,4 +1,4 @@
1
- import type { DirectusError } from '@directus/errors';
1
+ import { type DirectusError } from '@directus/errors';
2
2
  import type { WebSocket } from 'ws';
3
3
  import { ZodError } from 'zod';
4
4
  import type { WebSocketResponse } from './messages.js';
@@ -1,5 +1,5 @@
1
+ import { WebSocketMessage } from '@directus/types';
1
2
  import { WebSocketController } from '../controllers/index.js';
2
- import { WebSocketMessage } from '../messages.js';
3
3
  import type { WebSocketClient } from '../types.js';
4
4
  export declare class HeartbeatHandler {
5
5
  private pulse;
@@ -1,9 +1,9 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { ServiceUnavailableError } from '@directus/errors';
3
+ import { WebSocketMessage } from '@directus/types';
3
4
  import { toBoolean } from '@directus/utils';
4
5
  import emitter from '../../emitter.js';
5
6
  import { WebSocketController, getWebSocketController } from '../controllers/index.js';
6
- import { WebSocketMessage } from '../messages.js';
7
7
  import { fmtMessage, getMessageType } from '../utils/message.js';
8
8
  const env = useEnv();
9
9
  const HEARTBEAT_FREQUENCY = Number(env['WEBSOCKETS_HEARTBEAT_PERIOD']) * 1000;