@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.
- package/dist/auth/drivers/oauth2.js +8 -3
- package/dist/auth/drivers/openid.js +7 -3
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +5 -0
- package/dist/extensions/manager.js +2 -1
- package/dist/middleware/validate-batch.js +2 -0
- package/dist/services/files.js +4 -4
- package/dist/utils/delete-from-require-cache.js +8 -1
- package/dist/utils/sanitize-query.js +1 -0
- package/package.json +7 -7
|
@@ -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
|
-
|
|
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 =
|
|
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']) {
|
|
@@ -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
|
|
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({
|
package/dist/services/files.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "14.0.
|
|
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.
|
|
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-
|
|
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/
|
|
163
|
-
"@directus/
|
|
162
|
+
"@directus/validation": "0.0.8",
|
|
163
|
+
"@directus/utils": "11.0.1"
|
|
164
164
|
},
|
|
165
165
|
"devDependencies": {
|
|
166
166
|
"@ngneat/falso": "6.4.0",
|