@directus/api 17.0.0 → 17.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/controllers/items.js +8 -7
  2. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
  3. package/dist/flows.js +2 -1
  4. package/dist/middleware/collection-exists.js +6 -6
  5. package/dist/operations/request/index.js +5 -5
  6. package/dist/request/agent-with-ip-validation.d.ts +11 -0
  7. package/dist/request/agent-with-ip-validation.js +34 -0
  8. package/dist/request/index.js +6 -5
  9. package/dist/request/is-denied-ip.d.ts +1 -0
  10. package/dist/request/{validate-ip.js → is-denied-ip.js} +10 -12
  11. package/dist/services/collections.d.ts +3 -2
  12. package/dist/services/collections.js +1 -1
  13. package/dist/services/fields.js +2 -1
  14. package/dist/services/files.js +4 -3
  15. package/dist/services/graphql/index.js +3 -2
  16. package/dist/services/import-export.js +2 -1
  17. package/dist/services/items.js +2 -1
  18. package/dist/services/permissions.js +1 -1
  19. package/dist/services/relations.js +1 -1
  20. package/dist/services/specifications.js +4 -3
  21. package/dist/services/utils.js +1 -1
  22. package/dist/telemetry/utils/get-user-item-count.js +2 -1
  23. package/dist/types/collection.d.ts +2 -13
  24. package/dist/utils/get-field-system-rows.d.ts +2 -0
  25. package/dist/utils/get-field-system-rows.js +17 -0
  26. package/dist/utils/get-permissions.js +1 -1
  27. package/dist/utils/get-schema.js +3 -2
  28. package/dist/utils/merge-permissions-for-share.js +1 -1
  29. package/dist/utils/should-skip-cache.js +1 -1
  30. package/dist/websocket/handlers/items.js +2 -1
  31. package/dist/websocket/messages.d.ts +18 -18
  32. package/package.json +34 -33
  33. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -107
  34. package/dist/database/system-data/app-access-permissions/index.d.ts +0 -3
  35. package/dist/database/system-data/app-access-permissions/index.js +0 -17
  36. package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +0 -17
  37. package/dist/database/system-data/collections/collections.yaml +0 -103
  38. package/dist/database/system-data/collections/index.d.ts +0 -2
  39. package/dist/database/system-data/collections/index.js +0 -9
  40. package/dist/database/system-data/fields/_defaults.yaml +0 -16
  41. package/dist/database/system-data/fields/activity.yaml +0 -83
  42. package/dist/database/system-data/fields/collections.yaml +0 -249
  43. package/dist/database/system-data/fields/dashboards.yaml +0 -20
  44. package/dist/database/system-data/fields/extensions.yaml +0 -10
  45. package/dist/database/system-data/fields/fields.yaml +0 -104
  46. package/dist/database/system-data/fields/files.yaml +0 -160
  47. package/dist/database/system-data/fields/flows.yaml +0 -26
  48. package/dist/database/system-data/fields/folders.yaml +0 -14
  49. package/dist/database/system-data/fields/index.d.ts +0 -2
  50. package/dist/database/system-data/fields/index.js +0 -33
  51. package/dist/database/system-data/fields/migrations.yaml +0 -10
  52. package/dist/database/system-data/fields/notifications.yaml +0 -15
  53. package/dist/database/system-data/fields/operations.yaml +0 -23
  54. package/dist/database/system-data/fields/panels.yaml +0 -29
  55. package/dist/database/system-data/fields/permissions.yaml +0 -37
  56. package/dist/database/system-data/fields/presets.yaml +0 -56
  57. package/dist/database/system-data/fields/relations.yaml +0 -34
  58. package/dist/database/system-data/fields/revisions.yaml +0 -30
  59. package/dist/database/system-data/fields/roles.yaml +0 -61
  60. package/dist/database/system-data/fields/sessions.yaml +0 -16
  61. package/dist/database/system-data/fields/settings.yaml +0 -471
  62. package/dist/database/system-data/fields/shares.yaml +0 -83
  63. package/dist/database/system-data/fields/translations.yaml +0 -27
  64. package/dist/database/system-data/fields/users.yaml +0 -224
  65. package/dist/database/system-data/fields/versions.yaml +0 -38
  66. package/dist/database/system-data/fields/webhooks.yaml +0 -141
  67. package/dist/database/system-data/relations/index.d.ts +0 -2
  68. package/dist/database/system-data/relations/index.js +0 -9
  69. package/dist/database/system-data/relations/relations.yaml +0 -197
  70. package/dist/request/request-interceptor.d.ts +0 -2
  71. package/dist/request/request-interceptor.js +0 -28
  72. package/dist/request/response-interceptor.d.ts +0 -2
  73. package/dist/request/response-interceptor.js +0 -5
  74. package/dist/request/validate-ip.d.ts +0 -1
