directus 9.4.2 → 9.4.3

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.
package/dist/app.js CHANGED
@@ -106,6 +106,15 @@ async function createApp() {
106
106
  directives: {
107
107
  // Unsafe-eval is required for vue3 / vue-i18n / app extensions
108
108
  scriptSrc: ["'self'", "'unsafe-eval'"],
109
+ // Even though this is recommended to have enabled, it breaks most local
110
+ // installations. Making this opt-in rather than opt-out is a little more
111
+ // friendly. Ref #10806
112
+ upgradeInsecureRequests: null,
113
+ // These are required for MapLibre
114
+ workerSrc: ["'self'", 'blob:'],
115
+ childSrc: ["'self'", 'blob:'],
116
+ imgSrc: ["'self'", 'data:', 'blob:'],
117
+ connectSrc: ["'self'", 'https://*'],
109
118
  },
110
119
  }, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
111
120
  await emitter_1.default.emitInit('app.before', { app });
package/dist/cli/index.js CHANGED
@@ -22,7 +22,7 @@ const pkg = require('../../package.json');
22
22
  async function createCli() {
23
23
  const program = new commander_1.Command();
24
24
  const extensionManager = (0, extensions_1.getExtensionManager)();
25
- await extensionManager.initialize({ schedule: false });
25
+ await extensionManager.initialize({ schedule: false, watch: false });
26
26
  await emitter_1.default.emitInit('cli.before', { program });
27
27
  program.name('directus').usage('[command] [options]');
28
28
  program.version(pkg.version, '-v, --version');
@@ -16,7 +16,7 @@ router.get('/:type', (0, async_handler_1.default)(async (req, res, next) => {
16
16
  throw new exceptions_1.RouteNotFoundException(req.path);
17
17
  }
18
18
  const extensionManager = (0, extensions_1.getExtensionManager)();
19
- const extensions = extensionManager.listExtensions(type);
19
+ const extensions = extensionManager.getExtensionsList(type);
20
20
  res.locals.payload = {
21
21
  data: extensions,
22
22
  };
@@ -13,7 +13,7 @@ async function run(database, direction, log = true) {
13
13
  let migrationFiles = await fs_extra_1.default.readdir(__dirname);
14
14
  const customMigrationsPath = path_1.default.resolve(env_1.default.EXTENSIONS_PATH, 'migrations');
15
15
  let customMigrationFiles = ((await fs_extra_1.default.pathExists(customMigrationsPath)) && (await fs_extra_1.default.readdir(customMigrationsPath))) || [];
16
- migrationFiles = migrationFiles.filter((file) => file.startsWith('run') === false && file.endsWith('.d.ts') === false);
16
+ migrationFiles = migrationFiles.filter((file) => /^[0-9]+[A-Z]-[^.]+\.(?:js|ts)$/.test(file));
17
17
  customMigrationFiles = customMigrationFiles.filter((file) => file.endsWith('.js'));
18
18
  const completedMigrations = await database.select('*').from('directus_migrations').orderBy('version');
19
19
  const migrations = [
package/dist/env.js CHANGED
@@ -52,6 +52,7 @@ const defaults = {
52
52
  AUTH_PROVIDERS: '',
53
53
  AUTH_DISABLE_DEFAULT: false,
54
54
  EXTENSIONS_PATH: './extensions',
55
+ EXTENSIONS_AUTO_RELOAD: false,
55
56
  EMAIL_FROM: 'no-reply@directus.io',
56
57
  EMAIL_TRANSPORT: 'sendmail',
57
58
  EMAIL_SENDMAIL_NEW_LINE: 'unix',
@@ -2,22 +2,27 @@ import { Router } from 'express';
2
2
  import { AppExtensionType, ExtensionType } from '@directus/shared/types';
3
3
  export declare function getExtensionManager(): ExtensionManager;
4
4
  declare class ExtensionManager {
5
- private isInitialized;
5
+ private isLoaded;
6
+ private isScheduleHookEnabled;
6
7
  private extensions;
7
8
  private appExtensions;
8
- private apiHooks;
9
- private apiEndpoints;
9
+ private apiExtensions;
10
10
  private apiEmitter;
11
11
  private endpointRouter;
12
- private isScheduleHookEnabled;
12
+ private watcher;
13
13
  constructor();
14
- initialize({ schedule }?: {
14
+ initialize({ schedule, watch }?: {
15
15
  schedule: boolean;
16
+ watch: boolean;
16
17
  }): Promise<void>;
17
18
  reload(): Promise<void>;
18
- listExtensions(type?: ExtensionType): string[];
19
+ getExtensionsList(type?: ExtensionType): string[];
19
20
  getAppExtensions(type: AppExtensionType): string | undefined;
20
21
  getEndpointRouter(): Router;
22
+ private load;
23
+ private unload;
24
+ private initializeWatcher;
25
+ private updateWatchedExtensions;
21
26
  private getExtensions;
22
27
  private generateExtensionBundles;
23
28
  private getSharedDepsMapping;
@@ -45,6 +45,8 @@ const plugin_alias_1 = __importDefault(require("@rollup/plugin-alias"));
45
45
  const url_1 = require("./utils/url");
46
46
  const get_module_default_1 = __importDefault(require("./utils/get-module-default"));
47
47
  const lodash_1 = require("lodash");
48
+ const chokidar_1 = __importDefault(require("chokidar"));
49
+ const utils_1 = require("@directus/shared/utils");
48
50
  let extensionManager;
49
51
  function getExtensionManager() {
50
52
  if (extensionManager) {
@@ -56,19 +58,66 @@ function getExtensionManager() {
56
58
  exports.getExtensionManager = getExtensionManager;
57
59
  class ExtensionManager {
58
60
  constructor() {
59
- this.isInitialized = false;
61
+ this.isLoaded = false;
62
+ this.isScheduleHookEnabled = true;
60
63
  this.extensions = [];
61
64
  this.appExtensions = {};
62
- this.apiHooks = [];
63
- this.apiEndpoints = [];
64
- this.isScheduleHookEnabled = true;
65
+ this.apiExtensions = { hooks: [], endpoints: [] };
66
+ this.watcher = null;
65
67
  this.apiEmitter = new emitter_1.Emitter();
66
68
  this.endpointRouter = (0, express_1.Router)();
67
69
  }
68
- async initialize({ schedule } = { schedule: true }) {
70
+ async initialize({ schedule, watch } = { schedule: true, watch: true }) {
69
71
  this.isScheduleHookEnabled = schedule;
70
- if (this.isInitialized)
71
- return;
72
+ if (watch) {
73
+ this.initializeWatcher();
74
+ }
75
+ if (!this.isLoaded) {
76
+ await this.load();
77
+ this.updateWatchedExtensions(this.extensions);
78
+ const loadedExtensions = this.getExtensionsList();
79
+ if (loadedExtensions.length > 0) {
80
+ logger_1.default.info(`Loaded extensions: ${loadedExtensions.join(', ')}`);
81
+ }
82
+ }
83
+ }
84
+ async reload() {
85
+ if (this.isLoaded) {
86
+ logger_1.default.info('Reloading extensions');
87
+ const prevExtensions = (0, lodash_1.clone)(this.extensions);
88
+ await this.unload();
89
+ await this.load();
90
+ const added = this.extensions.filter((extension) => !prevExtensions.some((prevExtension) => extension.path === prevExtension.path));
91
+ const removed = prevExtensions.filter((prevExtension) => !this.extensions.some((extension) => prevExtension.path === extension.path));
92
+ this.updateWatchedExtensions(added, removed);
93
+ const addedExtensions = added.map((extension) => extension.name);
94
+ const removedExtensions = removed.map((extension) => extension.name);
95
+ if (addedExtensions.length > 0) {
96
+ logger_1.default.info(`Added extensions: ${addedExtensions.join(', ')}`);
97
+ }
98
+ if (removedExtensions.length > 0) {
99
+ logger_1.default.info(`Removed extensions: ${removedExtensions.join(', ')}`);
100
+ }
101
+ }
102
+ else {
103
+ logger_1.default.warn('Extensions have to be loaded before they can be reloaded');
104
+ }
105
+ }
106
+ getExtensionsList(type) {
107
+ if (type === undefined) {
108
+ return this.extensions.map((extension) => extension.name);
109
+ }
110
+ else {
111
+ return this.extensions.filter((extension) => extension.type === type).map((extension) => extension.name);
112
+ }
113
+ }
114
+ getAppExtensions(type) {
115
+ return this.appExtensions[type];
116
+ }
117
+ getEndpointRouter() {
118
+ return this.endpointRouter;
119
+ }
120
+ async load() {
72
121
  try {
73
122
  await (0, node_1.ensureExtensionDirs)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES);
74
123
  this.extensions = await this.getExtensions();
@@ -82,38 +131,42 @@ class ExtensionManager {
82
131
  if (env_1.default.SERVE_APP) {
83
132
  this.appExtensions = await this.generateExtensionBundles();
84
133
  }
85
- const loadedExtensions = this.listExtensions();
86
- if (loadedExtensions.length > 0) {
87
- logger_1.default.info(`Loaded extensions: ${loadedExtensions.join(', ')}`);
88
- }
89
- this.isInitialized = true;
134
+ this.isLoaded = true;
90
135
  }
91
- async reload() {
92
- if (!this.isInitialized)
93
- return;
94
- logger_1.default.info('Reloading extensions');
136
+ async unload() {
95
137
  this.unregisterHooks();
96
138
  this.unregisterEndpoints();
97
139
  this.apiEmitter.offAll();
98
140
  if (env_1.default.SERVE_APP) {
99
141
  this.appExtensions = {};
100
142
  }
101
- this.isInitialized = false;
102
- await this.initialize();
143
+ this.isLoaded = false;
103
144
  }
104
- listExtensions(type) {
105
- if (type === undefined) {
106
- return this.extensions.map((extension) => extension.name);
107
- }
108
- else {
109
- return this.extensions.filter((extension) => extension.type === type).map((extension) => extension.name);
145
+ initializeWatcher() {
146
+ if (env_1.default.EXTENSIONS_AUTO_RELOAD && env_1.default.NODE_ENV !== 'development' && !this.watcher) {
147
+ logger_1.default.info('Watching extensions for changes...');
148
+ const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES).map((type) => path_1.default.resolve(env_1.default.EXTENSIONS_PATH, (0, utils_1.pluralize)(type)));
149
+ this.watcher = chokidar_1.default.watch([path_1.default.resolve('.', 'package.json'), ...localExtensionPaths], {
150
+ ignoreInitial: true,
151
+ });
152
+ this.watcher
153
+ .on('add', () => this.reload())
154
+ .on('change', () => this.reload())
155
+ .on('unlink', () => this.reload());
110
156
  }
111
157
  }
112
- getAppExtensions(type) {
113
- return this.appExtensions[type];
114
- }
115
- getEndpointRouter() {
116
- return this.endpointRouter;
158
+ updateWatchedExtensions(added, removed = []) {
159
+ if (this.watcher) {
160
+ const toPackageExtensionPaths = (extensions) => extensions
161
+ .filter((extension) => !extension.local)
162
+ .map((extension) => extension.type !== 'pack'
163
+ ? path_1.default.resolve(extension.path, extension.entrypoint || '')
164
+ : path_1.default.resolve(extension.path, 'package.json'));
165
+ const addedPackageExtensionPaths = toPackageExtensionPaths(added);
166
+ const removedPackageExtensionPaths = toPackageExtensionPaths(removed);
167
+ this.watcher.add(addedPackageExtensionPaths);
168
+ this.watcher.unwatch(removedPackageExtensionPaths);
169
+ }
117
170
  }
118
171
  async getExtensions() {
119
172
  const packageExtensions = await (0, node_1.getPackageExtensions)('.', env_1.default.SERVE_APP ? constants_1.EXTENSION_PACKAGE_TYPES : constants_1.API_EXTENSION_PACKAGE_TYPES);
@@ -188,7 +241,7 @@ class ExtensionManager {
188
241
  const registerFunctions = {
189
242
  filter: (event, handler) => {
190
243
  emitter_1.default.onFilter(event, handler);
191
- this.apiHooks.push({
244
+ this.apiExtensions.hooks.push({
192
245
  type: 'filter',
193
246
  path: hookPath,
194
247
  event,
@@ -197,7 +250,7 @@ class ExtensionManager {
197
250
  },
198
251
  action: (event, handler) => {
199
252
  emitter_1.default.onAction(event, handler);
200
- this.apiHooks.push({
253
+ this.apiExtensions.hooks.push({
201
254
  type: 'action',
202
255
  path: hookPath,
203
256
  event,
@@ -206,7 +259,7 @@ class ExtensionManager {
206
259
  },
207
260
  init: (event, handler) => {
208
261
  emitter_1.default.onInit(event, handler);
209
- this.apiHooks.push({
262
+ this.apiExtensions.hooks.push({
210
263
  type: 'init',
211
264
  path: hookPath,
212
265
  event,
@@ -225,7 +278,7 @@ class ExtensionManager {
225
278
  }
226
279
  }
227
280
  });
228
- this.apiHooks.push({
281
+ this.apiExtensions.hooks.push({
229
282
  type: 'schedule',
230
283
  path: hookPath,
231
284
  task,
@@ -263,12 +316,12 @@ class ExtensionManager {
263
316
  logger: logger_1.default,
264
317
  getSchema: get_schema_1.getSchema,
265
318
  });
266
- this.apiEndpoints.push({
319
+ this.apiExtensions.endpoints.push({
267
320
  path: endpointPath,
268
321
  });
269
322
  }
270
323
  unregisterHooks() {
271
- for (const hook of this.apiHooks) {
324
+ for (const hook of this.apiExtensions.hooks) {
272
325
  switch (hook.type) {
273
326
  case 'filter':
274
327
  emitter_1.default.offFilter(hook.event, hook.handler);
@@ -280,18 +333,18 @@ class ExtensionManager {
280
333
  emitter_1.default.offInit(hook.event, hook.handler);
281
334
  break;
282
335
  case 'schedule':
283
- hook.task.destroy();
336
+ hook.task.stop();
284
337
  break;
285
338
  }
286
339
  delete require.cache[require.resolve(hook.path)];
287
340
  }
288
- this.apiHooks = [];
341
+ this.apiExtensions.hooks = [];
289
342
  }
290
343
  unregisterEndpoints() {
291
- for (const endpoint of this.apiEndpoints) {
344
+ for (const endpoint of this.apiExtensions.endpoints) {
292
345
  delete require.cache[require.resolve(endpoint.path)];
293
346
  }
294
347
  this.endpointRouter.stack = [];
295
- this.apiEndpoints = [];
348
+ this.apiExtensions.endpoints = [];
296
349
  }
297
350
  }
@@ -52,9 +52,6 @@ class AssetsService {
52
52
  .from('directus_settings')
53
53
  .first();
54
54
  const systemPublicKeys = Object.values(publicSettings || {});
55
- if (systemPublicKeys.includes(id) === false && ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true) {
56
- await this.authorizationService.checkAccess('read', 'directus_files', id);
57
- }
58
55
  /**
59
56
  * This is a little annoying. Postgres will error out if you're trying to search in `where`
60
57
  * with a wrong type. In case of directus_files where id is a uuid, we'll have to verify the
@@ -63,6 +60,9 @@ class AssetsService {
63
60
  const isValidUUID = (0, uuid_validate_1.default)(id, 4);
64
61
  if (isValidUUID === false)
65
62
  throw new exceptions_1.ForbiddenException();
63
+ if (systemPublicKeys.includes(id) === false && ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true) {
64
+ await this.authorizationService.checkAccess('read', 'directus_files', id);
65
+ }
66
66
  const file = (await this.knex.select('*').from('directus_files').where({ id }).first());
67
67
  if (!file)
68
68
  throw new exceptions_1.ForbiddenException();
@@ -43,7 +43,7 @@ class AuthenticationService {
43
43
  const user = await this.knex
44
44
  .select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'r.admin_access', 'r.app_access', 'u.tfa_secret', 'u.provider', 'u.external_identifier', 'u.auth_data')
45
45
  .from('directus_users as u')
46
- .innerJoin('directus_roles as r', 'u.role', 'r.id')
46
+ .leftJoin('directus_roles as r', 'u.role', 'r.id')
47
47
  .where('u.id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
48
48
  .andWhere('u.provider', providerName)
49
49
  .first();
@@ -247,6 +247,9 @@ class AuthenticationService {
247
247
  collection: record.share_collection,
248
248
  item: record.share_item,
249
249
  };
250
+ tokenPayload.app_access = false;
251
+ tokenPayload.admin_access = false;
252
+ delete tokenPayload.id;
250
253
  }
251
254
  const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
252
255
  status: 'pending',
@@ -1397,10 +1397,10 @@ class GraphQLService {
1397
1397
  resolve: async () => {
1398
1398
  const extensionManager = (0, extensions_1.getExtensionManager)();
1399
1399
  return {
1400
- interfaces: extensionManager.listExtensions('interface'),
1401
- displays: extensionManager.listExtensions('display'),
1402
- layouts: extensionManager.listExtensions('layout'),
1403
- modules: extensionManager.listExtensions('module'),
1400
+ interfaces: extensionManager.getExtensionsList('interface'),
1401
+ displays: extensionManager.getExtensionsList('display'),
1402
+ layouts: extensionManager.getExtensionsList('layout'),
1403
+ modules: extensionManager.getExtensionsList('module'),
1404
1404
  };
1405
1405
  },
1406
1406
  },
@@ -107,7 +107,7 @@ function getLocalType(column, field) {
107
107
  return 'hash';
108
108
  if (special.includes('csv'))
109
109
  return 'csv';
110
- if (special.includes('uuid'))
110
+ if (special.includes('uuid') || special.includes('file'))
111
111
  return 'uuid';
112
112
  if (type === null || type === void 0 ? void 0 : type.startsWith('geometry')) {
113
113
  return special[0] || 'geometry';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.4.2",
3
+ "version": "9.4.3",
4
4
  "license": "GPL-3.0-only",
5
5
  "homepage": "https://github.com/directus/directus#readme",
6
6
  "description": "Directus is a real-time API and App dashboard for managing SQL database content.",
@@ -76,16 +76,16 @@
76
76
  ],
77
77
  "dependencies": {
78
78
  "@aws-sdk/client-ses": "^3.40.0",
79
- "@directus/app": "9.4.2",
80
- "@directus/drive": "9.4.2",
81
- "@directus/drive-azure": "9.4.2",
82
- "@directus/drive-gcs": "9.4.2",
83
- "@directus/drive-s3": "9.4.2",
84
- "@directus/extensions-sdk": "9.4.2",
85
- "@directus/format-title": "9.4.2",
86
- "@directus/schema": "9.4.2",
87
- "@directus/shared": "9.4.2",
88
- "@directus/specs": "9.4.2",
79
+ "@directus/app": "9.4.3",
80
+ "@directus/drive": "9.4.3",
81
+ "@directus/drive-azure": "9.4.3",
82
+ "@directus/drive-gcs": "9.4.3",
83
+ "@directus/drive-s3": "9.4.3",
84
+ "@directus/extensions-sdk": "9.4.3",
85
+ "@directus/format-title": "9.4.3",
86
+ "@directus/schema": "9.4.3",
87
+ "@directus/shared": "9.4.3",
88
+ "@directus/specs": "9.4.3",
89
89
  "@godaddy/terminus": "^4.9.0",
90
90
  "@rollup/plugin-alias": "^3.1.2",
91
91
  "@rollup/plugin-virtual": "^2.0.3",
@@ -97,6 +97,7 @@
97
97
  "busboy": "^0.3.1",
98
98
  "camelcase": "^6.2.0",
99
99
  "chalk": "^4.1.1",
100
+ "chokidar": "^3.5.2",
100
101
  "commander": "^8.0.0",
101
102
  "cookie-parser": "^1.4.5",
102
103
  "cors": "^2.8.5",
@@ -170,7 +171,7 @@
170
171
  "sqlite3": "^5.0.2",
171
172
  "tedious": "^13.0.0"
172
173
  },
173
- "gitHead": "1a5a9180ee6d0ad8f03076bc6e3cfdd62a0dd2f9",
174
+ "gitHead": "65bfe68b0c7993a1bd5b0daac0855265911313d0",
174
175
  "devDependencies": {
175
176
  "@types/async": "3.2.10",
176
177
  "@types/atob": "2.1.2",