directus 9.17.4 → 9.18.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.
package/dist/app.js CHANGED
@@ -168,7 +168,7 @@ async function createApp() {
168
168
  res.send(constants_1.ROBOTSTXT);
169
169
  });
170
170
  if (env_1.default.SERVE_APP) {
171
- const adminPath = require.resolve('@directus/app', require.main ? { paths: [require.main.filename] } : undefined);
171
+ const adminPath = require.resolve('@directus/app');
172
172
  const adminUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin');
173
173
  // Set the App's base path according to the APIs public URL
174
174
  const html = await fs_extra_1.default.readFile(adminPath, 'utf8');
@@ -18,7 +18,11 @@ async function usersPasswd({ email, password }) {
18
18
  const passwordHashed = await (0, generate_hash_1.generateHash)(password);
19
19
  const schema = await (0, get_schema_1.getSchema)();
20
20
  const service = new services_1.UsersService({ schema, knex: database });
21
- const user = await service.knex.select('id').from('directus_users').where({ email }).first();
21
+ const user = await service.knex
22
+ .select('id')
23
+ .from('directus_users')
24
+ .whereRaw('LOWER(??) = ?', ['email', email.toLowerCase()])
25
+ .first();
22
26
  if (user) {
23
27
  await service.knex('directus_users').update({ password: passwordHashed }).where({ id: user.id });
24
28
  logger_1.default.info(`Password is updated for user ${user.id}`);
@@ -251,7 +251,7 @@ CORS_MAX_AGE=18000
251
251
  # The amount of threads to compute the hash on. Each thread has a memory pool with HASH_MEMORY_COST size [1]
252
252
  # HASH_PARALLELISM=2
253
253
 
254
- # The variant of the hash function (0: argon2d, 1: argon2i, or 2: argon2id) [1]
254
+ # The variant of the hash function (0: argon2d, 1: argon2i, or 2: argon2id) [2]
255
255
  # HASH_TYPE=2
256
256
 
257
257
  # An extra and optional non-secret value. The value will be included B64 encoded in the parameters portion of the digest []
@@ -169,7 +169,7 @@ class ExtensionManager {
169
169
  if (!this.watcher) {
170
170
  logger_1.default.info('Watching extensions for changes...');
171
171
  const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_OR_HYBRID_EXTENSION_TYPES).flatMap((type) => {
172
- const typeDir = path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type));
172
+ const typeDir = path_1.default.posix.join((0, node_1.pathToRelativeUrl)(env_1.default.EXTENSIONS_PATH), (0, utils_1.pluralize)(type));
173
173
  return (0, utils_1.isIn)(type, constants_1.HYBRID_EXTENSION_TYPES)
174
174
  ? [path_1.default.posix.join(typeDir, '*', 'app.js'), path_1.default.posix.join(typeDir, '*', 'api.js')]
175
175
  : path_1.default.posix.join(typeDir, '*', 'index.js');
@@ -239,8 +239,7 @@ class ExtensionManager {
239
239
  return bundles;
240
240
  }
241
241
  async getSharedDepsMapping(deps) {
242
- var _a;
243
- const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app', (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename), 'dist', 'assets'));
242
+ const appDir = await fs_extra_1.default.readdir(path_1.default.join((0, node_1.resolvePackage)('@directus/app', __dirname), 'dist', 'assets'));
244
243
  const depsMapping = {};
245
244
  for (const dep of deps) {
246
245
  const depRegex = new RegExp(`${(0, lodash_1.escapeRegExp)(dep.replace(/\//g, '_'))}\\.[0-9a-f]{8}\\.entry\\.js`);
@@ -286,7 +285,7 @@ class ExtensionManager {
286
285
  }
287
286
  }
288
287
  async registerOperations() {
289
- const internalPaths = await (0, globby_1.default)(path_1.default.posix.join(path_1.default.relative('.', __dirname).split(path_1.default.sep).join(path_1.default.posix.sep), 'operations/*/index.(js|ts)'));
288
+ const internalPaths = await (0, globby_1.default)(path_1.default.posix.join((0, node_1.pathToRelativeUrl)(__dirname), 'operations/*/index.(js|ts)'));
290
289
  const internalOperations = internalPaths.map((internalPath) => {
291
290
  const dirs = internalPath.split(path_1.default.sep);
292
291
  return {
@@ -12,7 +12,7 @@ const logger_1 = __importDefault(require("../logger"));
12
12
  const checkCacheMiddleware = (0, async_handler_1.default)(async (req, res, next) => {
13
13
  var _a, _b, _c, _d;
14
14
  const { cache } = (0, cache_1.getCache)();
15
- if (req.method.toLowerCase() !== 'get' && ((_a = req.path) === null || _a === void 0 ? void 0 : _a.startsWith('/graphql')) === false)
15
+ if (req.method.toLowerCase() !== 'get' && ((_a = req.originalUrl) === null || _a === void 0 ? void 0 : _a.startsWith('/graphql')) === false)
16
16
  return next();
17
17
  if (env_1.default.CACHE_ENABLED !== true)
18
18
  return next();
@@ -24,7 +24,7 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
24
24
  const maxSize = (0, bytes_1.parse)(env_1.default.CACHE_VALUE_MAX_SIZE);
25
25
  exceedsMaxSize = valueSize > maxSize;
26
26
  }
27
- if ((req.method.toLowerCase() === 'get' || ((_a = req.path) === null || _a === void 0 ? void 0 : _a.startsWith('/graphql'))) &&
27
+ if ((req.method.toLowerCase() === 'get' || ((_a = req.originalUrl) === null || _a === void 0 ? void 0 : _a.startsWith('/graphql'))) &&
28
28
  env_1.default.CACHE_ENABLED === true &&
29
29
  cache &&
30
30
  !req.sanitizedQuery.export &&
@@ -13,7 +13,7 @@ exports.default = (0, utils_1.defineOperationApi)({
13
13
  customAccountability = accountability;
14
14
  }
15
15
  else if (permissions === '$full') {
16
- customAccountability = null;
16
+ customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)('system', { database, schema, accountability });
17
17
  }
18
18
  else if (permissions === '$public') {
19
19
  customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)(null, { database, schema, accountability });
@@ -13,7 +13,7 @@ exports.default = (0, utils_1.defineOperationApi)({
13
13
  customAccountability = accountability;
14
14
  }
15
15
  else if (permissions === '$full') {
16
- customAccountability = null;
16
+ customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)('system', { database, schema, accountability });
17
17
  }
18
18
  else if (permissions === '$public') {
19
19
  customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)(null, { database, schema, accountability });
@@ -13,7 +13,7 @@ exports.default = (0, utils_1.defineOperationApi)({
13
13
  customAccountability = accountability;
14
14
  }
15
15
  else if (permissions === '$full') {
16
- customAccountability = null;
16
+ customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)('system', { database, schema, accountability });
17
17
  }
18
18
  else if (permissions === '$public') {
19
19
  customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)(null, { database, schema, accountability });
@@ -14,7 +14,7 @@ exports.default = (0, utils_1.defineOperationApi)({
14
14
  customAccountability = accountability;
15
15
  }
16
16
  else if (permissions === '$full') {
17
- customAccountability = null;
17
+ customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)('system', { database, schema, accountability });
18
18
  }
19
19
  else if (permissions === '$public') {
20
20
  customAccountability = await (0, get_accountability_for_role_1.getAccountabilityForRole)(null, { database, schema, accountability });
@@ -1,6 +1,7 @@
1
1
  declare type Options = {
2
2
  body: string;
3
3
  to: string;
4
+ type: 'wysiwyg' | 'markdown';
4
5
  subject: string;
5
6
  };
6
7
  declare const _default: import("@directus/shared/types").OperationApiConfig<Options>;
@@ -5,10 +5,10 @@ const services_1 = require("../../services");
5
5
  const md_1 = require("../../utils/md");
6
6
  exports.default = (0, utils_1.defineOperationApi)({
7
7
  id: 'mail',
8
- handler: async ({ body, to, subject }, { accountability, database, getSchema }) => {
8
+ handler: async ({ body, to, type, subject }, { accountability, database, getSchema }) => {
9
9
  const mailService = new services_1.MailService({ schema: await getSchema({ database }), accountability, knex: database });
10
10
  await mailService.send({
11
- html: (0, md_1.md)(body),
11
+ html: type === 'wysiwyg' ? body : (0, md_1.md)(body),
12
12
  to,
13
13
  subject,
14
14
  });
@@ -139,7 +139,9 @@ class AssetsService {
139
139
  const transformer = (0, sharp_1.default)({
140
140
  limitInputPixels: Math.pow(env_1.default.ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION, 2),
141
141
  sequentialRead: true,
142
- }).rotate();
142
+ });
143
+ if (transforms.find((transform) => transform[0] === 'rotate') === undefined)
144
+ transformer.rotate();
143
145
  transforms.forEach(([method, ...args]) => transformer[method].apply(transformer, args));
144
146
  readStream.on('error', (e) => {
145
147
  logger_1.default.error(e, `Couldn't transform file ${file.id}`);
@@ -284,7 +284,7 @@ class GraphQLService {
284
284
  type = (0, graphql_1.GraphQLNonNull)(type);
285
285
  }
286
286
  if (collection.primary === field.field) {
287
- type = graphql_1.GraphQLID;
287
+ type = (0, graphql_1.GraphQLNonNull)(graphql_1.GraphQLID);
288
288
  }
289
289
  acc[field.field] = {
290
290
  type,
@@ -292,7 +292,11 @@ class UsersService extends items_1.ItemsService {
292
292
  }
293
293
  const STALL_TIME = 500;
294
294
  const timeStart = perf_hooks_1.performance.now();
295
- const user = await this.knex.select('status', 'password').from('directus_users').where({ email }).first();
295
+ const user = await this.knex
296
+ .select('status', 'password')
297
+ .from('directus_users')
298
+ .whereRaw('LOWER(??) = ?', ['email', email.toLowerCase()])
299
+ .first();
296
300
  if ((user === null || user === void 0 ? void 0 : user.status) !== 'active') {
297
301
  await (0, stall_1.stall)(STALL_TIME, timeStart);
298
302
  throw new exceptions_2.ForbiddenException();
@@ -14,6 +14,15 @@ async function getAccountabilityForRole(role, context) {
14
14
  };
15
15
  generatedAccountability.permissions = await (0, get_permissions_1.getPermissions)(generatedAccountability, context.schema);
16
16
  }
17
+ else if (role === 'system') {
18
+ generatedAccountability = {
19
+ user: null,
20
+ role: null,
21
+ admin: true,
22
+ app: true,
23
+ permissions: [],
24
+ };
25
+ }
17
26
  else {
18
27
  const roleInfo = await context.database
19
28
  .select(['app_access', 'admin_access'])
@@ -8,13 +8,14 @@ const url_1 = __importDefault(require("url"));
8
8
  const object_hash_1 = __importDefault(require("object-hash"));
9
9
  const lodash_1 = require("lodash");
10
10
  function getCacheKey(req) {
11
- var _a;
11
+ var _a, _b;
12
12
  const path = url_1.default.parse(req.originalUrl).pathname;
13
13
  const isGraphQl = path === null || path === void 0 ? void 0 : path.includes('/graphql');
14
+ const isGet = ((_a = req.method) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'get';
14
15
  const info = {
15
- user: ((_a = req.accountability) === null || _a === void 0 ? void 0 : _a.user) || null,
16
+ user: ((_b = req.accountability) === null || _b === void 0 ? void 0 : _b.user) || null,
16
17
  path,
17
- query: isGraphQl ? (0, lodash_1.pick)(req.query, ['query', 'variables']) : req.sanitizedQuery,
18
+ query: isGraphQl ? (0, lodash_1.pick)(isGet ? req.query : req.body, ['query', 'variables']) : req.sanitizedQuery,
18
19
  };
19
20
  const key = (0, object_hash_1.default)(info);
20
21
  return key;
@@ -4,32 +4,48 @@ const get_cache_key_1 = require("../../src/utils/get-cache-key");
4
4
  const restUrl = 'http://localhost/items/example';
5
5
  const graphQlUrl = 'http://localhost/graphql';
6
6
  const accountability = { user: '00000000-0000-0000-0000-000000000000' };
7
+ const method = 'GET';
7
8
  const requests = [
8
9
  {
9
10
  name: 'as unauthenticated request',
10
- params: { originalUrl: restUrl },
11
+ params: { method, originalUrl: restUrl },
11
12
  key: '17da8272c9a0ec6eea38a37d6d78bddeb7c79045',
12
13
  },
13
14
  {
14
15
  name: 'as authenticated request',
15
- params: { originalUrl: restUrl, accountability },
16
+ params: { method, originalUrl: restUrl, accountability },
16
17
  key: '99a6394222a3d7d149ac1662fc2fff506932db58',
17
18
  },
18
19
  {
19
20
  name: 'a request with a fields query',
20
- params: { originalUrl: restUrl, sanitizedQuery: { fields: ['id', 'name'] } },
21
+ params: { method, originalUrl: restUrl, sanitizedQuery: { fields: ['id', 'name'] } },
21
22
  key: 'aa6e2d8a78de4dfb4af6eaa230d1cd9b7d31ed19',
22
23
  },
23
24
  {
24
25
  name: 'a request with a filter query',
25
- params: { originalUrl: restUrl, sanitizedQuery: { filter: { name: { _eq: 'test' } } } },
26
+ params: { method, originalUrl: restUrl, sanitizedQuery: { filter: { name: { _eq: 'test' } } } },
26
27
  key: 'd7eb8970f0429e1cf85e12eb5bb8669f618b09d3',
27
28
  },
28
29
  {
29
- name: 'a GraphQL query request',
30
- params: { originalUrl: graphQlUrl, query: { query: 'query { test { id } }' } },
30
+ name: 'a GraphQL GET query request',
31
+ params: { method, originalUrl: graphQlUrl, query: { query: 'query { test { id } }' } },
31
32
  key: '201731b75c627c60554512d819b6935b54c73814',
32
33
  },
34
+ {
35
+ name: 'a GraphQL POST query request',
36
+ params: { method: 'POST', originalUrl: graphQlUrl, body: { query: 'query { test { name } }' } },
37
+ key: '64eb0c48ea69d0863ff930398f29b5c7884f88f7',
38
+ },
39
+ {
40
+ name: 'an authenticated GraphQL GET query request',
41
+ params: { method, originalUrl: graphQlUrl, accountability, query: { query: 'query { test { id } }' } },
42
+ key: '9bc52c98dcf2de04c64589f52e0ada1e38d53a90',
43
+ },
44
+ {
45
+ name: 'an authenticated GraphQL POST query request',
46
+ params: { method: 'POST', originalUrl: graphQlUrl, accountability, body: { query: 'query { test { name } }' } },
47
+ key: '051ea77ce5ba71bbc88bcb567b9ddc602b585c13',
48
+ },
33
49
  ];
34
50
  const cases = requests.map(({ name, params, key }) => [name, params, key]);
35
51
  describe('get cache key', () => {
@@ -37,7 +53,7 @@ describe('get cache key', () => {
37
53
  expect((0, get_cache_key_1.getCacheKey)(params)).toEqual(key);
38
54
  });
39
55
  test('should create a unique key for each request', () => {
40
- const keys = requests.map((r) => r.key);
56
+ const keys = cases.map(([, params]) => (0, get_cache_key_1.getCacheKey)(params));
41
57
  const hasDuplicate = keys.some((key) => keys.indexOf(key) !== keys.lastIndexOf(key));
42
58
  expect(hasDuplicate).toBeFalsy();
43
59
  });
@@ -46,8 +62,13 @@ describe('get cache key', () => {
46
62
  const operationName = 'test';
47
63
  const variables1 = JSON.stringify({ name: 'test 1' });
48
64
  const variables2 = JSON.stringify({ name: 'test 2' });
49
- const req1 = { originalUrl: graphQlUrl, query: { query, operationName, variables: variables1 } };
50
- const req2 = { originalUrl: graphQlUrl, query: { query, operationName, variables: variables2 } };
65
+ const req1 = { method, originalUrl: graphQlUrl, query: { query, operationName, variables: variables1 } };
66
+ const req2 = { method, originalUrl: graphQlUrl, query: { query, operationName, variables: variables2 } };
67
+ const postReq1 = { method: 'POST', originalUrl: req1.originalUrl, body: req1.query };
68
+ const postReq2 = { method: 'POST', originalUrl: req2.originalUrl, body: req2.query };
51
69
  expect((0, get_cache_key_1.getCacheKey)(req1)).not.toEqual((0, get_cache_key_1.getCacheKey)(req2));
70
+ expect((0, get_cache_key_1.getCacheKey)(postReq1)).not.toEqual((0, get_cache_key_1.getCacheKey)(postReq2));
71
+ expect((0, get_cache_key_1.getCacheKey)(req1)).toEqual((0, get_cache_key_1.getCacheKey)(postReq1));
72
+ expect((0, get_cache_key_1.getCacheKey)(req2)).toEqual((0, get_cache_key_1.getCacheKey)(postReq2));
52
73
  });
53
74
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.17.4",
3
+ "version": "9.18.0",
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.",
@@ -66,16 +66,16 @@
66
66
  ],
67
67
  "dependencies": {
68
68
  "@aws-sdk/client-ses": "^3.107.0",
69
- "@directus/app": "9.17.4",
70
- "@directus/drive": "9.17.4",
71
- "@directus/drive-azure": "9.17.4",
72
- "@directus/drive-gcs": "9.17.4",
73
- "@directus/drive-s3": "9.17.4",
69
+ "@directus/app": "9.18.0",
70
+ "@directus/drive": "9.18.0",
71
+ "@directus/drive-azure": "9.18.0",
72
+ "@directus/drive-gcs": "9.18.0",
73
+ "@directus/drive-s3": "9.18.0",
74
74
  "@directus/extensions-sdk": "^9.14.1",
75
75
  "@directus/format-title": "^9.15.0",
76
- "@directus/schema": "9.17.4",
77
- "@directus/shared": "9.17.4",
78
- "@directus/specs": "9.17.4",
76
+ "@directus/schema": "9.18.0",
77
+ "@directus/shared": "9.18.0",
78
+ "@directus/specs": "9.18.0",
79
79
  "@godaddy/terminus": "^4.10.2",
80
80
  "@rollup/plugin-alias": "^3.1.9",
81
81
  "@rollup/plugin-virtual": "^2.1.0",