directus 9.4.1 → 9.4.2

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
@@ -27,6 +27,7 @@ const express_1 = __importDefault(require("express"));
27
27
  const fs_extra_1 = __importDefault(require("fs-extra"));
28
28
  const path_1 = __importDefault(require("path"));
29
29
  const qs_1 = __importDefault(require("qs"));
30
+ const helmet_1 = __importDefault(require("helmet"));
30
31
  const activity_1 = __importDefault(require("./controllers/activity"));
31
32
  const assets_1 = __importDefault(require("./controllers/assets"));
32
33
  const auth_1 = __importDefault(require("./controllers/auth"));
@@ -75,6 +76,8 @@ const webhooks_2 = require("./webhooks");
75
76
  const cache_2 = require("./cache");
76
77
  const auth_2 = require("./auth");
77
78
  const url_1 = require("./utils/url");
79
+ const get_config_from_env_1 = require("./utils/get-config-from-env");
80
+ const lodash_1 = require("lodash");
78
81
  async function createApp() {
79
82
  (0, validate_env_1.validateEnv)(['KEY', 'SECRET']);
80
83
  if (!new url_1.Url(env_1.default.PUBLIC_URL).isAbsolute()) {
@@ -96,8 +99,15 @@ async function createApp() {
96
99
  await extensionManager.initialize();
97
100
  const app = (0, express_1.default)();
98
101
  app.disable('x-powered-by');
99
- app.set('trust proxy', true);
102
+ app.set('trust proxy', env_1.default.IP_TRUST_PROXY);
100
103
  app.set('query parser', (str) => qs_1.default.parse(str, { depth: 10 }));
104
+ app.use(helmet_1.default.contentSecurityPolicy((0, lodash_1.merge)({
105
+ useDefaults: true,
106
+ directives: {
107
+ // Unsafe-eval is required for vue3 / vue-i18n / app extensions
108
+ scriptSrc: ["'self'", "'unsafe-eval'"],
109
+ },
110
+ }, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
101
111
  await emitter_1.default.emitInit('app.before', { app });
102
112
  await emitter_1.default.emitInit('middlewares.before', { app });
103
113
  app.use(logger_1.expressLogger);
@@ -26,6 +26,7 @@ exports.createLDAPAuthRouter = exports.LDAPAuthDriver = void 0;
26
26
  const express_1 = require("express");
27
27
  const ldapjs_1 = __importStar(require("ldapjs"));
28
28
  const ms_1 = __importDefault(require("ms"));
29
+ const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
29
30
  const joi_1 = __importDefault(require("joi"));
30
31
  const auth_1 = require("../auth");
31
32
  const exceptions_1 = require("../../exceptions");
@@ -292,7 +293,7 @@ function createLDAPAuthRouter(provider) {
292
293
  router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
293
294
  var _a, _b;
294
295
  const accountability = {
295
- ip: req.ip,
296
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
296
297
  userAgent: req.get('user-agent'),
297
298
  role: null,
298
299
  };
@@ -14,6 +14,7 @@ const async_handler_1 = __importDefault(require("../../utils/async-handler"));
14
14
  const env_1 = __importDefault(require("../../env"));
15
15
  const respond_1 = require("../../middleware/respond");
16
16
  const constants_1 = require("../../constants");
17
+ const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
17
18
  class LocalAuthDriver extends auth_1.AuthDriver {
18
19
  async getUserID(payload) {
19
20
  if (!payload.email) {
@@ -50,7 +51,7 @@ function createLocalAuthRouter(provider) {
50
51
  router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
51
52
  var _a;
52
53
  const accountability = {
53
- ip: req.ip,
54
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
54
55
  userAgent: req.get('user-agent'),
55
56
  role: null,
56
57
  };
@@ -17,6 +17,7 @@ const respond_1 = require("../../middleware/respond");
17
17
  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
+ const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
20
21
  class OAuth2AuthDriver extends local_1.LocalAuthDriver {
21
22
  constructor(options, config) {
22
23
  super(options, config);
@@ -210,7 +211,7 @@ function createOAuth2AuthRouter(providerName) {
210
211
  const { verifier, redirect, prompt } = tokenData;
211
212
  const authenticationService = new services_1.AuthenticationService({
212
213
  accountability: {
213
- ip: req.ip,
214
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
214
215
  userAgent: req.get('user-agent'),
215
216
  role: null,
216
217
  },
@@ -17,6 +17,7 @@ const respond_1 = require("../../middleware/respond");
17
17
  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
+ const get_ip_from_req_1 = require("../../utils/get-ip-from-req");
20
21
  class OpenIDAuthDriver extends local_1.LocalAuthDriver {
21
22
  constructor(options, config) {
22
23
  super(options, config);
@@ -212,7 +213,7 @@ function createOpenIDAuthRouter(providerName) {
212
213
  const { verifier, redirect, prompt } = tokenData;
213
214
  const authenticationService = new services_1.AuthenticationService({
214
215
  accountability: {
215
- ip: req.ip,
216
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
216
217
  userAgent: req.get('user-agent'),
217
218
  role: null,
218
219
  },
package/dist/cache.js CHANGED
@@ -49,9 +49,7 @@ function getConfig(store = 'memory', ttl, namespaceSuffix = '') {
49
49
  };
50
50
  if (store === 'redis') {
51
51
  const KeyvRedis = require('@keyv/redis');
52
- config.store = new KeyvRedis(env_1.default.CACHE_REDIS || (0, get_config_from_env_1.getConfigFromEnv)('CACHE_REDIS_'), {
53
- commandTimeout: 500,
54
- });
52
+ config.store = new KeyvRedis(env_1.default.CACHE_REDIS || (0, get_config_from_env_1.getConfigFromEnv)('CACHE_REDIS_'));
55
53
  }
56
54
  if (store === 'memcache') {
57
55
  const KeyvMemcache = require('keyv-memcache');
@@ -12,6 +12,7 @@ const validate_batch_1 = require("../middleware/validate-batch");
12
12
  const services_1 = require("../services");
13
13
  const types_1 = require("../types");
14
14
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
15
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
15
16
  const router = express_1.default.Router();
16
17
  router.use((0, use_collection_1.default)('directus_activity'));
17
18
  const readHandler = (0, async_handler_1.default)(async (req, res, next) => {
@@ -72,7 +73,7 @@ router.post('/comment', (0, async_handler_1.default)(async (req, res, next) => {
72
73
  ...req.body,
73
74
  action: types_1.Action.COMMENT,
74
75
  user: (_a = req.accountability) === null || _a === void 0 ? void 0 : _a.user,
75
- ip: req.ip,
76
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
76
77
  user_agent: req.get('user-agent'),
77
78
  });
78
79
  try {
@@ -14,6 +14,7 @@ const get_auth_providers_1 = require("../utils/get-auth-providers");
14
14
  const logger_1 = __importDefault(require("../logger"));
15
15
  const drivers_1 = require("../auth/drivers");
16
16
  const constants_1 = require("../constants");
17
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
17
18
  const router = (0, express_1.Router)();
18
19
  const authProviders = (0, get_auth_providers_1.getAuthProviders)();
19
20
  for (const authProvider of authProviders) {
@@ -44,7 +45,7 @@ if (!env_1.default.AUTH_DISABLE_DEFAULT) {
44
45
  router.post('/refresh', (0, async_handler_1.default)(async (req, res, next) => {
45
46
  var _a;
46
47
  const accountability = {
47
- ip: req.ip,
48
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
48
49
  userAgent: req.get('user-agent'),
49
50
  role: null,
50
51
  };
@@ -79,7 +80,7 @@ router.post('/refresh', (0, async_handler_1.default)(async (req, res, next) => {
79
80
  router.post('/logout', (0, async_handler_1.default)(async (req, res, next) => {
80
81
  var _a;
81
82
  const accountability = {
82
- ip: req.ip,
83
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
83
84
  userAgent: req.get('user-agent'),
84
85
  role: null,
85
86
  };
@@ -107,7 +108,7 @@ router.post('/password/request', (0, async_handler_1.default)(async (req, res, n
107
108
  throw new exceptions_1.InvalidPayloadException(`"email" field is required.`);
108
109
  }
109
110
  const accountability = {
110
- ip: req.ip,
111
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
111
112
  userAgent: req.get('user-agent'),
112
113
  role: null,
113
114
  };
@@ -134,7 +135,7 @@ router.post('/password/reset', (0, async_handler_1.default)(async (req, res, nex
134
135
  throw new exceptions_1.InvalidPayloadException(`"password" field is required.`);
135
136
  }
136
137
  const accountability = {
137
- ip: req.ip,
138
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
138
139
  userAgent: req.get('user-agent'),
139
140
  role: null,
140
141
  };
@@ -88,6 +88,9 @@ function getDatabase() {
88
88
  // act the same
89
89
  (0, lodash_1.merge)(knexConfig, { connection: { options: { useUTC: false } } });
90
90
  }
91
+ if (env_1.default.DB_CLIENT === 'mysql' && !env_1.default.DB_CHARSET) {
92
+ logger_1.default.warn(`DB_CHARSET hasn't been set. Please make sure DB_CHARSET matches your database's collation.`);
93
+ }
91
94
  database = (0, knex_1.knex)(knexConfig);
92
95
  const times = {};
93
96
  database
@@ -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.string('project_descriptor', 100).nullable();
7
+ });
8
+ }
9
+ exports.up = up;
10
+ async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('project_descriptor');
13
+ });
14
+ }
15
+ exports.down = down;
@@ -14,6 +14,16 @@ fields:
14
14
  translations: Name
15
15
  width: half
16
16
 
17
+ - field: project_descriptor
18
+ interface: input
19
+ options:
20
+ iconRight: title
21
+ placeholder: $t:field_options.directus_settings.project_name_placeholder
22
+ translations:
23
+ language: en-US
24
+ translations: Name
25
+ width: half
26
+
17
27
  - field: project_url
18
28
  interface: input
19
29
  options:
@@ -22,7 +32,7 @@ fields:
22
32
  translations:
23
33
  language: en-US
24
34
  translations: Website
25
- width: half
35
+ width: full
26
36
 
27
37
  - field: branding_divider
28
38
  interface: presentation-divider
package/dist/env.js CHANGED
@@ -61,6 +61,8 @@ const defaults = {
61
61
  ASSETS_TRANSFORM_MAX_CONCURRENT: 1,
62
62
  ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 6000,
63
63
  ASSETS_TRANSFORM_MAX_OPERATIONS: 5,
64
+ IP_TRUST_PROXY: true,
65
+ IP_CUSTOM_HEADER: false,
64
66
  SERVE_APP: true,
65
67
  };
66
68
  // Allows us to force certain environment variable into a type, instead of relying
@@ -27,6 +27,7 @@ const database_1 = __importDefault(require("../database"));
27
27
  const env_1 = __importDefault(require("../env"));
28
28
  const exceptions_1 = require("../exceptions");
29
29
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
30
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
30
31
  const is_directus_jwt_1 = __importDefault(require("../utils/is-directus-jwt"));
31
32
  /**
32
33
  * Verify the passed JWT and assign the user ID and role to `req`
@@ -37,7 +38,7 @@ const authenticate = (0, async_handler_1.default)(async (req, res, next) => {
37
38
  role: null,
38
39
  admin: false,
39
40
  app: false,
40
- ip: req.ip.startsWith('::ffff:') ? req.ip.substring(7) : req.ip,
41
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
41
42
  userAgent: req.get('user-agent'),
42
43
  };
43
44
  const database = (0, database_1.default)();
@@ -9,6 +9,7 @@ const env_1 = __importDefault(require("../env"));
9
9
  const exceptions_1 = require("../exceptions");
10
10
  const rate_limiter_1 = require("../rate-limiter");
11
11
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
12
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
12
13
  const validate_env_1 = require("../utils/validate-env");
13
14
  let checkRateLimit = (req, res, next) => next();
14
15
  if (env_1.default.RATE_LIMITER_ENABLED === true) {
@@ -16,7 +17,7 @@ if (env_1.default.RATE_LIMITER_ENABLED === true) {
16
17
  exports.rateLimiter = (0, rate_limiter_1.createRateLimiter)();
17
18
  checkRateLimit = (0, async_handler_1.default)(async (req, res, next) => {
18
19
  try {
19
- await exports.rateLimiter.consume(req.ip, 1);
20
+ await exports.rateLimiter.consume((0, get_ip_from_req_1.getIPFromReq)(req), 1);
20
21
  }
21
22
  catch (rateLimiterRes) {
22
23
  if (rateLimiterRes instanceof Error)
@@ -70,7 +70,7 @@ export declare class GraphQLService {
70
70
  * Effectively merges the selections with the fragments used in those selections
71
71
  */
72
72
  replaceFragmentsInSelections(selections: readonly SelectionNode[] | undefined, fragments: Record<string, FragmentDefinitionNode>): readonly SelectionNode[] | null;
73
- injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, DeleteCollectionTypes, }: {
73
+ injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }: {
74
74
  CreateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
75
75
  ReadCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
76
76
  UpdateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
@@ -1307,7 +1307,7 @@ class GraphQLService {
1307
1307
  })).filter((s) => s);
1308
1308
  return result;
1309
1309
  }
1310
- injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, DeleteCollectionTypes, }, schema) {
1310
+ injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }, schema) {
1311
1311
  var _a, _b, _c, _d, _e;
1312
1312
  const AuthTokens = schemaComposer.createObjectTC({
1313
1313
  name: 'auth_tokens',
@@ -2131,6 +2131,32 @@ class GraphQLService {
2131
2131
  },
2132
2132
  });
2133
2133
  }
2134
+ if ('directus_users' in schema.update.collections) {
2135
+ schemaComposer.Mutation.addFields({
2136
+ update_users_me: {
2137
+ type: ReadCollectionTypes['directus_users'],
2138
+ args: {
2139
+ data: (0, graphql_compose_1.toInputObjectType)(UpdateCollectionTypes['directus_users']),
2140
+ },
2141
+ resolve: async (_, args, __, info) => {
2142
+ var _a, _b, _c;
2143
+ if (!((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.user))
2144
+ return null;
2145
+ const service = new users_1.UsersService({
2146
+ schema: this.schema,
2147
+ accountability: this.accountability,
2148
+ });
2149
+ await service.updateOne(this.accountability.user, args.data);
2150
+ if ('directus_users' in ReadCollectionTypes) {
2151
+ const selections = this.replaceFragmentsInSelections((_c = (_b = info.fieldNodes[0]) === null || _b === void 0 ? void 0 : _b.selectionSet) === null || _c === void 0 ? void 0 : _c.selections, info.fragments);
2152
+ const query = this.getQuery(args, selections || [], info.variableValues);
2153
+ return await service.readOne(this.accountability.user, query);
2154
+ }
2155
+ return true;
2156
+ },
2157
+ },
2158
+ });
2159
+ }
2134
2160
  if ('directus_activity' in schema.create.collections) {
2135
2161
  schemaComposer.Mutation.addFields({
2136
2162
  create_comment: {
@@ -52,6 +52,7 @@ class ServerService {
52
52
  const projectInfo = await this.settingsService.readSingleton({
53
53
  fields: [
54
54
  'project_name',
55
+ 'project_descriptor',
55
56
  'project_logo',
56
57
  'project_color',
57
58
  'public_foreground',
@@ -0,0 +1,2 @@
1
+ import { Request } from 'express';
2
+ export declare function getIPFromReq(req: Request): string;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getIPFromReq = void 0;
7
+ const net_1 = require("net");
8
+ const env_1 = __importDefault(require("../env"));
9
+ const logger_1 = __importDefault(require("../logger"));
10
+ function getIPFromReq(req) {
11
+ let ip = req.ip;
12
+ if (env_1.default.IP_CUSTOM_HEADER) {
13
+ const customIPHeaderValue = req.get(env_1.default.IP_CUSTOM_HEADER);
14
+ if (typeof customIPHeaderValue === 'string' && (0, net_1.isIP)(customIPHeaderValue) !== 0) {
15
+ ip = customIPHeaderValue;
16
+ }
17
+ else {
18
+ logger_1.default.warn(`Custom IP header didn't return valid IP address: ${JSON.stringify(customIPHeaderValue)}`);
19
+ }
20
+ }
21
+ // IP addresses starting with ::ffff: are IPv4 addresses in IPv6 format. We can strip the prefix to get back to IPv4
22
+ return ip.startsWith('::ffff:') ? ip.substring(7) : ip;
23
+ }
24
+ exports.getIPFromReq = getIPFromReq;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.4.1",
3
+ "version": "9.4.2",
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.1",
80
- "@directus/drive": "9.4.1",
81
- "@directus/drive-azure": "9.4.1",
82
- "@directus/drive-gcs": "9.4.1",
83
- "@directus/drive-s3": "9.4.1",
84
- "@directus/extensions-sdk": "9.4.1",
85
- "@directus/format-title": "9.4.1",
86
- "@directus/schema": "9.4.1",
87
- "@directus/shared": "9.4.1",
88
- "@directus/specs": "9.4.1",
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",
89
89
  "@godaddy/terminus": "^4.9.0",
90
90
  "@rollup/plugin-alias": "^3.1.2",
91
91
  "@rollup/plugin-virtual": "^2.0.3",
@@ -114,6 +114,7 @@
114
114
  "fs-extra": "^10.0.0",
115
115
  "graphql": "^15.5.0",
116
116
  "graphql-compose": "^9.0.1",
117
+ "helmet": "^4.6.0",
117
118
  "inquirer": "^8.1.1",
118
119
  "joi": "^17.3.0",
119
120
  "js-yaml": "^4.1.0",
@@ -169,7 +170,7 @@
169
170
  "sqlite3": "^5.0.2",
170
171
  "tedious": "^13.0.0"
171
172
  },
172
- "gitHead": "4991ba858bdde8bdf03aee475d77a218da6e46ab",
173
+ "gitHead": "1a5a9180ee6d0ad8f03076bc6e3cfdd62a0dd2f9",
173
174
  "devDependencies": {
174
175
  "@types/async": "3.2.10",
175
176
  "@types/atob": "2.1.2",