@directus/api 25.0.0 → 26.0.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 +3 -3
- package/dist/auth/drivers/oauth2.d.ts +2 -0
- package/dist/auth/drivers/oauth2.js +40 -2
- package/dist/auth/drivers/openid.js +8 -1
- package/dist/controllers/access.js +2 -2
- package/dist/controllers/comments.js +2 -2
- package/dist/controllers/dashboards.js +2 -2
- package/dist/controllers/files.js +2 -2
- package/dist/controllers/flows.js +2 -2
- package/dist/controllers/folders.js +2 -2
- package/dist/controllers/items.js +2 -2
- package/dist/controllers/notifications.js +2 -2
- package/dist/controllers/operations.js +2 -2
- package/dist/controllers/panels.js +2 -2
- package/dist/controllers/permissions.js +2 -2
- package/dist/controllers/policies.js +2 -2
- package/dist/controllers/presets.js +2 -2
- package/dist/controllers/roles.js +2 -2
- package/dist/controllers/shares.js +2 -2
- package/dist/controllers/translations.js +2 -2
- package/dist/controllers/users.js +2 -2
- package/dist/controllers/utils.js +8 -3
- package/dist/controllers/versions.js +2 -2
- package/dist/controllers/webhooks.js +1 -1
- package/dist/database/helpers/capabilities/dialects/default.d.ts +3 -0
- package/dist/database/helpers/capabilities/dialects/default.js +3 -0
- package/dist/database/helpers/capabilities/dialects/mysql.d.ts +4 -0
- package/dist/database/helpers/capabilities/dialects/mysql.js +9 -0
- package/dist/database/helpers/capabilities/dialects/postgres.d.ts +5 -0
- package/dist/database/helpers/capabilities/dialects/postgres.js +14 -0
- package/dist/database/helpers/capabilities/index.d.ts +7 -0
- package/dist/database/helpers/capabilities/index.js +7 -0
- package/dist/database/helpers/capabilities/types.d.ts +11 -0
- package/dist/database/helpers/capabilities/types.js +15 -0
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -2
- package/dist/database/helpers/schema/dialects/cockroachdb.js +0 -4
- package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -2
- package/dist/database/helpers/schema/dialects/postgres.js +0 -4
- package/dist/database/index.js +1 -1
- package/dist/database/migrations/20250224A-visual-editor.d.ts +3 -0
- package/dist/database/migrations/20250224A-visual-editor.js +35 -0
- package/dist/database/run-ast/lib/get-db-query.js +16 -4
- package/dist/logger/index.js +3 -3
- package/dist/middleware/sanitize-query.js +17 -7
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/item-delete/index.js +1 -1
- package/dist/operations/item-read/index.js +1 -1
- package/dist/operations/item-update/index.js +1 -1
- package/dist/permissions/lib/fetch-permissions.js +6 -4
- package/dist/permissions/modules/process-ast/utils/context-has-dynamic-variables.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/context-has-dynamic-variables.js +3 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +1 -0
- package/dist/permissions/modules/process-payload/process-payload.js +13 -4
- package/dist/permissions/types.d.ts +2 -1
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +3 -2
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +24 -5
- package/dist/permissions/utils/fetch-dynamic-variable-data.d.ts +9 -0
- package/dist/permissions/utils/{fetch-dynamic-variable-context.js → fetch-dynamic-variable-data.js} +11 -12
- package/dist/rate-limiter.js +1 -1
- package/dist/services/assets.js +12 -2
- package/dist/services/authentication.js +2 -2
- package/dist/services/collections.js +39 -3
- package/dist/services/fields/build-collection-and-field-relations.d.ts +21 -0
- package/dist/services/fields/build-collection-and-field-relations.js +55 -0
- package/dist/services/fields/get-collection-meta-updates.d.ts +11 -0
- package/dist/services/fields/get-collection-meta-updates.js +72 -0
- package/dist/services/fields/get-collection-relation-list.d.ts +5 -0
- package/dist/services/fields/get-collection-relation-list.js +28 -0
- package/dist/services/fields.js +17 -12
- package/dist/services/graphql/resolvers/get-collection-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-collection-type.js +34 -0
- package/dist/services/graphql/resolvers/get-field-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-field-type.js +51 -0
- package/dist/services/graphql/resolvers/get-relation-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-relation-type.js +39 -0
- package/dist/services/graphql/resolvers/mutation.js +1 -1
- package/dist/services/graphql/resolvers/query.js +4 -4
- package/dist/services/graphql/resolvers/system-admin.d.ts +2 -2
- package/dist/services/graphql/resolvers/system-admin.js +207 -199
- package/dist/services/graphql/resolvers/system.d.ts +1 -7
- package/dist/services/graphql/resolvers/system.js +12 -113
- package/dist/services/graphql/schema/index.js +1 -1
- package/dist/services/graphql/schema/parse-query.d.ts +2 -2
- package/dist/services/graphql/schema/parse-query.js +6 -6
- package/dist/services/graphql/schema/read.d.ts +2 -2
- package/dist/services/graphql/schema/read.js +86 -2
- package/dist/services/graphql/schema-cache.d.ts +2 -2
- package/dist/services/graphql/schema-cache.js +1 -3
- package/dist/services/graphql/subscription.d.ts +3 -3
- package/dist/services/graphql/subscription.js +3 -3
- package/dist/services/graphql/utils/{aggrgate-query.d.ts → aggregate-query.d.ts} +2 -2
- package/dist/services/graphql/utils/{aggrgate-query.js → aggregate-query.js} +3 -3
- package/dist/services/items.d.ts +1 -0
- package/dist/services/items.js +30 -16
- package/dist/services/meta.js +4 -2
- package/dist/services/payload.d.ts +1 -0
- package/dist/services/payload.js +32 -17
- package/dist/services/shares.js +1 -1
- package/dist/services/specifications.js +10 -5
- package/dist/services/tus/lockers.d.ts +1 -1
- package/dist/services/tus/lockers.js +6 -5
- package/dist/services/tus/server.js +24 -0
- package/dist/services/users.js +1 -0
- package/dist/types/services.d.ts +2 -0
- package/dist/utils/apply-query.d.ts +1 -0
- package/dist/utils/apply-query.js +42 -31
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-config-from-env.d.ts +6 -1
- package/dist/utils/get-config-from-env.js +16 -11
- package/dist/utils/get-graphql-type.js +3 -1
- package/dist/utils/is-login-redirect-allowed.js +2 -0
- package/dist/utils/redact-object.js +5 -1
- package/dist/utils/sanitize-query.d.ts +5 -2
- package/dist/utils/sanitize-query.js +34 -9
- package/dist/websocket/controllers/base.d.ts +2 -2
- package/dist/websocket/handlers/items.js +4 -4
- package/dist/websocket/handlers/subscribe.js +2 -2
- package/dist/websocket/messages.d.ts +7 -7
- package/dist/websocket/messages.js +1 -1
- package/package.json +58 -58
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +0 -8
package/dist/app.js
CHANGED
|
@@ -94,7 +94,7 @@ export default async function createApp() {
|
|
|
94
94
|
const app = express();
|
|
95
95
|
app.disable('x-powered-by');
|
|
96
96
|
app.set('trust proxy', env['IP_TRUST_PROXY']);
|
|
97
|
-
app.set('query parser', (str) => qs.parse(str, { depth:
|
|
97
|
+
app.set('query parser', (str) => qs.parse(str, { depth: Number(env['QUERYSTRING_MAX_PARSE_DEPTH']) }));
|
|
98
98
|
if (env['PRESSURE_LIMITER_ENABLED']) {
|
|
99
99
|
const sampleInterval = Number(env['PRESSURE_LIMITER_SAMPLE_INTERVAL']);
|
|
100
100
|
if (Number.isNaN(sampleInterval) === true || Number.isFinite(sampleInterval) === false) {
|
|
@@ -134,7 +134,7 @@ export default async function createApp() {
|
|
|
134
134
|
},
|
|
135
135
|
}, getConfigFromEnv('CONTENT_SECURITY_POLICY_'))));
|
|
136
136
|
if (env['HSTS_ENABLED']) {
|
|
137
|
-
app.use(helmet.hsts(getConfigFromEnv('HSTS_',
|
|
137
|
+
app.use(helmet.hsts(getConfigFromEnv('HSTS_', { omitPrefix: 'HSTS_ENABLED' })));
|
|
138
138
|
}
|
|
139
139
|
await emitter.emitInit('app.before', { app });
|
|
140
140
|
await emitter.emitInit('middlewares.before', { app });
|
|
@@ -203,9 +203,9 @@ export default async function createApp() {
|
|
|
203
203
|
}
|
|
204
204
|
app.get('/server/ping', (_req, res) => res.send('pong'));
|
|
205
205
|
app.use(authenticate);
|
|
206
|
+
app.use(schema);
|
|
206
207
|
app.use(sanitizeQuery);
|
|
207
208
|
app.use(cache);
|
|
208
|
-
app.use(schema);
|
|
209
209
|
await emitter.emitInit('middlewares.after', { app });
|
|
210
210
|
await emitter.emitInit('routes.before', { app });
|
|
211
211
|
app.use('/auth', authRouter);
|
|
@@ -2,12 +2,14 @@ import { Router } from 'express';
|
|
|
2
2
|
import type { Client } from 'openid-client';
|
|
3
3
|
import { UsersService } from '../../services/users.js';
|
|
4
4
|
import type { AuthDriverOptions, User } from '../../types/index.js';
|
|
5
|
+
import type { RoleMap } from '../../types/rolemap.js';
|
|
5
6
|
import { LocalAuthDriver } from './local.js';
|
|
6
7
|
export declare class OAuth2AuthDriver extends LocalAuthDriver {
|
|
7
8
|
client: Client;
|
|
8
9
|
redirectUrl: string;
|
|
9
10
|
usersService: UsersService;
|
|
10
11
|
config: Record<string, any>;
|
|
12
|
+
roleMap: RoleMap;
|
|
11
13
|
constructor(options: AuthDriverOptions, config: Record<string, any>);
|
|
12
14
|
generateCodeVerifier(): string;
|
|
13
15
|
generateAuthUrl(codeVerifier: string, prompt?: boolean): string;
|
|
@@ -27,6 +27,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
27
27
|
redirectUrl;
|
|
28
28
|
usersService;
|
|
29
29
|
config;
|
|
30
|
+
roleMap;
|
|
30
31
|
constructor(options, config) {
|
|
31
32
|
super(options, config);
|
|
32
33
|
const env = useEnv();
|
|
@@ -40,13 +41,31 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
40
41
|
this.redirectUrl = redirectUrl.toString();
|
|
41
42
|
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
42
43
|
this.config = additionalConfig;
|
|
44
|
+
this.roleMap = {};
|
|
45
|
+
const roleMapping = this.config['roleMapping'];
|
|
46
|
+
if (roleMapping) {
|
|
47
|
+
this.roleMap = roleMapping;
|
|
48
|
+
}
|
|
49
|
+
// role mapping will fail on login if AUTH_<provider>_ROLE_MAPPING is an array instead of an object.
|
|
50
|
+
// This happens if the 'json:' prefix is missing from the variable declaration. To save the user from exhaustive debugging, we'll try to fail early here.
|
|
51
|
+
if (roleMapping instanceof Array) {
|
|
52
|
+
logger.error("[OAuth2] Expected a JSON-Object as role mapping, got an Array instead. Make sure you declare the variable with 'json:' prefix.");
|
|
53
|
+
throw new InvalidProviderError();
|
|
54
|
+
}
|
|
43
55
|
const issuer = new Issuer({
|
|
44
56
|
authorization_endpoint: authorizeUrl,
|
|
45
57
|
token_endpoint: accessUrl,
|
|
46
58
|
userinfo_endpoint: profileUrl,
|
|
47
59
|
issuer: additionalConfig['provider'],
|
|
48
60
|
});
|
|
49
|
-
|
|
61
|
+
// extract client overrides/options excluding CLIENT_ID and CLIENT_SECRET as they are passed directly
|
|
62
|
+
const clientOptionsOverrides = getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_CLIENT_`, {
|
|
63
|
+
omitKey: [
|
|
64
|
+
`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`,
|
|
65
|
+
`AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`,
|
|
66
|
+
],
|
|
67
|
+
type: 'underscore',
|
|
68
|
+
});
|
|
50
69
|
this.client = new issuer.Client({
|
|
51
70
|
client_id: clientId,
|
|
52
71
|
client_secret: clientSecret,
|
|
@@ -105,6 +124,21 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
105
124
|
catch (e) {
|
|
106
125
|
throw handleError(e);
|
|
107
126
|
}
|
|
127
|
+
let role = this.config['defaultRoleId'];
|
|
128
|
+
const groupClaimName = this.config['groupClaimName'] ?? 'groups';
|
|
129
|
+
const groups = userInfo[groupClaimName];
|
|
130
|
+
if (Array.isArray(groups)) {
|
|
131
|
+
for (const key in this.roleMap) {
|
|
132
|
+
if (groups.includes(key)) {
|
|
133
|
+
// Overwrite default role if user is member of a group specified in roleMap
|
|
134
|
+
role = this.roleMap[key];
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
logger.debug(`[OAuth2] Configured group claim with name "${groupClaimName}" does not exist or is empty.`);
|
|
141
|
+
}
|
|
108
142
|
// Flatten response to support dot indexes
|
|
109
143
|
userInfo = flatten(userInfo);
|
|
110
144
|
const { provider, emailKey, identifierKey, allowPublicRegistration, syncUserInfo } = this.config;
|
|
@@ -121,7 +155,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
121
155
|
last_name: userInfo[this.config['lastNameKey']],
|
|
122
156
|
email: email,
|
|
123
157
|
external_identifier: identifier,
|
|
124
|
-
role:
|
|
158
|
+
role: role,
|
|
125
159
|
auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
|
126
160
|
};
|
|
127
161
|
const userId = await this.fetchUserId(identifier);
|
|
@@ -131,6 +165,10 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
131
165
|
let emitPayload = {
|
|
132
166
|
auth_data: userPayload.auth_data,
|
|
133
167
|
};
|
|
168
|
+
// Make sure a user's role gets updated if their oauth group or role mapping changes
|
|
169
|
+
if (this.config['roleMapping']) {
|
|
170
|
+
emitPayload['role'] = role;
|
|
171
|
+
}
|
|
134
172
|
if (syncUserInfo) {
|
|
135
173
|
emitPayload = {
|
|
136
174
|
...emitPayload,
|
|
@@ -38,7 +38,14 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
38
38
|
throw new InvalidProviderConfigError({ provider: additionalConfig['provider'] });
|
|
39
39
|
}
|
|
40
40
|
const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback');
|
|
41
|
-
|
|
41
|
+
// extract client overrides/options excluding CLIENT_ID and CLIENT_SECRET as they are passed directly
|
|
42
|
+
const clientOptionsOverrides = getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_CLIENT_`, {
|
|
43
|
+
omitKey: [
|
|
44
|
+
`AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`,
|
|
45
|
+
`AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`,
|
|
46
|
+
],
|
|
47
|
+
type: 'underscore',
|
|
48
|
+
});
|
|
42
49
|
this.redirectUrl = redirectUrl.toString();
|
|
43
50
|
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
|
44
51
|
this.config = additionalConfig;
|
|
@@ -87,7 +87,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
87
87
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
88
88
|
}
|
|
89
89
|
else {
|
|
90
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
90
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
91
91
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
92
92
|
}
|
|
93
93
|
try {
|
|
@@ -132,7 +132,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
132
132
|
await service.deleteMany(req.body.keys);
|
|
133
133
|
}
|
|
134
134
|
else {
|
|
135
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
135
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
136
136
|
await service.deleteByQuery(sanitizedQuery);
|
|
137
137
|
}
|
|
138
138
|
return next();
|
|
@@ -85,7 +85,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
85
85
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
86
86
|
}
|
|
87
87
|
else {
|
|
88
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
88
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
89
89
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
90
90
|
}
|
|
91
91
|
try {
|
|
@@ -130,7 +130,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
130
130
|
await service.deleteMany(req.body.keys);
|
|
131
131
|
}
|
|
132
132
|
else {
|
|
133
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
133
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
134
134
|
await service.deleteByQuery(sanitizedQuery);
|
|
135
135
|
}
|
|
136
136
|
return next();
|
|
@@ -79,7 +79,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
79
79
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
82
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
83
83
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
@@ -124,7 +124,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
124
124
|
await service.deleteMany(req.body.keys);
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
127
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
128
128
|
await service.deleteByQuery(sanitizedQuery);
|
|
129
129
|
}
|
|
130
130
|
return next();
|
|
@@ -220,7 +220,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
220
220
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
221
221
|
}
|
|
222
222
|
else {
|
|
223
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
223
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
224
224
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
225
225
|
}
|
|
226
226
|
try {
|
|
@@ -265,7 +265,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
265
265
|
await service.deleteMany(req.body.keys);
|
|
266
266
|
}
|
|
267
267
|
else {
|
|
268
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
268
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
269
269
|
await service.deleteByQuery(sanitizedQuery);
|
|
270
270
|
}
|
|
271
271
|
return next();
|
|
@@ -101,7 +101,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
101
101
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
104
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
104
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
105
105
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
106
106
|
}
|
|
107
107
|
try {
|
|
@@ -146,7 +146,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
146
146
|
await service.deleteMany(req.body.keys);
|
|
147
147
|
}
|
|
148
148
|
else {
|
|
149
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
149
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
150
150
|
await service.deleteByQuery(sanitizedQuery);
|
|
151
151
|
}
|
|
152
152
|
return next();
|
|
@@ -88,7 +88,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
88
88
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
91
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
92
92
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
@@ -133,7 +133,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
133
133
|
await service.deleteMany(req.body.keys);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
136
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
137
137
|
await service.deleteByQuery(sanitizedQuery);
|
|
138
138
|
}
|
|
139
139
|
return next();
|
|
@@ -111,7 +111,7 @@ router.patch('/:collection', collectionExists, validateBatch('update'), asyncHan
|
|
|
111
111
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
114
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
114
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
115
115
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
116
116
|
}
|
|
117
117
|
try {
|
|
@@ -163,7 +163,7 @@ router.delete('/:collection', collectionExists, validateBatch('delete'), asyncHa
|
|
|
163
163
|
await service.deleteMany(req.body.keys);
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
166
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
166
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
167
167
|
await service.deleteByQuery(sanitizedQuery);
|
|
168
168
|
}
|
|
169
169
|
return next();
|
|
@@ -88,7 +88,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
88
88
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
91
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
92
92
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
@@ -133,7 +133,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
133
133
|
await service.deleteMany(req.body.keys);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
136
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
137
137
|
await service.deleteByQuery(sanitizedQuery);
|
|
138
138
|
}
|
|
139
139
|
return next();
|
|
@@ -79,7 +79,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
79
79
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
82
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
83
83
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
@@ -124,7 +124,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
124
124
|
await service.deleteMany(req.body.keys);
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
127
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
128
128
|
await service.deleteByQuery(sanitizedQuery);
|
|
129
129
|
}
|
|
130
130
|
return next();
|
|
@@ -79,7 +79,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
79
79
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
80
80
|
}
|
|
81
81
|
else {
|
|
82
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
82
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
83
83
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
84
84
|
}
|
|
85
85
|
try {
|
|
@@ -124,7 +124,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
124
124
|
await service.deleteMany(req.body.keys);
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
127
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
128
128
|
await service.deleteByQuery(sanitizedQuery);
|
|
129
129
|
}
|
|
130
130
|
return next();
|
|
@@ -105,7 +105,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
105
105
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
108
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
109
109
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
110
110
|
}
|
|
111
111
|
try {
|
|
@@ -150,7 +150,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
150
150
|
await service.deleteMany(req.body.keys);
|
|
151
151
|
}
|
|
152
152
|
else {
|
|
153
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
153
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
154
154
|
await service.deleteByQuery(sanitizedQuery);
|
|
155
155
|
}
|
|
156
156
|
return next();
|
|
@@ -108,7 +108,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
108
108
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
111
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
111
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
112
112
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
113
113
|
}
|
|
114
114
|
try {
|
|
@@ -153,7 +153,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
153
153
|
await service.deleteMany(req.body.keys);
|
|
154
154
|
}
|
|
155
155
|
else {
|
|
156
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
156
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
157
157
|
await service.deleteByQuery(sanitizedQuery);
|
|
158
158
|
}
|
|
159
159
|
return next();
|
|
@@ -88,7 +88,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
88
88
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
91
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
92
92
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
@@ -133,7 +133,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
133
133
|
await service.deleteMany(req.body.keys);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
136
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
137
137
|
await service.deleteByQuery(sanitizedQuery);
|
|
138
138
|
}
|
|
139
139
|
return next();
|
|
@@ -100,7 +100,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
100
100
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
103
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
104
104
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
105
105
|
}
|
|
106
106
|
try {
|
|
@@ -145,7 +145,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
145
145
|
await service.deleteMany(req.body.keys);
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
148
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
149
149
|
await service.deleteByQuery(sanitizedQuery);
|
|
150
150
|
}
|
|
151
151
|
return next();
|
|
@@ -168,7 +168,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
168
168
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
169
169
|
}
|
|
170
170
|
else {
|
|
171
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
171
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
172
172
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
@@ -213,7 +213,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
213
213
|
await service.deleteMany(req.body.keys);
|
|
214
214
|
}
|
|
215
215
|
else {
|
|
216
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
216
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
217
217
|
await service.deleteByQuery(sanitizedQuery);
|
|
218
218
|
}
|
|
219
219
|
return next();
|
|
@@ -88,7 +88,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
88
88
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
91
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
92
92
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
93
93
|
}
|
|
94
94
|
try {
|
|
@@ -133,7 +133,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
133
133
|
await service.deleteMany(req.body.keys);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
136
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
137
137
|
await service.deleteByQuery(sanitizedQuery);
|
|
138
138
|
}
|
|
139
139
|
return next();
|
|
@@ -138,7 +138,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
138
138
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
141
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
142
142
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
143
143
|
}
|
|
144
144
|
try {
|
|
@@ -183,7 +183,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
183
183
|
await service.deleteMany(req.body.keys);
|
|
184
184
|
}
|
|
185
185
|
else {
|
|
186
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
186
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
187
187
|
await service.deleteByQuery(sanitizedQuery);
|
|
188
188
|
}
|
|
189
189
|
return next();
|
|
@@ -36,8 +36,13 @@ router.post('/hash/verify', asyncHandler(async (req, res) => {
|
|
|
36
36
|
if (!req.body?.hash) {
|
|
37
37
|
throw new InvalidPayloadError({ reason: `"hash" is required` });
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
try {
|
|
40
|
+
const result = await argon2.verify(req.body.hash, req.body.string);
|
|
41
|
+
return res.json({ data: result });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new InvalidPayloadError({ reason: `Invalid "hash" or "string"` });
|
|
45
|
+
}
|
|
41
46
|
}));
|
|
42
47
|
const SortSchema = Joi.object({
|
|
43
48
|
item: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
|
@@ -104,7 +109,7 @@ router.post('/export/:collection', collectionExists, asyncHandler(async (req, _r
|
|
|
104
109
|
accountability: req.accountability,
|
|
105
110
|
schema: req.schema,
|
|
106
111
|
});
|
|
107
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability ?? null);
|
|
112
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability ?? null);
|
|
108
113
|
// We're not awaiting this, as it's supposed to run async in the background
|
|
109
114
|
service.exportToFile(req.params['collection'], sanitizedQuery, req.body.format, {
|
|
110
115
|
file: req.body.file,
|
|
@@ -89,7 +89,7 @@ router.patch('/', validateBatch('update'), asyncHandler(async (req, res, next) =
|
|
|
89
89
|
keys = await service.updateMany(req.body.keys, req.body.data);
|
|
90
90
|
}
|
|
91
91
|
else {
|
|
92
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
92
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
93
93
|
keys = await service.updateByQuery(sanitizedQuery, req.body.data);
|
|
94
94
|
}
|
|
95
95
|
try {
|
|
@@ -134,7 +134,7 @@ router.delete('/', validateBatch('delete'), asyncHandler(async (req, _res, next)
|
|
|
134
134
|
await service.deleteMany(req.body.keys);
|
|
135
135
|
}
|
|
136
136
|
else {
|
|
137
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
137
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
138
138
|
await service.deleteByQuery(sanitizedQuery);
|
|
139
139
|
}
|
|
140
140
|
return next();
|
|
@@ -58,7 +58,7 @@ router.delete('/', asyncHandler(async (req, _res, next) => {
|
|
|
58
58
|
await service.deleteMany(req.body.keys);
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
|
-
const sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
61
|
+
const sanitizedQuery = await sanitizeQuery(req.body.query, req.schema, req.accountability);
|
|
62
62
|
await service.deleteByQuery(sanitizedQuery);
|
|
63
63
|
}
|
|
64
64
|
return next();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CapabilitiesHelper } from '../types.js';
|
|
2
|
+
export class CapabilitiesHelperMySQL extends CapabilitiesHelper {
|
|
3
|
+
supportsColumnPositionInGroupBy() {
|
|
4
|
+
// Supported in MySQL https://dev.mysql.com/doc/refman/8.4/en/select.html#id756773
|
|
5
|
+
// > Columns selected for output can be referred to in ORDER BY and GROUP BY clauses using column names,
|
|
6
|
+
// column aliases, or column positions. Column positions are integers and begin with 1:
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CapabilitiesHelper } from '../types.js';
|
|
2
|
+
export class CapabilitiesHelperPostgres extends CapabilitiesHelper {
|
|
3
|
+
supportsColumnPositionInGroupBy() {
|
|
4
|
+
// Supported in Postgres https://www.postgresql.org/docs/8.3/sql-select.html#SQL-GROUPBY
|
|
5
|
+
// Supported in CockroachDB (tested manually)
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
supportsDeduplicationOfParameters() {
|
|
9
|
+
// Postgres infers the type from the context in which the parameter is first referenced.
|
|
10
|
+
// This causes issues when the same parameter is used in different contexts with different types.
|
|
11
|
+
// See https://www.postgresql.org/docs/current/sql-prepare.html
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CapabilitiesHelperPostgres as postgres } from './dialects/postgres.js';
|
|
2
|
+
export { CapabilitiesHelperPostgres as redshift } from './dialects/postgres.js';
|
|
3
|
+
export { CapabilitiesHelperPostgres as cockroachdb } from './dialects/postgres.js';
|
|
4
|
+
export { CapabilitiesHelperDefault as oracle } from './dialects/default.js';
|
|
5
|
+
export { CapabilitiesHelperMySQL as mysql } from './dialects/mysql.js';
|
|
6
|
+
export { CapabilitiesHelperDefault as mssql } from './dialects/default.js';
|
|
7
|
+
export { CapabilitiesHelperDefault as sqlite } from './dialects/default.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CapabilitiesHelperPostgres as postgres } from './dialects/postgres.js';
|
|
2
|
+
export { CapabilitiesHelperPostgres as redshift } from './dialects/postgres.js';
|
|
3
|
+
export { CapabilitiesHelperPostgres as cockroachdb } from './dialects/postgres.js';
|
|
4
|
+
export { CapabilitiesHelperDefault as oracle } from './dialects/default.js';
|
|
5
|
+
export { CapabilitiesHelperMySQL as mysql } from './dialects/mysql.js';
|
|
6
|
+
export { CapabilitiesHelperDefault as mssql } from './dialects/default.js';
|
|
7
|
+
export { CapabilitiesHelperDefault as sqlite } from './dialects/default.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DatabaseHelper } from '../types.js';
|
|
2
|
+
export declare class CapabilitiesHelper extends DatabaseHelper {
|
|
3
|
+
supportsColumnPositionInGroupBy(): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Indicates if the values within the list of parameters can be safely deduplicated.
|
|
6
|
+
* This is useful for databases that do not automatically cast the value for cases when a parameter is referenced multiple times in the query,
|
|
7
|
+
* but the targeting type is different. For example when referencing a parameter which a UUID, postgres cannot use the same parameter reference
|
|
8
|
+
* to compare it against column of type UUID and at the same time against a column of type a string.
|
|
9
|
+
*/
|
|
10
|
+
supportsDeduplicationOfParameters(): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DatabaseHelper } from '../types.js';
|
|
2
|
+
export class CapabilitiesHelper extends DatabaseHelper {
|
|
3
|
+
supportsColumnPositionInGroupBy() {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Indicates if the values within the list of parameters can be safely deduplicated.
|
|
8
|
+
* This is useful for databases that do not automatically cast the value for cases when a parameter is referenced multiple times in the query,
|
|
9
|
+
* but the targeting type is different. For example when referencing a parameter which a UUID, postgres cannot use the same parameter reference
|
|
10
|
+
* to compare it against column of type UUID and at the same time against a column of type a string.
|
|
11
|
+
*/
|
|
12
|
+
supportsDeduplicationOfParameters() {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
|
+
import * as capabilitiesHelpers from './capabilities/index.js';
|
|
3
4
|
import * as dateHelpers from './date/index.js';
|
|
4
5
|
import * as fnHelpers from './fn/index.js';
|
|
5
6
|
import * as geometryHelpers from './geometry/index.js';
|
|
@@ -12,6 +13,7 @@ export declare function getHelpers(database: Knex): {
|
|
|
12
13
|
schema: schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.mysql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle | schemaHelpers.redshift;
|
|
13
14
|
sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
|
|
14
15
|
number: numberHelpers.cockroachdb | numberHelpers.mssql | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle;
|
|
16
|
+
capabilities: capabilitiesHelpers.postgres | capabilitiesHelpers.oracle | capabilitiesHelpers.mysql;
|
|
15
17
|
};
|
|
16
18
|
export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.postgres | fnHelpers.mssql | fnHelpers.mysql | fnHelpers.sqlite | fnHelpers.oracle;
|
|
17
19
|
export type Helpers = ReturnType<typeof getHelpers>;
|