directus 9.5.0 → 9.6.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 (38) hide show
  1. package/dist/auth/drivers/ldap.d.ts +0 -1
  2. package/dist/auth/drivers/ldap.js +54 -60
  3. package/dist/auth/drivers/openid.js +6 -6
  4. package/dist/cli/commands/init/index.js +8 -0
  5. package/dist/cli/utils/create-env/env-stub.liquid +1 -0
  6. package/dist/controllers/files.js +4 -1
  7. package/dist/database/index.js +33 -4
  8. package/dist/database/migrations/20220303A-remove-default-project-color.d.ts +3 -0
  9. package/dist/database/migrations/20220303A-remove-default-project-color.js +22 -0
  10. package/dist/database/run-ast.d.ts +1 -1
  11. package/dist/database/run-ast.js +42 -35
  12. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +8 -2
  13. package/dist/database/system-data/fields/collections.yaml +2 -0
  14. package/dist/database/system-data/fields/settings.yaml +19 -3
  15. package/dist/env.js +3 -0
  16. package/dist/extensions.js +0 -2
  17. package/dist/middleware/authenticate.d.ts +5 -3
  18. package/dist/middleware/authenticate.js +22 -39
  19. package/dist/middleware/extract-token.js +1 -1
  20. package/dist/server.js +5 -2
  21. package/dist/services/authorization.js +2 -1
  22. package/dist/services/collections.js +25 -27
  23. package/dist/services/fields.js +16 -16
  24. package/dist/services/mail/templates/base.liquid +2 -2
  25. package/dist/services/payload.js +7 -3
  26. package/dist/services/relations.js +4 -0
  27. package/dist/services/utils.js +10 -0
  28. package/dist/utils/apply-query.js +2 -24
  29. package/dist/utils/get-permissions.js +1 -1
  30. package/dist/utils/is-directus-jwt.js +4 -21
  31. package/dist/utils/jwt.d.ts +2 -0
  32. package/dist/utils/jwt.js +49 -0
  33. package/dist/utils/md.js +1 -1
  34. package/dist/utils/merge-permissions.js +18 -6
  35. package/example.env +1 -0
  36. package/package.json +24 -22
  37. package/dist/__mocks__/cache.d.ts +0 -6
  38. package/dist/__mocks__/cache.js +0 -8
@@ -9,7 +9,6 @@ export declare class LDAPAuthDriver extends AuthDriver {
9
9
  config: Record<string, any>;
10
10
  constructor(options: AuthDriverOptions, config: Record<string, any>);
11
11
  private validateBindClient;
12
- private fetchUserDn;
13
12
  private fetchUserInfo;
14
13
  private fetchUserGroups;
15
14
  private fetchUserId;
@@ -44,7 +44,11 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
44
44
  var _a;
45
45
  super(options, config);
46
46
  const { bindDn, bindPassword, userDn, provider, clientUrl } = config;
47
- if (!bindDn || !bindPassword || !userDn || !provider || (!clientUrl && !((_a = config.client) === null || _a === void 0 ? void 0 : _a.socketPath))) {
47
+ if (bindDn === undefined ||
48
+ bindPassword === undefined ||
49
+ !userDn ||
50
+ !provider ||
51
+ (!clientUrl && !((_a = config.client) === null || _a === void 0 ? void 0 : _a.socketPath))) {
48
52
  throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider });
49
53
  }
50
54
  const clientConfig = typeof config.client === 'object' ? config.client : {};
@@ -90,55 +94,38 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
90
94
  });
91
95
  res.on('end', (result) => {
92
96
  if ((result === null || result === void 0 ? void 0 : result.status) === 0) {
93
- // Handle edge case with IBM systems where authenticated bind user could not fetch their DN
97
+ // Handle edge case where authenticated bind user could not fetch their own DN
94
98
  reject(new exceptions_1.UnexpectedResponseException('Failed to find bind user record'));
95
99
  }
96
100
  });
97
101
  });
98
102
  });
99
103
  }
