directus 9.5.2 → 9.7.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 (73) hide show
  1. package/dist/app.js +3 -1
  2. package/dist/auth/drivers/ldap.d.ts +0 -1
  3. package/dist/auth/drivers/ldap.js +54 -60
  4. package/dist/auth/drivers/oauth2.js +3 -0
  5. package/dist/auth/drivers/openid.js +3 -0
  6. package/dist/cache.d.ts +4 -1
  7. package/dist/cache.js +27 -5
  8. package/dist/cli/commands/schema/apply.d.ts +1 -0
  9. package/dist/cli/commands/schema/apply.js +9 -5
  10. package/dist/cli/index.js +1 -0
  11. package/dist/cli/utils/create-env/env-stub.liquid +1 -0
  12. package/dist/controllers/assets.js +9 -1
  13. package/dist/controllers/utils.js +18 -1
  14. package/dist/database/index.js +0 -3
  15. package/dist/database/migrations/20220303A-remove-default-project-color.d.ts +3 -0
  16. package/dist/database/migrations/20220303A-remove-default-project-color.js +22 -0
  17. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  18. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  19. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  20. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +73 -0
  21. package/dist/database/migrations/run.js +1 -1
  22. package/dist/database/run-ast.d.ts +1 -1
  23. package/dist/database/run-ast.js +48 -35
  24. package/dist/database/system-data/fields/collections.yaml +4 -4
  25. package/dist/database/system-data/fields/fields.yaml +8 -8
  26. package/dist/database/system-data/fields/files.yaml +2 -2
  27. package/dist/database/system-data/fields/panels.yaml +2 -2
  28. package/dist/database/system-data/fields/permissions.yaml +4 -4
  29. package/dist/database/system-data/fields/presets.yaml +3 -3
  30. package/dist/database/system-data/fields/relations.yaml +1 -1
  31. package/dist/database/system-data/fields/revisions.yaml +2 -2
  32. package/dist/database/system-data/fields/roles.yaml +4 -4
  33. package/dist/database/system-data/fields/settings.yaml +25 -6
  34. package/dist/database/system-data/fields/users.yaml +2 -2
  35. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  36. package/dist/env.js +9 -2
  37. package/dist/exceptions/database/dialects/mysql.js +23 -17
  38. package/dist/extensions.js +0 -2
  39. package/dist/middleware/authenticate.d.ts +5 -3
  40. package/dist/middleware/authenticate.js +22 -39
  41. package/dist/middleware/respond.js +7 -28
  42. package/dist/server.js +5 -2
  43. package/dist/services/authorization.js +60 -1
  44. package/dist/services/collections.js +6 -6
  45. package/dist/services/fields.js +3 -3
  46. package/dist/services/files.js +69 -3
  47. package/dist/services/graphql.js +2 -2
  48. package/dist/services/import-export.d.ts +34 -0
  49. package/dist/services/import-export.js +270 -0
  50. package/dist/services/index.d.ts +2 -1
  51. package/dist/services/index.js +2 -1
  52. package/dist/services/items.js +1 -0
  53. package/dist/services/mail/templates/base.liquid +2 -2
  54. package/dist/services/payload.js +3 -3
  55. package/dist/services/permissions.js +10 -10
  56. package/dist/services/relations.js +7 -4
  57. package/dist/services/utils.js +10 -0
  58. package/dist/utils/apply-query.js +2 -24
  59. package/dist/utils/get-date-formatted.d.ts +1 -0
  60. package/dist/utils/get-date-formatted.js +14 -0
  61. package/dist/utils/get-local-type.js +2 -2
  62. package/dist/utils/get-permissions.js +1 -1
  63. package/dist/utils/get-schema.js +1 -1
  64. package/dist/utils/is-directus-jwt.js +4 -21
  65. package/dist/utils/jwt.d.ts +2 -0
  66. package/dist/utils/jwt.js +49 -0
  67. package/dist/utils/merge-permissions.js +18 -6
  68. package/example.env +1 -0
  69. package/package.json +17 -18
  70. package/dist/__mocks__/cache.d.ts +0 -6
  71. package/dist/__mocks__/cache.js +0 -8
  72. package/dist/services/import.d.ts +0 -13
  73. package/dist/services/import.js +0 -118
