@directus/api 14.0.1 → 14.0.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.
@@ -54,8 +54,9 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
54
54
  return generators.codeVerifier();
55
55
  }
56
56
  generateAuthUrl(codeVerifier, prompt = false) {
57
+ const { plainCodeChallenge } = this.config;
57
58
  try {
58
- const codeChallenge = generators.codeChallenge(codeVerifier);
59
+ const codeChallenge = plainCodeChallenge ? codeVerifier : generators.codeChallenge(codeVerifier);
59
60
  const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
60
61
  return this.client.authorizationUrl({
61
62
  scope: this.config['scope'] ?? 'email',
@@ -63,7 +64,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
63
64
  prompt: prompt ? 'consent' : undefined,
64
65
  ...paramsConfig,
65
66
  code_challenge: codeChallenge,
66
- code_challenge_method: 'S256',
67
+ code_challenge_method: plainCodeChallenge ? 'plain' : 'S256',
67
68
  // Some providers require state even with PKCE
68
69
  state: codeChallenge,
69
70
  });
@@ -85,10 +86,14 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
85
86
  logger.warn('[OAuth2] No code, codeVerifier or state in payload');
86
87
  throw new InvalidCredentialsError();
87
88
  }
89
+ const { plainCodeChallenge } = this.config;
88
90
  let tokenSet;
89
91
  let userInfo;
90
92
  try {
91
- tokenSet = await this.client.oauthCallback(this.redirectUrl, { code: payload['code'], state: payload['state'] }, { code_verifier: payload['codeVerifier'], state: generators.codeChallenge(payload['codeVerifier']) });
93
+ const codeChallenge = plainCodeChallenge
94
+ ? payload['codeVerifier']
95
+ : generators.codeChallenge(payload['codeVerifier']);
96
+ tokenSet = await this.client.oauthCallback(this.redirectUrl, { code: payload['code'], state: payload['state'] }, { code_verifier: payload['codeVerifier'], state: codeChallenge });
92
97
  userInfo = await this.client.userinfo(tokenSet.access_token);
93
98
  }
94
99
  catch (e) {
@@ -64,9 +64,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
64
64
  return generators.codeVerifier();
65
65
  }
66
66
  async generateAuthUrl(codeVerifier, prompt = false) {
67
+ const { plainCodeChallenge } = this.config;
67
68
  try {
68
69
  const client = await this.client;
69
- const codeChallenge = generators.codeChallenge(codeVerifier);
70
+ const codeChallenge = plainCodeChallenge ? codeVerifier : generators.codeChallenge(codeVerifier);
70
71
  const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
71
72
  return client.authorizationUrl({
72
73
  scope: this.config['scope'] ?? 'openid profile email',
@@ -74,7 +75,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
74
75
  prompt: prompt ? 'consent' : undefined,
75
76
  ...paramsConfig,
76
77
  code_challenge: codeChallenge,
77
- code_challenge_method: 'S256',
78
+ code_challenge_method: plainCodeChallenge ? 'plain' : 'S256',
78
79
  // Some providers require state even with PKCE
79
80
  state: codeChallenge,
80
81
  nonce: codeChallenge,
@@ -97,11 +98,14 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
97
98
  logger.warn('[OpenID] No code, codeVerifier or state in payload');
98
99
  throw new InvalidCredentialsError();
99
100
  }
101
+ const { plainCodeChallenge } = this.config;
100
102
  let tokenSet;
101
103
  let userInfo;
102
104
  try {
103
105
  const client = await this.client;
104
- const codeChallenge = generators.codeChallenge(payload['codeVerifier']);
106
+ const codeChallenge = plainCodeChallenge
107
+ ? payload['codeVerifier']
108
+ : generators.codeChallenge(payload['codeVerifier']);
105
109
  tokenSet = await client.callback(this.redirectUrl, { code: payload['code'], state: payload['state'], iss: payload['iss'] }, { code_verifier: payload['codeVerifier'], state: codeChallenge, nonce: codeChallenge });
106
110
  userInfo = tokenSet.claims();
107
111
  if (client.issuer.metadata['userinfo_endpoint']) {
@@ -107,3 +107,8 @@
107
107
  - tfa_secret
108
108
  - status
109
109
  - role
110
+
111
+ # This is a temporary allowed field to help people migrate from
112
+ # 10.6 to 10.7 and should be removed in 10.8
113
+ # @TODO remove
114
+ - theme
@@ -566,7 +566,8 @@ export class ExtensionManager {
566
566
  */
567
567
  registerEndpoint(config, name) {
568
568
  const endpointRegistrationCallback = typeof config === 'function' ? config : config.handler;
569
- const routeName = typeof config === 'function' ? name : config.id;
569
+ const nameWithoutType = name.includes(':') ? name.split(':')[0] : name;
570
+ const routeName = typeof config === 'function' ? nameWithoutType : config.id;
570
571
  const scopedRouter = express.Router();
571
572
  this.endpointRouter.use(`/${routeName}`, scopedRouter);
572
573
  endpointRegistrationCallback(scopedRouter, {
@@ -2,6 +2,7 @@ import Joi from 'joi';
2
2
  import { InvalidPayloadError } from '@directus/errors';
3
3
  import asyncHandler from '../utils/async-handler.js';
4
4
  import { sanitizeQuery } from '../utils/sanitize-query.js';
5
+ import { validateQuery } from '../utils/validate-query.js';
5
6
  export const validateBatch = (scope) => asyncHandler(async (req, _res, next) => {
6
7
  if (req.method.toLowerCase() === 'get') {
7
8
  req.body = {};
@@ -18,6 +19,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
18
19
  // In reads, the query in the body should override the query params for searching
19
20
  if (scope === 'read' && req.body.query) {
20
21
  req.sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
22
+ validateQuery(req.sanitizedQuery);
21
23
  }
22
24
  // Every cRUD action has either keys or query
23
25
  let batchSchema = Joi.object().keys({
@@ -29,23 +29,23 @@ export class FilesService extends ItemsService {
29
29
  */
30
30
  async uploadOne(stream, data, primaryKey, opts) {
31
31
  const storage = await getStorage();
32
- let existingFile = {};
32
+ let existingFile = null;
33
33
  if (primaryKey !== undefined) {
34
34
  existingFile =
35
35
  (await this.knex
36
36
  .select('folder', 'filename_download')
37
37
  .from('directus_files')
38
38
  .where({ id: primaryKey })
39
- .first()) ?? {};
39
+ .first()) ?? null;
40
40
  }
41
- const payload = { ...existingFile, ...clone(data) };
41
+ const payload = { ...(existingFile ?? {}), ...clone(data) };
42
42
  if ('folder' in payload === false) {
43
43
  const settings = await this.knex.select('storage_default_folder').from('directus_settings').first();
44
44
  if (settings?.storage_default_folder) {
45
45
  payload.folder = settings.storage_default_folder;
46
46
  }
47
47
  }
48
- if (primaryKey !== undefined) {
48
+ if (existingFile !== null && primaryKey !== undefined) {
49
49
  await this.updateOne(primaryKey, payload, { emitEvents: false });
50
50
  // If the file you're uploading already exists, we'll consider this upload a replace. In that case, we'll
51
51
  // delete the previously saved file and thumbnails to ensure they're generated fresh
@@ -1,5 +1,12 @@
1
1
  import { createRequire } from 'node:module';
2
+ import logger from '../logger.js';
2
3
  const require = createRequire(import.meta.url);
3
4
  export function deleteFromRequireCache(modulePath) {
4
- delete require.cache[require.resolve(modulePath)];
5
+ try {
6
+ const moduleCachePath = require.resolve(modulePath);
7
+ delete require.cache[moduleCachePath];
8
+ }
9
+ catch (error) {
10
+ logger.trace(`Module cache not found for ${modulePath}, skipped cache delete.`);
11
+ }
5
12
  }
@@ -81,6 +81,7 @@ function sanitizeSort(rawSort) {
81
81
  fields = rawSort.split(',');
82
82
  else if (Array.isArray(rawSort))
83
83
  fields = rawSort;
84
+ fields = fields.map((field) => field.trim());
84
85
  return fields;
85
86
  }
86
87
  function sanitizeAggregate(rawAggregate) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "14.0.1",
3
+ "version": "14.0.2",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -144,23 +144,23 @@
144
144
  "ws": "8.14.2",
145
145
  "zod": "3.22.4",
146
146
  "zod-validation-error": "1.0.1",
147
- "@directus/app": "10.11.0",
147
+ "@directus/app": "10.12.0",
148
148
  "@directus/constants": "11.0.1",
149
- "@directus/errors": "0.2.0",
150
149
  "@directus/extensions": "0.1.1",
150
+ "@directus/errors": "0.2.0",
151
151
  "@directus/extensions-sdk": "10.1.14",
152
152
  "@directus/pressure": "1.0.12",
153
153
  "@directus/schema": "11.0.0",
154
154
  "@directus/specs": "10.2.1",
155
155
  "@directus/storage": "10.0.7",
156
156
  "@directus/storage-driver-azure": "10.0.13",
157
- "@directus/storage-driver-cloudinary": "10.0.13",
158
157
  "@directus/storage-driver-gcs": "10.0.13",
159
- "@directus/storage-driver-local": "10.0.13",
158
+ "@directus/storage-driver-cloudinary": "10.0.13",
160
159
  "@directus/storage-driver-s3": "10.0.13",
160
+ "@directus/storage-driver-local": "10.0.13",
161
161
  "@directus/storage-driver-supabase": "1.0.5",
162
- "@directus/utils": "11.0.1",
163
- "@directus/validation": "0.0.8"
162
+ "@directus/validation": "0.0.8",
163
+ "@directus/utils": "11.0.1"
164
164
  },
165
165
  "devDependencies": {
166
166
  "@ngneat/falso": "6.4.0",