100
- async fetchUserDn(identifier) {
101
- const { userDn, userAttribute, userScope } = this.config;
104
+ async fetchUserInfo(baseDn, filter, scope) {
105
+ let { firstNameAttribute, lastNameAttribute, mailAttribute } = this.config;
106
+ firstNameAttribute !== null && firstNameAttribute !== void 0 ? firstNameAttribute : (firstNameAttribute = 'givenName');
107
+ lastNameAttribute !== null && lastNameAttribute !== void 0 ? lastNameAttribute : (lastNameAttribute = 'sn');
108
+ mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : (mailAttribute = 'mail');
102
109
  return new Promise((resolve, reject) => {
103
- // Search for the user in LDAP by attribute
104
- this.bindClient.search(userDn, {
105
- filter: new ldapjs_1.EqualityFilter({ attribute: userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn', value: identifier }),
106
- scope: userScope !== null && userScope !== void 0 ? userScope : 'one',
110
+ // Search for the user in LDAP by filter
111
+ this.bindClient.search(baseDn, {
112
+ filter,
113
+ scope,
114
+ attributes: ['uid', firstNameAttribute, lastNameAttribute, mailAttribute, 'userAccountControl'],
107
115
  }, (err, res) => {
108
116
  if (err) {
109
117
  reject(handleError(err));
110
118
  return;
111
119
  }
112
120
  res.on('searchEntry', ({ object }) => {
113
- resolve(object.dn.toLowerCase());
114
- });
115
- res.on('error', (err) => {
116
- reject(handleError(err));
117
- });
118
- res.on('end', () => {
119
- resolve(undefined);
120
- });
121
- });
122
- });
123
- }
124
- async fetchUserInfo(userDn) {
125
- const { mailAttribute } = this.config;
126
- return new Promise((resolve, reject) => {
127
- // Fetch user info in LDAP by domain component
128
- this.bindClient.search(userDn, { attributes: ['givenName', 'sn', mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail', 'userAccountControl'] }, (err, res) => {
129
- if (err) {
130
- reject(handleError(err));
131
- return;
132
- }
133
- res.on('searchEntry', ({ object }) => {
134
- const email = object[mailAttribute !== null && mailAttribute !== void 0 ? mailAttribute : 'mail'];
121
+ var _a;
135
122
  const user = {
136
- firstName: typeof object.givenName === 'object' ? object.givenName[0] : object.givenName,
137
- lastName: typeof object.sn === 'object' ? object.sn[0] : object.sn,
138
- email: typeof email === 'object' ? email[0] : email,
139
- userAccountControl: typeof object.userAccountControl === 'object'
140
- ? Number(object.userAccountControl[0])
141
- : Number(object.userAccountControl),
123
+ dn: object.dn,
124
+ uid: getEntryValue(object.uid),
125
+ firstName: getEntryValue(object[firstNameAttribute]),
126
+ lastName: getEntryValue(object[lastNameAttribute]),
127
+ email: getEntryValue(object[mailAttribute]),
128
+ userAccountControl: Number((_a = getEntryValue(object.userAccountControl)) !== null && _a !== void 0 ? _a : 0),
142
129
  };
143
130
  resolve(user);
144
131
  });
@@ -151,18 +138,14 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
151
138
  });
152
139
  });
153
140
  }
154
- async fetchUserGroups(userDn) {
155
- const { groupDn, groupAttribute, groupScope } = this.config;
156
- if (!groupDn) {
157
- return Promise.resolve([]);
158
- }
141
+ async fetchUserGroups(baseDn, filter, scope) {
159
142
  return new Promise((resolve, reject) => {
160
143
  let userGroups = [];
161
144
  // Search for the user info in LDAP by group attribute
162
- this.bindClient.search(groupDn, {
145
+ this.bindClient.search(baseDn, {
146
+ filter,
147
+ scope,
163
148
  attributes: ['cn'],
164
- filter: new ldapjs_1.EqualityFilter({ attribute: groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member', value: userDn }),
165
- scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
166
149
  }, (err, res) => {
167
150
  if (err) {
168
151
  reject(handleError(err));
@@ -199,28 +182,36 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
199
182
  throw new exceptions_1.InvalidCredentialsException();
200
183
  }
201
184
  await this.validateBindClient();
202
- const userDn = await this.fetchUserDn(payload.identifier);
203
- if (!userDn) {
185
+ const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute } = this.config;
186
+ const userInfo = await this.fetchUserInfo(userDn, new ldapjs_1.EqualityFilter({
187
+ attribute: userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn',
188
+ value: payload.identifier,
189
+ }), userScope !== null && userScope !== void 0 ? userScope : 'one');
190
+ if (!(userInfo === null || userInfo === void 0 ? void 0 : userInfo.dn)) {
204
191
  throw new exceptions_1.InvalidCredentialsException();
205
192
  }
206
- const userId = await this.fetchUserId(userDn);
207
- const userGroups = await this.fetchUserGroups(userDn);
208
193
  let userRole;
209
- if (userGroups.length) {
210
- userRole = await this.knex
211
- .select('id')
212
- .from('directus_roles')
213
- .whereRaw(`LOWER(??) IN (${userGroups.map(() => '?')})`, [
214
- 'name',
215
- ...userGroups.map((group) => group.toLowerCase()),
216
- ])
217
- .first();
194
+ if (groupDn) {
195
+ const userGroups = await this.fetchUserGroups(groupDn, new ldapjs_1.EqualityFilter({
196
+ attribute: groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member',
197
+ value: (groupAttribute === null || groupAttribute === void 0 ? void 0 : groupAttribute.toLowerCase()) === 'memberuid' && userInfo.uid ? userInfo.uid : userInfo.dn,
198
+ }), groupScope !== null && groupScope !== void 0 ? groupScope : 'one');
199
+ if (userGroups.length) {
200
+ userRole = await this.knex
201
+ .select('id')
202
+ .from('directus_roles')
203
+ .whereRaw(`LOWER(??) IN (${userGroups.map(() => '?')})`, [
204
+ 'name',
205
+ ...userGroups.map((group) => group.toLowerCase()),
206
+ ])
207
+ .first();
208
+ }
218
209
  }
210
+ const userId = await this.fetchUserId(userInfo.dn);
219
211
  if (userId) {
220
212
  await this.usersService.updateOne(userId, { role: (_a = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _a !== void 0 ? _a : null });
221
213
  return userId;
222
214
  }
223
- const userInfo = await this.fetchUserInfo(userDn);
224
215
  if (!userInfo) {
225
216
  throw new exceptions_1.InvalidCredentialsException();
226
217
  }
@@ -229,10 +220,10 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
229
220
  first_name: userInfo.firstName,
230
221
  last_name: userInfo.lastName,
231
222
  email: userInfo.email,
232
- external_identifier: userDn,
223
+ external_identifier: userInfo.dn,
233
224
  role: userRole === null || userRole === void 0 ? void 0 : userRole.id,
234
225
  });
235
- return (await this.fetchUserId(userDn));
226
+ return (await this.fetchUserId(userInfo.dn));
236
227
  }
237
228
  async verify(user, password) {
238
229
  if (!user.external_identifier || !password) {
@@ -282,6 +273,9 @@ const handleError = (e) => {
282
273
  message: e.message,
283
274
  });
284
275
  };
276
+ const getEntryValue = (value) => {
277
+ return typeof value === 'object' ? value[0] : value;
278
+ };
285
279
  function createLDAPAuthRouter(provider) {
286
280
  const router = (0, express_1.Router)();
287
281
  const loginSchema = joi_1.default.object({
@@ -91,12 +91,12 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
91
91
  try {
92
92
  const client = await this.client;
93
93
  tokenSet = await client.callback(this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: openid_client_1.generators.codeChallenge(payload.codeVerifier) });
94
- const issuer = client.issuer;
95
- if (issuer.metadata.userinfo_endpoint) {
96
- userInfo = await client.userinfo(tokenSet.access_token);
97
- }
98
- else {
99
- userInfo = tokenSet.claims();
94
+ userInfo = tokenSet.claims();
95
+ if (client.issuer.metadata.userinfo_endpoint) {
96
+ userInfo = {
97
+ ...userInfo,
98
+ ...(await client.userinfo(tokenSet.access_token)),
99
+ };
100
100
  }
101
101
  }
102
102
  catch (e) {
@@ -8,6 +8,7 @@ const execa_1 = __importDefault(require("execa"));
8
8
  const inquirer_1 = __importDefault(require("inquirer"));
9
9
  const ora_1 = __importDefault(require("ora"));
10
10
  const uuid_1 = require("uuid");
11
+ const joi_1 = __importDefault(require("joi"));
11
12
  const run_1 = __importDefault(require("../../../database/migrations/run"));
12
13
  const run_2 = __importDefault(require("../../../database/seeds/run"));
13
14
  const create_db_connection_1 = __importDefault(require("../../utils/create-db-connection"));
@@ -62,6 +63,13 @@ async function init() {
62
63
  name: 'email',
63
64
  message: 'Email',
64
65
  default: 'admin@example.com',
66
+ validate: (input) => {
67
+ const emailSchema = joi_1.default.string().email().required();
68
+ const { error } = emailSchema.validate(input);
69
+ if (error)
70
+ throw new Error('The email entered is not a valid email address!');
71
+ return true;
72
+ },
65
73
  },
66
74
  {
67
75
  type: 'password',
@@ -1,6 +1,7 @@
1
1
  ####################################################################################################
2
2
  ## General
3
3
 
4
+ HOST="0.0.0.0"
4
5
  PORT=8055
5
6
  PUBLIC_URL="/"
6
7
 
@@ -20,7 +20,7 @@ const router = express_1.default.Router();
20
20
  router.use((0, use_collection_1.default)('directus_files'));
21
21
  const multipartHandler = (0, async_handler_1.default)(async (req, res, next) => {
22
22
  if (req.is('multipart/form-data') === false)
23
- throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
23
+ return next();
24
24
  let headers;
25
25
  if (req.headers['content-type']) {
26
26
  headers = req.headers;
@@ -93,6 +93,9 @@ const multipartHandler = (0, async_handler_1.default)(async (req, res, next) =>
93
93
  }
94
94
  });
95
95
  router.post('/', multipartHandler, (0, async_handler_1.default)(async (req, res, next) => {
96
+ if (req.is('multipart/form-data') === false) {
97
+ throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
98
+ }
96
99
  const service = new services_1.FilesService({
97
100
  accountability: req.accountability,
98
101
  schema: req.schema,
@@ -103,16 +103,14 @@ function getDatabase() {
103
103
  // act the same
104
104
  (0, lodash_1.merge)(knexConfig, { connection: { options: { useUTC: false } } });
105
105
  }
106
- if (env_1.default.DB_CLIENT === 'mysql' && !env_1.default.DB_CHARSET) {
107
- logger_1.default.warn(`DB_CHARSET hasn't been set. Please make sure DB_CHARSET matches your database's collation.`);
108
- }
109
106
  database = (0, knex_1.knex)(knexConfig);
107
+ validateDatabaseCharset(database);
110
108
  const times = {};
111
109
  database
112
110
  .on('query', (queryInfo) => {
113
111
  times[queryInfo.__knexUid] = perf_hooks_1.performance.now();
114
112
  })
115
- .on('query-response', (response, queryInfo) => {
113
+ .on('query-response', (_response, queryInfo) => {
116
114
  const delta = perf_hooks_1.performance.now() - times[queryInfo.__knexUid];
117
115
  logger_1.default.trace(`[${delta.toFixed(3)}ms] ${queryInfo.sql} [${queryInfo.bindings.join(', ')}]`);
118
116
  delete times[queryInfo.__knexUid];
@@ -234,3 +232,34 @@ async function validateDatabaseExtensions() {
234
232
  }
235
233
  }
236
234
  exports.validateDatabaseExtensions = validateDatabaseExtensions;
235
+ async function validateDatabaseCharset(database) {
236
+ database = database !== null && database !== void 0 ? database : getDatabase();
237
+ if (getDatabaseClient(database) === 'mysql') {
238
+ if (env_1.default.DB_CHARSET) {
239
+ logger_1.default.warn(`Using custom DB_CHARSET "${env_1.default.DB_CHARSET}". Using a charset different from the database's default can cause problems in relationships. Omitting DB_CHARSET is strongly recommended.`);
240
+ }
241
+ const { collation } = await database.select(database.raw(`@@collation_database as collation`)).first();
242
+ const tables = await database('information_schema.tables')
243
+ .select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' })
244
+ .where({ TABLE_SCHEMA: env_1.default.DB_DATABASE });
245
+ const columns = await database('information_schema.columns')
246
+ .select({ table_name: 'TABLE_NAME', name: 'COLUMN_NAME', collation: 'COLLATION_NAME' })
247
+ .where({ TABLE_SCHEMA: env_1.default.DB_DATABASE })
248
+ .whereNot({ COLLATION_NAME: collation });
249
+ let inconsistencies = '';
250
+ for (const table of tables) {
251
+ const tableColumns = columns.filter((column) => column.table_name === table.name);
252
+ const tableHasInvalidCollation = table.collation !== collation;
253
+ if (tableHasInvalidCollation || tableColumns.length > 0) {
254
+ inconsistencies += `\t\t- Table "${table.name}": "${table.collation}"\n`;
255
+ for (const column of tableColumns) {
256
+ inconsistencies += `\t\t - Column "${column.name}": "${column.collation}"\n`;
257
+ }
258
+ }
259
+ }
260
+ if (inconsistencies) {
261
+ logger_1.default.warn(`Some tables and columns do not match your database's default collation (${collation}):\n${inconsistencies}`);
262
+ }
263
+ }
264
+ return;
265
+ }
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ const helpers_1 = require("../helpers");
5
+ async function up(knex) {
6
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
7
+ await helper.changeToString('directus_settings', 'project_color', {
8
+ nullable: true,
9
+ default: null,
10
+ length: 50,
11
+ });
12
+ }
13
+ exports.up = up;
14
+ async function down(knex) {
15
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
16
+ await helper.changeToString('directus_settings', 'project_color', {
17
+ nullable: true,
18
+ default: '#00C897',
19
+ length: 10,
20
+ });
21
+ }
22
+ exports.down = down;
@@ -1,5 +1,5 @@
1
- import { Knex } from 'knex';
2
1
  import { Item, SchemaOverview } from '@directus/shared/types';
2
+ import { Knex } from 'knex';
3
3
  import { AST, NestedCollectionNode } from '../types/ast';
4
4
  declare type RunASTOptions = {
5
5
  /**
@@ -3,15 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("@directus/shared/utils");
6
7
  const lodash_1 = require("lodash");
8
+ const _1 = __importDefault(require("."));
9
+ const helpers_1 = require("../database/helpers");
10
+ const env_1 = __importDefault(require("../env"));
7
11
  const payload_1 = require("../services/payload");
8
12
  const apply_function_to_column_name_1 = require("../utils/apply-function-to-column-name");
9
13
  const apply_query_1 = __importDefault(require("../utils/apply-query"));
10
14
  const get_column_1 = require("../utils/get-column");
11
15
  const strip_function_1 = require("../utils/strip-function");
12
- const utils_1 = require("@directus/shared/utils");
13
- const _1 = __importDefault(require("."));
14
- const helpers_1 = require("../database/helpers");
15
16
  /**
16
17
  * Execute a given AST using Knex. Returns array of items based on requested AST.
17
18
  */
@@ -44,10 +45,30 @@ async function runAST(originalAST, schema, options) {
44
45
  // Apply the `_in` filters to the nested collection batches
45
46
  const nestedNodes = applyParentFilters(schema, nestedCollectionNodes, items);
46
47
  for (const nestedNode of nestedNodes) {
47
- const nestedItems = await runAST(nestedNode, schema, { knex, nested: true });
48
- if (nestedItems) {
49
- // Merge all fetched nested records with the parent items
50
- items = mergeWithParentItems(schema, nestedItems, items, nestedNode);
48
+ let nestedItems = [];
49
+ if (nestedNode.type === 'o2m') {
50
+ let hasMore = true;
51
+ let batchCount = 0;
52
+ while (hasMore) {
53
+ const node = (0, lodash_1.merge)({}, nestedNode, {
54
+ query: { limit: env_1.default.RELATIONAL_BATCH_SIZE, offset: batchCount * env_1.default.RELATIONAL_BATCH_SIZE },
55
+ });
56
+ nestedItems = (await runAST(node, schema, { knex, nested: true }));
57
+ if (nestedItems) {
58
+ items = mergeWithParentItems(schema, nestedItems, items, nestedNode);
59
+ }
60
+ if (!nestedItems || nestedItems.length < env_1.default.RELATIONAL_BATCH_SIZE) {
61
+ hasMore = false;
62
+ }
63
+ batchCount++;
64
+ }
65
+ }
66
+ else {
67
+ nestedItems = (await runAST(nestedNode, schema, { knex, nested: true }));
68
+ if (nestedItems) {
69
+ // Merge all fetched nested records with the parent items
70
+ items = mergeWithParentItems(schema, nestedItems, items, nestedNode);
71
+ }
51
72
  }
52
73
  }
53
74
  // During the fetching of data, we have to inject a couple of required fields for the child nesting
@@ -146,13 +167,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
146
167
  if (nestedNode.type === 'm2o') {
147
168
  const foreignField = schema.collections[nestedNode.relation.related_collection].primary;
148
169
  const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.relation.field])).filter((id) => id);
149
- const limit = nestedNode.query.limit;
150
- if (limit === -1) {
151
- (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
152
- }
153
- else {
154
- nestedNode.query.union = [foreignField, foreignIds];
155
- }
170
+ (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
156
171
  }
157
172
  else if (nestedNode.type === 'o2m') {
158
173
  const relatedM2OisFetched = !!nestedNode.children.find((child) => {
@@ -174,13 +189,7 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
174
189
  }
175
190
  const foreignField = nestedNode.relation.field;
176
191
  const foreignIds = (0, lodash_1.uniq)(parentItems.map((res) => res[nestedNode.parentKey])).filter((id) => id);
177
- const limit = nestedNode.query.limit;
178
- if (limit === -1) {
179
- (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
180
- }
181
- else {
182
- nestedNode.query.union = [foreignField, foreignIds];
183
- }
192
+ (0, lodash_1.merge)(nestedNode, { query: { filter: { [foreignField]: { _in: foreignIds } } } });
184
193
  }
185
194
  else if (nestedNode.type === 'a2o') {
186
195
  const keysPerCollection = {};
@@ -193,20 +202,16 @@ function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
193
202
  for (const relatedCollection of nestedNode.names) {
194
203
  const foreignField = nestedNode.relatedKey[relatedCollection];
195
204
  const foreignIds = (0, lodash_1.uniq)(keysPerCollection[relatedCollection]);
196
- const limit = nestedNode.query[relatedCollection].limit;
197
- if (limit === -1) {
198
- (0, lodash_1.merge)(nestedNode, { query: { [relatedCollection]: { filter: { [foreignField]: { _in: foreignIds } } } } });
199
- }
200
- else {
201
- nestedNode.query[relatedCollection].union = [foreignField, foreignIds];
202
- }
205
+ (0, lodash_1.merge)(nestedNode, {
206
+ query: { [relatedCollection]: { filter: { [foreignField]: { _in: foreignIds } } } },
207
+ });
203
208
  }
204
209
  }
205
210
  }
206
211
  return nestedCollectionNodes;
207
212
  }
208
213
  function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
209
- var _a;
214
+ var _a, _b;
210
215
  const nestedItems = (0, utils_1.toArray)(nestedItem);
211
216
  const parentItems = (0, lodash_1.clone)((0, utils_1.toArray)(parentItem));
212
217
  if (nestedNode.type === 'm2o') {
@@ -220,8 +225,9 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
220
225
  }
221
226
  else if (nestedNode.type === 'o2m') {
222
227
  for (const parentItem of parentItems) {
223
- const itemChildren = nestedItems
224
- .filter((nestedItem) => {
228
+ if (!parentItem[nestedNode.fieldKey])
229
+ parentItem[nestedNode.fieldKey] = [];
230
+ const itemChildren = nestedItems.filter((nestedItem) => {
225
231
  var _a;
226
232
  if (nestedItem === null)
227
233
  return false;
@@ -230,8 +236,10 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
230
236
  return (nestedItem[nestedNode.relation.field] ==
231
237
  parentItem[schema.collections[nestedNode.relation.related_collection].primary] ||
232
238
  ((_a = nestedItem[nestedNode.relation.field]) === null || _a === void 0 ? void 0 : _a[schema.collections[nestedNode.relation.related_collection].primary]) == parentItem[schema.collections[nestedNode.relation.related_collection].primary]);
233
- })
234
- .sort((a, b) => {
239
+ });
240
+ parentItem[nestedNode.fieldKey].push(...itemChildren);
241
+ parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].slice(0, (_a = nestedNode.query.limit) !== null && _a !== void 0 ? _a : 100);
242
+ parentItem[nestedNode.fieldKey] = parentItem[nestedNode.fieldKey].sort((a, b) => {
235
243
  // This is pre-filled in get-ast-from-query
236
244
  const sortField = nestedNode.query.sort[0];
237
245
  let column = sortField;
@@ -253,12 +261,11 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
253
261
  return a[column] < b[column] ? 1 : -1;
254
262
  }
255
263
  });
256
- parentItem[nestedNode.fieldKey] = itemChildren.length > 0 ? itemChildren : [];
257
264
  }
258
265
  }
259
266
  else if (nestedNode.type === 'a2o') {
260
267
  for (const parentItem of parentItems) {
261
- if (!((_a = nestedNode.relation.meta) === null || _a === void 0 ? void 0 : _a.one_collection_field)) {
268
+ if (!((_b = nestedNode.relation.meta) === null || _b === void 0 ? void 0 : _b.one_collection_field)) {
262
269
  parentItem[nestedNode.fieldKey] = null;
263
270
  continue;
264
271
  }
@@ -62,14 +62,20 @@
62
62
  permissions:
63
63
  recipient:
64
64
  _eq: $CURRENT_USER
65
- fields: '*'
66
65
 
67
66
  - collection: directus_notifications
68
67
  action: update
69
68
  permissions:
70
69
  recipient:
71
70
  _eq: $CURRENT_USER
72
- fields: 'status'
71
+ fields:
72
+ - status
73
+
74
+ - collection: directus_shares
75
+ action: read
76
+ permissions:
77
+ user_created:
78
+ _eq: $CURRENT_USER
73
79
 
74
80
  - collection: directus_users
75
81
  action: read
@@ -156,6 +156,8 @@ fields:
156
156
  - decimal
157
157
  - integer
158
158
  allowNone: true
159
+ allowPrimaryKey: false
160
+ allowForeignKeys: false
159
161
  width: half
160
162
 
161
163
  - field: accountability_divider
@@ -87,9 +87,25 @@ fields:
87
87
  language: css
88
88
  lineNumber: true
89
89
  template: |
90
- #app, #main-content {
91
- --border-radius-outline: 0px !important;
92
- --border-radius: 0px !important;
90
+ #app, #main-content, body {
91
+ --primary-alt: #F0ECFF !important;
92
+ --primary-10: #F0ECFF !important;
93
+ --primary-25: #D9D0FF !important;
94
+ --primary-50: #B3A1FF !important;
95
+ --primary-75: #8C73FF !important;
96
+ --primary-90: #7557FF !important;
97
+
98
+ --primary: #6644FF !important;
99
+
100
+ --primary-110: #5E41EC !important;
101
+ --primary-125: #523DCF !important;
102
+ --primary-150: #3E369F !important;
103
+ --primary-175: #2B3070 !important;
104
+ --primary-190: #1F2C53 !important;
105
+
106
+ --v-button-background-color: #6644FF !important;
107
+ --v-button-background-color-hover: #5E41EC !important;
108
+ --sidebar-detail-color-active: #5E41EC !important;
93
109
  }
94
110
  width: full
95
111
 
package/dist/env.js CHANGED
@@ -17,6 +17,7 @@ const utils_1 = require("@directus/shared/utils");
17
17
  const acceptedEnvTypes = ['string', 'number', 'regex', 'array'];
18
18
  const defaults = {
19
19
  CONFIG_PATH: path_1.default.resolve(process.cwd(), '.env'),
20
+ HOST: '0.0.0.0',
20
21
  PORT: 8055,
21
22
  PUBLIC_URL: '/',
22
23
  MAX_PAYLOAD_SIZE: '100kb',
@@ -65,10 +66,12 @@ const defaults = {
65
66
  IP_TRUST_PROXY: true,
66
67
  IP_CUSTOM_HEADER: false,
67
68
  SERVE_APP: true,
69
+ RELATIONAL_BATCH_SIZE: 25000,
68
70
  };
69
71
  // Allows us to force certain environment variable into a type, instead of relying
70
72
  // on the auto-parsed type in processValues. ref #3705
71
73
  const typeMap = {
74
+ HOST: 'string',
72
75
  PORT: 'string',
73
76
  DB_NAME: 'string',
74
77
  DB_USER: 'string',
@@ -38,8 +38,6 @@ const get_schema_1 = require("./utils/get-schema");
38
38
  const services = __importStar(require("./services"));
39
39
  const node_cron_1 = require("node-cron");
40
40
  const rollup_1 = require("rollup");
41
- // @TODO Remove this once a new version of @rollup/plugin-virtual has been released
42
- // @ts-expect-error
43
41
  const plugin_virtual_1 = __importDefault(require("@rollup/plugin-virtual"));
44
42
  const plugin_alias_1 = __importDefault(require("@rollup/plugin-alias"));
45
43
  const url_1 = require("./utils/url");
@@ -1,6 +1,8 @@
1
- import { RequestHandler } from 'express';
1
+ /// <reference types="qs" />
2
+ import { NextFunction, Request, Response } from 'express';
2
3
  /**
3
4
  * Verify the passed JWT and assign the user ID and role to `req`
4
5
  */
5
- declare const authenticate: RequestHandler;
6
- export default authenticate;
6
+ export declare const handler: (req: Request, res: Response, next: NextFunction) => Promise<void>;
7
+ declare const _default: import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
8
+ export default _default;