package/dist/app.js CHANGED
@@ -111,9 +111,11 @@ async function createApp() {
111
111
  // friendly. Ref #10806
112
112
  upgradeInsecureRequests: null,
113
113
  // These are required for MapLibre
114
+ // https://cdn.directus.io is required for images/videos in the official docs
114
115
  workerSrc: ["'self'", 'blob:'],
115
116
  childSrc: ["'self'", 'blob:'],
116
- imgSrc: ["'self'", 'data:', 'blob:'],
117
+ imgSrc: ["'self'", 'data:', 'blob:', 'https://cdn.directus.io'],
118
+ mediaSrc: ["'self'", 'https://cdn.directus.io'],
117
119
  connectSrc: ["'self'", 'https://*'],
118
120
  },
119
121
  }, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
@@ -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({
@@ -18,6 +18,7 @@ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
18
18
  const url_1 = require("../../utils/url");
19
19
  const logger_1 = __importDefault(require("../../logger"));
20
20
  const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
21
+ const get_config_from_env_1 = require("../../utils/get-config-from-env");
21
22
  class OAuth2AuthDriver extends local_1.LocalAuthDriver {
22
23
  constructor(options, config) {
23
24
  super(options, config);
@@ -35,11 +36,13 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
35
36
  userinfo_endpoint: profileUrl,
36
37
  issuer: additionalConfig.provider,
37
38
  });
39
+ const clientOptionsOverrides = (0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_CLIENT_`, [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], 'underscore');
38
40
  this.client = new issuer.Client({
39
41
  client_id: clientId,
40
42
  client_secret: clientSecret,
41
43
  redirect_uris: [this.redirectUrl],
42
44
  response_types: ['code'],
45
+ ...clientOptionsOverrides,
43
46
  });
44
47
  }
45
48
  generateCodeVerifier() {
@@ -18,6 +18,7 @@ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
18
18
  const url_1 = require("../../utils/url");
19
19
  const logger_1 = __importDefault(require("../../logger"));
20
20
  const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
21
+ const get_config_from_env_1 = require("../../utils/get-config-from-env");
21
22
  class OpenIDAuthDriver extends local_1.LocalAuthDriver {
22
23
  constructor(options, config) {
23
24
  super(options, config);
@@ -26,6 +27,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
26
27
  throw new exceptions_1.InvalidConfigException('Invalid provider config', { provider: additionalConfig.provider });
27
28
  }
28
29
  const redirectUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('auth', 'login', additionalConfig.provider, 'callback');
30
+ const clientOptionsOverrides = (0, get_config_from_env_1.getConfigFromEnv)(`AUTH_${config.provider.toUpperCase()}_CLIENT_`, [`AUTH_${config.provider.toUpperCase()}_CLIENT_ID`, `AUTH_${config.provider.toUpperCase()}_CLIENT_SECRET`], 'underscore');
29
31
  this.redirectUrl = redirectUrl.toString();
30
32
  this.usersService = new services_1.UsersService({ knex: this.knex, schema: this.schema });
31
33
  this.config = additionalConfig;
@@ -43,6 +45,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
43
45
  client_secret: clientSecret,
44
46
  redirect_uris: [this.redirectUrl],
45
47
  response_types: ['code'],
48
+ ...clientOptionsOverrides,
46
49
  }));
47
50
  })
48
51
  .catch(reject);
package/dist/cache.d.ts CHANGED
@@ -2,5 +2,8 @@ import Keyv from 'keyv';
2
2
  export declare function getCache(): {
3
3
  cache: Keyv | null;
4
4
  systemCache: Keyv;
5
+ lockCache: Keyv;
5
6
  };
6
- export declare function flushCaches(): Promise<void>;
7
+ export declare function flushCaches(forced?: boolean): Promise<void>;
8
+ export declare function clearSystemCache(forced?: boolean): Promise<void>;
9
+ export declare function setSystemCache(key: string, value: any, ttl?: number): Promise<void>;
package/dist/cache.js CHANGED
@@ -3,7 +3,7 @@ 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
- exports.flushCaches = exports.getCache = void 0;
6
+ exports.setSystemCache = exports.clearSystemCache = exports.flushCaches = exports.getCache = void 0;
7
7
  const keyv_1 = __importDefault(require("keyv"));
8
8
  const ms_1 = __importDefault(require("ms"));
9
9
  const env_1 = __importDefault(require("./env"));
@@ -12,6 +12,7 @@ const get_config_from_env_1 = require("./utils/get-config-from-env");
12
12
  const validate_env_1 = require("./utils/validate-env");
13
13
  let cache = null;
14
14
  let systemCache = null;
15
+ let lockCache = null;
15
16
  function getCache() {
16
17
  if (env_1.default.CACHE_ENABLED === true && cache === null) {
17
18
  (0, validate_env_1.validateEnv)(['CACHE_NAMESPACE', 'CACHE_TTL', 'CACHE_STORE']);
@@ -22,15 +23,36 @@ function getCache() {
22
23
  systemCache = getKeyvInstance(undefined, '_system');
23
24
  systemCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
24
25
  }
25
- return { cache, systemCache };
26
+ if (lockCache === null) {
27
+ lockCache = getKeyvInstance(undefined, '_lock');
28
+ lockCache.on('error', (err) => logger_1.default.warn(err, `[cache] ${err}`));
29
+ }
30
+ return { cache, systemCache, lockCache };
26
31
  }
27
32
  exports.getCache = getCache;
28
- async function flushCaches() {
29
- const { systemCache, cache } = getCache();
30
- await (systemCache === null || systemCache === void 0 ? void 0 : systemCache.clear());
33
+ async function flushCaches(forced) {
34
+ const { cache } = getCache();
35
+ await clearSystemCache(forced);
31
36
  await (cache === null || cache === void 0 ? void 0 : cache.clear());
32
37
  }
33
38
  exports.flushCaches = flushCaches;
39
+ async function clearSystemCache(forced) {
40
+ const { systemCache, lockCache } = getCache();
41
+ // Flush system cache when forced or when system cache lock not set
42
+ if (forced || !(await lockCache.get('system-cache-lock'))) {
43
+ await lockCache.set('system-cache-lock', true, 10000);
44
+ await systemCache.clear();
45
+ await lockCache.delete('system-cache-lock');
46
+ }
47
+ }
48
+ exports.clearSystemCache = clearSystemCache;
49
+ async function setSystemCache(key, value, ttl) {
50
+ const { systemCache, lockCache } = getCache();
51
+ if (!(await lockCache.get('system-cache-lock'))) {
52
+ await systemCache.set(key, value, ttl);
53
+ }
54
+ }
55
+ exports.setSystemCache = setSystemCache;
34
56
  function getKeyvInstance(ttl, namespaceSuffix) {
35
57
  switch (env_1.default.CACHE_STORE) {
36
58
  case 'redis':
@@ -1,3 +1,4 @@
1
1
  export declare function apply(snapshotPath: string, options?: {
2
2
  yes: boolean;
3
+ dryRun: boolean;
3
4
  }): Promise<void>;
@@ -63,7 +63,9 @@ async function apply(snapshotPath, options) {
63
63
  database.destroy();
64
64
  process.exit(0);
65
65
  }
66
- if ((options === null || options === void 0 ? void 0 : options.yes) !== true) {
66
+ const dryRun = (options === null || options === void 0 ? void 0 : options.dryRun) === true;
67
+ const promptForChanges = !dryRun && (options === null || options === void 0 ? void 0 : options.yes) !== true;
68
+ if (dryRun || promptForChanges) {
67
69
  let message = '';
68
70
  if (snapshotDiff.collections.length > 0) {
69
71
  message += chalk_1.default.black.underline.bold('Collections:');
@@ -141,14 +143,16 @@ async function apply(snapshotPath, options) {
141
143
  }
142
144
  }
143
145
  }
146
+ message += 'The following changes will be applied:\n\n' + chalk_1.default.black(message);
147
+ if (dryRun) {
148
+ logger_1.default.info(message);
149
+ process.exit(0);
150
+ }
144
151
  const { proceed } = await inquirer_1.default.prompt([
145
152
  {
146
153
  type: 'confirm',
147
154
  name: 'proceed',
148
- message: 'The following changes will be applied:\n\n' +
149
- chalk_1.default.black(message) +
150
- '\n\n' +
151
- 'Would you like to continue?',
155
+ message: message + '\n\n' + 'Would you like to continue?',
152
156
  },
153
157
  ]);
154
158
  if (proceed === false) {
package/dist/cli/index.js CHANGED
@@ -81,6 +81,7 @@ async function createCli() {
81
81
  .command('apply')
82
82
  .description('Apply a snapshot file to the current database')
83
83
  .option('-y, --yes', `Assume "yes" as answer to all prompts and run non-interactively`)
84
+ .option('-d, --dry-run', 'Plan and log changes to be applied', false)
84
85
  .argument('<path>', 'Path to snapshot file')
85
86
  .action(apply_1.apply);
86
87
  await emitter_1.default.emitInit('cli.after', { program });
@@ -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
 
@@ -14,6 +14,9 @@ const use_collection_1 = __importDefault(require("../middleware/use-collection")
14
14
  const services_1 = require("../services");
15
15
  const assets_1 = require("../types/assets");
16
16
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
17
+ const helmet_1 = __importDefault(require("helmet"));
18
+ const lodash_2 = require("lodash");
19
+ const get_config_from_env_1 = require("../utils/get-config-from-env");
17
20
  const router = (0, express_1.Router)();
18
21
  router.use((0, use_collection_1.default)('directus_files'));
19
22
  router.get('/:pk',
@@ -88,7 +91,12 @@ router.get('/:pk',
88
91
  return next();
89
92
  throw new exceptions_1.InvalidQueryException(`Dynamic asset generation has been disabled for this project.`);
90
93
  }
91
- }),
94
+ }), helmet_1.default.contentSecurityPolicy((0, lodash_2.merge)({
95
+ useDefaults: false,
96
+ directives: {
97
+ defaultSrc: ['none'],
98
+ },
99
+ }, (0, get_config_from_env_1.getConfigFromEnv)('ASSETS_CONTENT_SECURITY_POLICY'))),
92
100
  // Return file
93
101
  (0, async_handler_1.default)(async (req, res) => {
94
102
  var _a, _b;
@@ -95,12 +95,29 @@ router.post('/import/:collection', collection_exists_1.default, (0, async_handle
95
95
  busboy.on('error', (err) => next(err));
96
96
  req.pipe(busboy);
97
97
  }));
98
+ router.post('/export/:collection', collection_exists_1.default, (0, async_handler_1.default)(async (req, res, next) => {
99
+ if (!req.body.query) {
100
+ throw new exceptions_1.InvalidPayloadException(`"query" is required.`);
101
+ }
102
+ if (!req.body.format) {
103
+ throw new exceptions_1.InvalidPayloadException(`"format" is required.`);
104
+ }
105
+ const service = new services_1.ExportService({
106
+ accountability: req.accountability,
107
+ schema: req.schema,
108
+ });
109
+ // We're not awaiting this, as it's supposed to run async in the background
110
+ service.exportToFile(req.params.collection, req.body.query, req.body.format, {
111
+ file: req.body.file,
112
+ });
113
+ return next();
114
+ }), respond_1.respond);
98
115
  router.post('/cache/clear', (0, async_handler_1.default)(async (req, res) => {
99
116
  var _a;
100
117
  if (((_a = req.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true) {
101
118
  throw new exceptions_1.ForbiddenException();
102
119
  }
103
- await (0, cache_1.flushCaches)();
120
+ await (0, cache_1.flushCaches)(true);
104
121
  res.status(200).end();
105
122
  }));
106
123
  exports.default = router;
@@ -235,9 +235,6 @@ exports.validateDatabaseExtensions = validateDatabaseExtensions;
235
235
  async function validateDatabaseCharset(database) {
236
236
  database = database !== null && database !== void 0 ? database : getDatabase();
237
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
238
  const { collation } = await database.select(database.raw(`@@collation_database as collation`)).first();
242
239
  const tables = await database('information_schema.tables')
243
240
  .select({ name: 'TABLE_NAME', collation: 'TABLE_COLLATION' })
@@ -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;
@@ -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,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ async function up(knex) {
5
+ await knex.schema.alterTable('directus_settings', (table) => {
6
+ table.json('translation_strings');
7
+ });
8
+ }
9
+ exports.up = up;
10
+ async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('translation_strings');
13
+ });
14
+ }
15
+ exports.down = down;
@@ -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,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ const utils_1 = require("@directus/shared/utils");
5
+ const lodash_1 = require("lodash");
6
+ async function up(knex) {
7
+ const fields = await knex
8
+ .select('id', 'special')
9
+ .from('directus_fields')
10
+ .whereNotNull('special')
11
+ .orWhere('special', '<>', '');
12
+ for (const { id, special } of fields) {
13
+ let parsedSpecial;
14
+ try {
15
+ parsedSpecial = (0, utils_1.toArray)(special);
16
+ }
17
+ catch {
18
+ continue;
19
+ }
20
+ if (parsedSpecial && (0, lodash_1.isArray)(parsedSpecial)) {
21
+ let updateRequired = false;
22
+ parsedSpecial = parsedSpecial.map((special) => {
23
+ switch (special) {
24
+ case 'boolean':
25
+ case 'csv':
26
+ case 'json':
27
+ updateRequired = true;
28
+ return 'cast-' + special;
29
+ default:
30
+ return special;
31
+ }
32
+ });
33
+ if (updateRequired) {
34
+ await knex('directus_fields').update({ special: parsedSpecial }).where({ id });
35
+ }
36
+ }
37
+ }
38
+ }
39
+ exports.up = up;
40
+ async function down(knex) {
41
+ const fields = await knex
42
+ .select('id', 'special')
43
+ .from('directus_fields')
44
+ .whereNotNull('special')
45
+ .orWhere('special', '<>', '');
46
+ for (const { id, special } of fields) {
47
+ let parsedSpecial;
48
+ try {
49
+ parsedSpecial = (0, utils_1.toArray)(special);
50
+ }
51
+ catch {
52
+ continue;
53
+ }
54
+ if (parsedSpecial && (0, lodash_1.isArray)(parsedSpecial)) {
55
+ let updateRequired = false;
56
+ parsedSpecial = parsedSpecial.map((special) => {
57
+ switch (special) {
58
+ case 'cast-boolean':
59
+ case 'cast-csv':
60
+ case 'cast-json':
61
+ updateRequired = true;
62
+ return special.replace('cast-', '');
63
+ default:
64
+ return special;
65
+ }
66
+ });
67
+ if (updateRequired) {
68
+ await knex('directus_fields').update({ special: parsedSpecial }).where({ id });
69
+ }
70
+ }
71
+ }
72
+ }
73
+ exports.down = down;
@@ -63,7 +63,7 @@ async function run(database, direction, log = true) {
63
63
  await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
64
64
  }
65
65
  async function down() {
66
- const lastAppliedMigration = (0, lodash_1.orderBy)(completedMigrations, ['timestamp'], ['desc'])[0];
66
+ const lastAppliedMigration = (0, lodash_1.orderBy)(completedMigrations, ['timestamp', 'version'], ['desc', 'desc'])[0];
67
67
  if (!lastAppliedMigration) {
68
68
  throw Error('Nothing to downgrade');
69
69
  }
@@ -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
  /**