@@ -8,9 +8,10 @@ import { ItemsService } from '../services/items.js';
8
8
  import { MetaService } from '../services/meta.js';
9
9
  import asyncHandler from '../utils/async-handler.js';
10
10
  import { sanitizeQuery } from '../utils/sanitize-query.js';
11
+ import { isSystemCollection } from '@directus/system-data';
11
12
  const router = express.Router();
12
13
  router.post('/:collection', collectionExists, asyncHandler(async (req, res, next) => {
13
- if (req.params['collection'].startsWith('directus_'))
14
+ if (isSystemCollection(req.params['collection']))
14
15
  throw new ForbiddenError();
15
16
  if (req.singleton) {
16
17
  throw new RouteNotFoundError({ path: req.path });
@@ -47,7 +48,7 @@ router.post('/:collection', collectionExists, asyncHandler(async (req, res, next
47
48
  return next();
48
49
  }), respond);
49
50
  const readHandler = asyncHandler(async (req, res, next) => {
50
- if (req.params['collection'].startsWith('directus_'))
51
+ if (isSystemCollection(req.params['collection']))
51
52
  throw new ForbiddenError();
52
53
  const service = new ItemsService(req.collection, {
53
54
  accountability: req.accountability,
@@ -77,7 +78,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
77
78
  router.search('/:collection', collectionExists, validateBatch('read'), readHandler, respond);
78
79
  router.get('/:collection', collectionExists, readHandler, respond);
79
80
  router.get('/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => {
80
- if (req.params['collection'].startsWith('directus_'))
81
+ if (isSystemCollection(req.params['collection']))
81
82
  throw new ForbiddenError();
82
83
  const service = new ItemsService(req.collection, {
83
84
  accountability: req.accountability,
@@ -90,7 +91,7 @@ router.get('/:collection/:pk', collectionExists, asyncHandler(async (req, res, n
90
91
  return next();
91
92
  }), respond);
92
93
  router.patch('/:collection', collectionExists, validateBatch('update'), asyncHandler(async (req, res, next) => {
93
- if (req.params['collection'].startsWith('directus_'))
94
+ if (isSystemCollection(req.params['collection']))
94
95
  throw new ForbiddenError();
95
96
  const service = new ItemsService(req.collection, {
96
97
  accountability: req.accountability,
@@ -126,7 +127,7 @@ router.patch('/:collection', collectionExists, validateBatch('update'), asyncHan
126
127
  return next();
127
128
  }), respond);
128
129
  router.patch('/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => {
129
- if (req.params['collection'].startsWith('directus_'))
130
+ if (isSystemCollection(req.params['collection']))
130
131
  throw new ForbiddenError();
131
132
  if (req.singleton) {
132
133
  throw new RouteNotFoundError({ path: req.path });
@@ -149,7 +150,7 @@ router.patch('/:collection/:pk', collectionExists, asyncHandler(async (req, res,
149
150
  return next();
150
151
  }), respond);
151
152
  router.delete('/:collection', collectionExists, validateBatch('delete'), asyncHandler(async (req, _res, next) => {
152
- if (req.params['collection'].startsWith('directus_'))
153
+ if (isSystemCollection(req.params['collection']))
153
154
  throw new ForbiddenError();
154
155
  const service = new ItemsService(req.collection, {
155
156
  accountability: req.accountability,
@@ -168,7 +169,7 @@ router.delete('/:collection', collectionExists, validateBatch('delete'), asyncHa
168
169
  return next();
169
170
  }), respond);
170
171
  router.delete('/:collection/:pk', collectionExists, asyncHandler(async (req, _res, next) => {
171
- if (req.params['collection'].startsWith('directus_'))
172
+ if (isSystemCollection(req.params['collection']))
172
173
  throw new ForbiddenError();
173
174
  const service = new ItemsService(req.collection, {
174
175
  accountability: req.accountability,
@@ -16,7 +16,7 @@ export declare function generateApiExtensionsSandboxEntrypoint(type: ApiExtensio
16
16
  unregisterFunction: () => Promise<void>;
17
17
  } | {
18
18
  code: string;
19
- hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "PUT" | "PATCH">, cb: import("isolated-vm").Reference<(req: {
19
+ hostFunctions: ((path: import("isolated-vm").Reference<string>, method: import("isolated-vm").Reference<"GET" | "POST" | "DELETE" | "PATCH" | "PUT">, cb: import("isolated-vm").Reference<(req: {
20
20
  url: string;
21
21
  headers: import("http").IncomingHttpHeaders;
22
22
  body: string;
package/dist/flows.js CHANGED
@@ -19,6 +19,7 @@ import { mapValuesDeep } from './utils/map-values-deep.js';
19
19
  import { redactObject } from './utils/redact-object.js';
20
20
  import { sanitizeError } from './utils/sanitize-error.js';
21
21
  import { scheduleSynchronizedJob, validateCron } from './utils/schedule.js';
22
+ import { isSystemCollection } from '@directus/system-data';
22
23
  let flowManager;
23
24
  export function getFlowManager() {
24
25
  if (flowManager) {
@@ -111,7 +112,7 @@ class FlowManager {
111
112
  if (!flow.options?.['collections'])
112
113
  return [];
113
114
  return toArray(flow.options['collections']).map((collection) => {
114
- if (collection.startsWith('directus_')) {
115
+ if (isSystemCollection(collection)) {
115
116
  const action = scope.split('.')[1];
116
117
  return collection.substring(9) + '.' + action;
117
118
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Check if requested collection exists, and save it to req.collection
3
3
  */
4
- import { systemCollectionRows } from '../database/system-data/collections/index.js';
5
4
  import { ForbiddenError } from '@directus/errors';
5
+ import { systemCollectionRows } from '@directus/system-data';
6
6
  import asyncHandler from '../utils/async-handler.js';
7
7
  const collectionExists = asyncHandler(async (req, _res, next) => {
8
8
  if (!req.params['collection'])
@@ -11,11 +11,11 @@ const collectionExists = asyncHandler(async (req, _res, next) => {
11
11
  throw new ForbiddenError();
12
12
  }
13
13
  req.collection = req.params['collection'];
14
- if (req.collection.startsWith('directus_')) {
15
- const systemRow = systemCollectionRows.find((collection) => {
16
- return collection?.collection === req.collection;
17
- });
18
- req.singleton = !!systemRow?.singleton;
14
+ const systemCollectionRow = systemCollectionRows.find((collection) => {
15
+ return collection?.collection === req.collection;
16
+ });
17
+ if (systemCollectionRow !== undefined) {
18
+ req.singleton = !!systemCollectionRow?.singleton;
19
19
  }
20
20
  else {
21
21
  req.singleton = req.schema.collections[req.collection]?.singleton ?? false;
@@ -24,12 +24,12 @@ export default defineOperationApi({
24
24
  return { status: result.status, statusText: result.statusText, headers: result.headers, data: result.data };
25
25
  }
26
26
  catch (error) {
27
- if (isAxiosError(error)) {
27
+ if (isAxiosError(error) && error.response) {
28
28
  throw JSON.stringify({
29
- status: error.response?.status,
30
- statusText: error.response?.statusText,
31
- headers: error.response?.headers,
32
- data: error.response?.data,
29
+ status: error.response.status,
30
+ statusText: error.response.statusText,
31
+ headers: error.response.headers,
32
+ data: error.response.data,
33
33
  });
34
34
  }
35
35
  else {
@@ -0,0 +1,11 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import type { Agent, ClientRequestArgs } from 'node:http';
3
+ /**
4
+ * 'createConnection' is missing in 'Agent' type, but assigned in actual implementation:
5
+ * https://github.com/nodejs/node/blob/8a41d9b636be86350cd32847c3f89d327c4f6ff7/lib/_http_agent.js#L215
6
+ */
7
+ export type _Agent = Agent & {
8
+ createConnection: NonNullable<ClientRequestArgs['createConnection']>;
9
+ };
10
+ /** Extends a HTTP agent with IP validation */
11
+ export declare const agentWithIpValidation: (agent: Agent) => Agent;
@@ -0,0 +1,34 @@
1
+ import { isIP } from 'node:net';
2
+ import { isDeniedIp } from './is-denied-ip.js';
3
+ const deniedError = (domain) => new Error(`Requested domain "${domain}" resolves to a denied IP address`);
4
+ /** Extends a HTTP agent with IP validation */
5
+ export const agentWithIpValidation = (agent) => {
6
+ const _agent = agent;
7
+ const { createConnection } = _agent;
8
+ _agent.createConnection = function (options, oncreate) {
9
+ const { host } = options;
10
+ /*
11
+ * Unexpected, but according to the types 'host' might be undefined.
12
+ * In that case, the request is denied to be on the safe side,
13
+ * since the host cannot be verified.
14
+ */
15
+ if (!host) {
16
+ throw new Error('Request cannot be verified due to missing host');
17
+ }
18
+ /*
19
+ * At this point, host is only verified if it's already an IP address.
20
+ * Otherwise it will be verified on 'lookup' event.
21
+ */
22
+ if (isIP(host) !== 0 && isDeniedIp(host))
23
+ throw deniedError(host);
24
+ const socket = createConnection.call(this, options, oncreate);
25
+ // Emitted after resolving the host name but before connecting.
26
+ socket.on('lookup', (error, address) => {
27
+ if (error || !isDeniedIp(address))
28
+ return;
29
+ return socket.destroy(deniedError(host));
30
+ });
31
+ return socket;
32
+ };
33
+ return agent;
34
+ };
@@ -1,14 +1,15 @@
1
- import { requestInterceptor } from './request-interceptor.js';
2
- import { responseInterceptor } from './response-interceptor.js';
3
1
  export const _cache = {
4
2
  axiosInstance: null,
5
3
  };
6
4
  export async function getAxios() {
7
5
  if (!_cache.axiosInstance) {
8
6
  const axios = (await import('axios')).default;
9
- _cache.axiosInstance = axios.create();
10
- _cache.axiosInstance.interceptors.request.use(requestInterceptor);
11
- _cache.axiosInstance.interceptors.response.use(responseInterceptor);
7
+ const { Agent: AgentHttp } = await import('node:http');
8
+ const { Agent: AgentHttps } = await import('node:https');
9
+ const { agentWithIpValidation } = await import('./agent-with-ip-validation.js');
10
+ const httpAgent = agentWithIpValidation(new AgentHttp());
11
+ const httpsAgent = agentWithIpValidation(new AgentHttps());
12
+ _cache.axiosInstance = axios.create({ httpAgent, httpsAgent });
12
13
  }
13
14
  return _cache.axiosInstance;
14
15
  }
@@ -0,0 +1 @@
1
+ export declare function isDeniedIp(ip: string): boolean;
@@ -2,34 +2,32 @@ import { useEnv } from '@directus/env';
2
2
  import os from 'node:os';
3
3
  import { useLogger } from '../logger.js';
4
4
  import { ipInNetworks } from '../utils/ip-in-networks.js';
5
- const deniedError = (url) => new Error(`Requested URL "${url}" resolves to a denied IP address`);
6
- export function validateIp(ip, url) {
5
+ export function isDeniedIp(ip) {
7
6
  const env = useEnv();
8
7
  const logger = useLogger();
9
8
  const ipDenyList = env['IMPORT_IP_DENY_LIST'];
10
9
  if (ipDenyList.length === 0)
11
- return;
12
- let denied;
10
+ return false;
13
11
  try {
14
- denied = ipInNetworks(ip, ipDenyList);
12
+ const denied = ipInNetworks(ip, ipDenyList);
13
+ if (denied)
14
+ return true;
15
15
  }
16
16
  catch (error) {
17
- logger.warn(`Invalid "IMPORT_IP_DENY_LIST" configuration`);
17
+ logger.warn(`Cannot verify IP address due to invalid "IMPORT_IP_DENY_LIST" config`);
18
18
  logger.warn(error);
19
- throw deniedError(url);
19
+ return true;
20
20
  }
21
- if (denied)
22
- throw deniedError(url);
23
21
  if (ipDenyList.includes('0.0.0.0')) {
24
22
  const networkInterfaces = os.networkInterfaces();
25
23
  for (const networkInfo of Object.values(networkInterfaces)) {
26
24
  if (!networkInfo)
27
25
  continue;
28
26
  for (const info of networkInfo) {
29
- if (info.address === ip) {
30
- throw deniedError(url);
31
- }
27
+ if (info.address === ip)
28
+ return true;
32
29
  }
33
30
  }
34
31
  }
32
+ return false;
35
33
  }
@@ -3,12 +3,13 @@ import type { Accountability, RawField, SchemaOverview } from '@directus/types';
3
3
  import type Keyv from 'keyv';
4
4
  import type { Knex } from 'knex';
5
5
  import type { Helpers } from '../database/helpers/index.js';
6
- import type { AbstractServiceOptions, Collection, CollectionMeta, MutationOptions } from '../types/index.js';
6
+ import type { AbstractServiceOptions, Collection, MutationOptions } from '../types/index.js';
7
+ import { type BaseCollectionMeta } from '@directus/system-data';
7
8
  export type RawCollection = {
8
9
  collection: string;
9
10
  fields?: RawField[];
10
11
  schema?: Partial<Table> | null;
11
- meta?: Partial<CollectionMeta> | null;
12
+ meta?: Partial<BaseCollectionMeta> | null;
12
13
  };
13
14
  export declare class CollectionsService {
14
15
  knex: Knex;
@@ -7,12 +7,12 @@ import { clearSystemCache, getCache } from '../cache.js';
7
7
  import { ALIAS_TYPES } from '../constants.js';
8
8
  import { getHelpers } from '../database/helpers/index.js';
9
9
  import getDatabase, { getSchemaInspector } from '../database/index.js';
10
- import { systemCollectionRows } from '../database/system-data/collections/index.js';
11
10
  import emitter from '../emitter.js';
12
11
  import { getSchema } from '../utils/get-schema.js';
13
12
  import { shouldClearCache } from '../utils/should-clear-cache.js';
14
13
  import { FieldsService } from './fields.js';
15
14
  import { ItemsService } from './items.js';
15
+ import { systemCollectionRows } from '@directus/system-data';
16
16
  export class CollectionsService {
17
17
  knex;
18
18
  helpers;
@@ -7,7 +7,6 @@ import { ALIAS_TYPES } from '../constants.js';
7
7
  import { translateDatabaseError } from '../database/errors/translate.js';
8
8
  import { getHelpers } from '../database/helpers/index.js';
9
9
  import getDatabase, { getSchemaInspector } from '../database/index.js';
10
- import { systemFieldRows } from '../database/system-data/fields/index.js';
11
10
  import emitter from '../emitter.js';
12
11
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
13
12
  import { ItemsService } from './items.js';
@@ -18,6 +17,8 @@ import { getSchema } from '../utils/get-schema.js';
18
17
  import { sanitizeColumn } from '../utils/sanitize-schema.js';
19
18
  import { shouldClearCache } from '../utils/should-clear-cache.js';
20
19
  import { RelationsService } from './relations.js';
20
+ import { getSystemFieldRowsWithAuthProviders } from '../utils/get-field-system-rows.js';
21
+ const systemFieldRows = getSystemFieldRowsWithAuthProviders();
21
22
  export class FieldsService {
22
23
  knex;
23
24
  helpers;
@@ -288,11 +288,12 @@ export class FilesService extends ItemsService {
288
288
  decompress: false,
289
289
  });
290
290
  }
291
- catch (err) {
292
- logger.warn(err, `Couldn't fetch file from URL "${importURL}"`);
291
+ catch (error) {
292
+ logger.warn(`Couldn't fetch file from URL "${importURL}"${error.message ? `: ${error.message}` : ''}`);
293
+ logger.trace(error);
293
294
  throw new ServiceUnavailableError({
294
295
  service: 'external-file',
295
- reason: `Couldn't fetch file from url "${importURL}"`,
296
+ reason: `Couldn't fetch file from URL "${importURL}"`,
296
297
  });
297
298
  }
298
299
  const parsedURL = url.parse(fileResponse.request.res.responseUrl);
@@ -40,6 +40,7 @@ import { GraphQLStringOrFloat } from './types/string-or-float.js';
40
40
  import { GraphQLVoid } from './types/void.js';
41
41
  import { addPathToValidationError } from './utils/add-path-to-validation-error.js';
42
42
  import processError from './utils/process-error.js';
43
+ import { isSystemCollection } from '@directus/system-data';
43
44
  const env = useEnv();
44
45
  const validationRules = Array.from(specifiedRules);
45
46
  if (env['GRAPHQL_INTROSPECTION'] === false) {
@@ -129,10 +130,10 @@ export class GraphQLService {
129
130
  const { ReadCollectionTypes, VersionCollectionTypes } = getReadableTypes();
130
131
  const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
131
132
  const scopeFilter = (collection) => {
132
- if (this.scope === 'items' && collection.collection.startsWith('directus_') === true)
133
+ if (this.scope === 'items' && isSystemCollection(collection.collection))
133
134
  return false;
134
135
  if (this.scope === 'system') {
135
- if (collection.collection.startsWith('directus_') === false)
136
+ if (isSystemCollection(collection.collection) === false)
136
137
  return false;
137
138
  if (SYSTEM_DENY_LIST.includes(collection.collection))
138
139
  return false;
@@ -20,6 +20,7 @@ import { FilesService } from './files.js';
20
20
  import { ItemsService } from './items.js';
21
21
  import { NotificationsService } from './notifications.js';
22
22
  import { UsersService } from './users.js';
23
+ import { isSystemCollection } from '@directus/system-data';
23
24
  const env = useEnv();
24
25
  const logger = useLogger();
25
26
  export class ImportService {
@@ -32,7 +33,7 @@ export class ImportService {
32
33
  this.schema = options.schema;
33
34
  }
34
35
  async import(collection, mimetype, stream) {
35
- if (this.accountability?.admin !== true && collection.startsWith('directus_'))
36
+ if (this.accountability?.admin !== true && isSystemCollection(collection))
36
37
  throw new ForbiddenError();
37
38
  const createPermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'create');
38
39
  const updatePermissions = this.accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'update');
@@ -13,6 +13,7 @@ import { shouldClearCache } from '../utils/should-clear-cache.js';
13
13
  import { validateKeys } from '../utils/validate-keys.js';
14
14
  import { AuthorizationService } from './authorization.js';
15
15
  import { PayloadService } from './payload.js';
16
+ import { isSystemCollection } from '@directus/system-data';
16
17
  const env = useEnv();
17
18
  export class ItemsService {
18
19
  collection;
@@ -25,7 +26,7 @@ export class ItemsService {
25
26
  this.collection = collection;
26
27
  this.knex = options.knex || getDatabase();
27
28
  this.accountability = options.accountability || null;
28
- this.eventScope = this.collection.startsWith('directus_') ? this.collection.substring(9) : 'items';
29
+ this.eventScope = isSystemCollection(this.collection) ? this.collection.substring(9) : 'items';
29
30
  this.schema = options.schema;
30
31
  this.cache = getCache().cache;
31
32
  return this;
@@ -1,6 +1,6 @@
1
1
  import { ForbiddenError } from '@directus/errors';
2
2
  import { clearSystemCache, getCache } from '../cache.js';
3
- import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions/index.js';
3
+ import { appAccessMinimalPermissions } from '@directus/system-data';
4
4
  import { filterItems } from '../utils/filter-items.js';
5
5
  import { AuthorizationService } from './authorization.js';
6
6
  import { ItemsService } from './items.js';
@@ -4,12 +4,12 @@ import { toArray } from '@directus/utils';
4
4
  import { clearSystemCache, getCache } from '../cache.js';
5
5
  import { getHelpers } from '../database/helpers/index.js';
6
6
  import getDatabase, { getSchemaInspector } from '../database/index.js';
7
- import { systemRelationRows } from '../database/system-data/relations/index.js';
8
7
  import emitter from '../emitter.js';
9
8
  import { getDefaultIndexName } from '../utils/get-default-index-name.js';
10
9
  import { getSchema } from '../utils/get-schema.js';
11
10
  import { ItemsService } from './items.js';
12
11
  import { PermissionsService } from './permissions.js';
12
+ import { systemRelationRows } from '@directus/system-data';
13
13
  export class RelationsService {
14
14
  knex;
15
15
  permissionsService;
@@ -8,6 +8,7 @@ import getDatabase from '../database/index.js';
8
8
  import { getRelationType } from '../utils/get-relation-type.js';
9
9
  import { reduceSchema } from '../utils/reduce-schema.js';
10
10
  import { GraphQLService } from './graphql/index.js';
11
+ import { isSystemCollection } from '@directus/system-data';
11
12
  const env = useEnv();
12
13
  export class SpecificationService {
13
14
  accountability;
@@ -77,7 +78,7 @@ class OASSpecsService {
77
78
  }
78
79
  }
79
80
  for (const collection of collections) {
80
- const isSystem = collection.collection.startsWith('directus_');
81
+ const isSystem = isSystemCollection(collection.collection);
81
82
  // If the collection is one of the system collections, pull the tag from the static spec
82
83
  if (isSystem) {
83
84
  for (const tag of spec.tags) {
@@ -106,7 +107,7 @@ class OASSpecsService {
106
107
  if (!tags)
107
108
  return paths;
108
109
  for (const tag of tags) {
109
- const isSystem = 'x-collection' in tag === false || tag['x-collection'].startsWith('directus_');
110
+ const isSystem = 'x-collection' in tag === false || isSystemCollection(tag['x-collection']);
110
111
  if (isSystem) {
111
112
  for (const [path, pathItem] of Object.entries(spec.paths)) {
112
113
  for (const [method, operation] of Object.entries(pathItem)) {
@@ -268,7 +269,7 @@ class OASSpecsService {
268
269
  const tag = tags.find((tag) => tag['x-collection'] === collection.collection);
269
270
  if (!tag)
270
271
  continue;
271
- const isSystem = collection.collection.startsWith('directus_');
272
+ const isSystem = isSystemCollection(collection.collection);
272
273
  const fieldsInCollection = Object.values(collection.fields);
273
274
  if (isSystem) {
274
275
  const schemaComponent = cloneDeep(spec.components.schemas[tag.name]);
@@ -1,6 +1,6 @@
1
1
  import { flushCaches, getCache } from '../cache.js';
2
2
  import getDatabase from '../database/index.js';
3
- import { systemCollectionRows } from '../database/system-data/collections/index.js';
3
+ import { systemCollectionRows } from '@directus/system-data';
4
4
  import emitter from '../emitter.js';
5
5
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
6
6
  import { shouldClearCache } from '../utils/should-clear-cache.js';
@@ -1,6 +1,7 @@
1
1
  import {} from 'knex';
2
2
  import { getSchema } from '../../utils/get-schema.js';
3
3
  import { getItemCount } from './get-item-count.js';
4
+ import { isSystemCollection } from '@directus/system-data';
4
5
  /**
5
6
  * Sum all passed values together. Meant to be used with .reduce()
6
7
  */
@@ -10,7 +11,7 @@ export const sum = (acc, val) => (acc += val);
10
11
  */
11
12
  export const getUserItemCount = async (db) => {
12
13
  const schema = await getSchema({ database: db });
13
- const userCollections = Object.keys(schema.collections).filter((collection) => collection.startsWith('directus_') === false);
14
+ const userCollections = Object.keys(schema.collections).filter((collection) => isSystemCollection(collection) === false);
14
15
  const counts = await getItemCount(db, userCollections);
15
16
  const collections = userCollections.length;
16
17
  const items = Object.values(counts).reduce(sum, 0);
@@ -1,20 +1,9 @@
1
1
  import type { Field } from '@directus/types';
2
2
  import type { Table } from '@directus/schema';
3
- export type CollectionMeta = {
4
- collection: string;
5
- note: string | null;
6
- hidden: boolean;
7
- singleton: boolean;
8
- icon: string | null;
9
- translations: Record<string, string>;
10
- versioning: boolean;
11
- item_duplication_fields: string[] | null;
12
- accountability: 'all' | 'accountability' | null;
13
- group: string | null;
14
- };
3
+ import type { BaseCollectionMeta } from '@directus/system-data';
15
4
  export type Collection = {
16
5
  collection: string;
17
6
  fields?: Field[];
18
- meta: CollectionMeta | null;
7
+ meta: BaseCollectionMeta | null;
19
8
  schema: Table | null;
20
9
  };
@@ -0,0 +1,2 @@
1
+ import type { FieldMeta } from '@directus/types';
2
+ export declare function getSystemFieldRowsWithAuthProviders(): FieldMeta[];
@@ -0,0 +1,17 @@
1
+ import { systemFieldRows } from '@directus/system-data';
2
+ import formatTitle from '@directus/format-title';
3
+ import { getAuthProviders } from './get-auth-providers.js';
4
+ // Dynamically populate auth providers field
5
+ export function getSystemFieldRowsWithAuthProviders() {
6
+ return systemFieldRows.map((systemField) => {
7
+ if (systemField.collection === 'directus_users' && systemField.field === 'provider') {
8
+ if (!systemField.options)
9
+ systemField.options = {};
10
+ systemField.options['choices'] = getAuthProviders().map(({ name }) => ({
11
+ text: formatTitle(name),
12
+ value: name,
13
+ }));
14
+ }
15
+ return systemField;
16
+ });
17
+ }
@@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash-es';
4
4
  import hash from 'object-hash';
5
5
  import { getCache, getCacheValue, getSystemCache, setCacheValue, setSystemCache } from '../cache.js';
6
6
  import getDatabase from '../database/index.js';
7
- import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions/index.js';
7
+ import { appAccessMinimalPermissions } from '@directus/system-data';
8
8
  import { useLogger } from '../logger.js';
9
9
  import { RolesService } from '../services/roles.js';
10
10
  import { UsersService } from '../services/users.js';
@@ -5,12 +5,12 @@ import { mapValues } from 'lodash-es';
5
5
  import { getSchemaCache, setSchemaCache } from '../cache.js';
6
6
  import { ALIAS_TYPES } from '../constants.js';
7
7
  import getDatabase from '../database/index.js';
8
- import { systemCollectionRows } from '../database/system-data/collections/index.js';
9
- import { systemFieldRows } from '../database/system-data/fields/index.js';
10
8
  import { useLogger } from '../logger.js';
11
9
  import { RelationsService } from '../services/relations.js';
12
10
  import getDefaultValue from './get-default-value.js';
13
11
  import getLocalType from './get-local-type.js';
12
+ import { systemCollectionRows } from '@directus/system-data';
13
+ import { getSystemFieldRowsWithAuthProviders } from './get-field-system-rows.js';
14
14
  const logger = useLogger();
15
15
  export async function getSchema(options) {
16
16
  const env = useEnv();
@@ -49,6 +49,7 @@ async function getDatabaseSchema(database, schemaInspector) {
49
49
  collections: {},
50
50
  relations: [],
51
51
  };
52
+ const systemFieldRows = getSystemFieldRowsWithAuthProviders();
52
53
  const schemaOverview = await schemaInspector.overview();
53
54
  const collections = [
54
55
  ...(await database
@@ -1,5 +1,5 @@
1
1
  import { assign, set, uniq } from 'lodash-es';
2
- import { schemaPermissions } from '../database/system-data/app-access-permissions/index.js';
2
+ import { schemaPermissions } from '@directus/system-data';
3
3
  import { mergePermissions } from './merge-permissions.js';
4
4
  import { reduceSchema } from './reduce-schema.js';
5
5
  export function mergePermissionsForShare(currentPermissions, accountability, schema) {
@@ -1,7 +1,7 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { getEndpoint } from '@directus/utils';
3
2
  import url from 'url';
4
3
  import { Url } from './url.js';
4
+ import { getEndpoint } from '@directus/utils';
5
5
  /**
6
6
  * Whether to skip caching for the current request
7
7
  *
@@ -1,6 +1,7 @@
1
1
  import emitter from '../../emitter.js';
2
2
  import { ItemsService, MetaService } from '../../services/index.js';
3
3
  import { getSchema } from '../../utils/get-schema.js';
4
+ import { isSystemCollection } from '@directus/system-data';
4
5
  import { sanitizeQuery } from '../../utils/sanitize-query.js';
5
6
  import { WebSocketError, handleWebSocketError } from '../errors.js';
6
7
  import { WebSocketItemsMessage } from '../messages.js';
@@ -26,7 +27,7 @@ export class ItemsHandler {
26
27
  const uid = message.uid;
27
28
  const accountability = client.accountability;
28
29
  const schema = await getSchema();
29
- if (!schema.collections[message.collection] || message.collection.startsWith('directus_')) {
30
+ if (!schema.collections[message.collection] || isSystemCollection(message.collection)) {
30
31
  throw new WebSocketError('items', 'INVALID_COLLECTION', 'The provided collection does not exists or is not accessible.', uid);
31
32
  }
32
33
  const isSingleton = !!schema.collections[message.collection]?.singleton;