@directus/api 11.1.0 → 12.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 -4
- package/dist/auth/auth.d.ts +4 -4
- package/dist/auth/auth.js +2 -2
- package/dist/auth/drivers/ldap.js +20 -17
- package/dist/auth/drivers/local.js +5 -5
- package/dist/auth/drivers/oauth2.js +16 -16
- package/dist/auth/drivers/openid.js +18 -17
- package/dist/auth/drivers/saml.js +6 -7
- package/dist/auth.js +3 -2
- package/dist/cache.js +3 -13
- package/dist/cli/utils/create-env/env-stub.liquid +5 -7
- package/dist/controllers/activity.js +7 -6
- package/dist/controllers/assets.js +25 -12
- package/dist/controllers/auth.js +8 -7
- package/dist/controllers/collections.js +4 -3
- package/dist/controllers/dashboards.js +5 -4
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +9 -8
- package/dist/controllers/files.js +11 -11
- package/dist/controllers/flows.js +5 -4
- package/dist/controllers/folders.js +5 -4
- package/dist/controllers/items.js +14 -13
- package/dist/controllers/not-found.js +2 -2
- package/dist/controllers/notifications.js +5 -4
- package/dist/controllers/operations.js +5 -4
- package/dist/controllers/panels.js +5 -4
- package/dist/controllers/permissions.js +5 -4
- package/dist/controllers/presets.js +5 -4
- package/dist/controllers/relations.js +6 -5
- package/dist/controllers/roles.js +5 -4
- package/dist/controllers/schema.js +8 -8
- package/dist/controllers/server.js +2 -2
- package/dist/controllers/settings.js +3 -2
- package/dist/controllers/shares.js +7 -6
- package/dist/controllers/translations.js +6 -5
- package/dist/controllers/users.js +22 -21
- package/dist/controllers/utils.js +10 -10
- package/dist/controllers/webhooks.js +5 -4
- package/dist/{exceptions/database → database/errors}/dialects/mssql.js +8 -18
- package/dist/{exceptions/database → database/errors}/dialects/mysql.js +9 -19
- package/dist/{exceptions/database → database/errors}/dialects/oracle.js +2 -2
- package/dist/{exceptions/database → database/errors}/dialects/postgres.js +7 -18
- package/dist/{exceptions/database → database/errors}/dialects/sqlite.js +7 -10
- package/dist/{exceptions/database → database/errors}/translate.js +1 -1
- package/dist/database/migrations/run.js +10 -1
- package/dist/env.js +6 -13
- package/dist/errors/codes.d.ts +29 -0
- package/dist/errors/codes.js +30 -0
- package/dist/errors/contains-null-values.d.ts +7 -0
- package/dist/errors/contains-null-values.js +4 -0
- package/dist/errors/content-too-large.d.ts +1 -0
- package/dist/errors/content-too-large.js +3 -0
- package/dist/errors/forbidden.d.ts +1 -0
- package/dist/errors/forbidden.js +3 -0
- package/dist/errors/hit-rate-limit.d.ts +6 -0
- package/dist/errors/hit-rate-limit.js +8 -0
- package/dist/errors/illegal-asset-transformation.d.ts +4 -0
- package/dist/errors/illegal-asset-transformation.js +3 -0
- package/dist/errors/index.d.ts +28 -0
- package/dist/errors/index.js +28 -0
- package/dist/errors/invalid-credentials.d.ts +1 -0
- package/dist/errors/invalid-credentials.js +3 -0
- package/dist/errors/invalid-foreign-key.d.ts +6 -0
- package/dist/errors/invalid-foreign-key.js +14 -0
- package/dist/errors/invalid-ip.d.ts +1 -0
- package/dist/errors/invalid-ip.js +3 -0
- package/dist/errors/invalid-otp.d.ts +1 -0
- package/dist/errors/invalid-otp.js +3 -0
- package/dist/errors/invalid-payload.d.ts +5 -0
- package/dist/errors/invalid-payload.js +4 -0
- package/dist/errors/invalid-provider-config.d.ts +5 -0
- package/dist/errors/invalid-provider-config.js +3 -0
- package/dist/errors/invalid-provider.d.ts +1 -0
- package/dist/errors/invalid-provider.js +3 -0
- package/dist/errors/invalid-query.d.ts +5 -0
- package/dist/errors/invalid-query.js +4 -0
- package/dist/errors/invalid-token.d.ts +1 -0
- package/dist/errors/invalid-token.js +3 -0
- package/dist/errors/method-not-allowed.d.ts +6 -0
- package/dist/errors/method-not-allowed.js +6 -0
- package/dist/errors/not-null-violation.d.ts +6 -0
- package/dist/errors/not-null-violation.js +14 -0
- package/dist/errors/range-not-satisfiable.d.ts +7 -0
- package/dist/errors/range-not-satisfiable.js +7 -0
- package/dist/errors/record-not-unique.d.ts +6 -0
- package/dist/errors/record-not-unique.js +14 -0
- package/dist/errors/route-not-found.d.ts +5 -0
- package/dist/errors/route-not-found.js +4 -0
- package/dist/errors/service-unavailable.d.ts +7 -0
- package/dist/errors/service-unavailable.js +4 -0
- package/dist/errors/token-expired.d.ts +1 -0
- package/dist/errors/token-expired.js +3 -0
- package/dist/errors/unexpected-response.d.ts +1 -0
- package/dist/errors/unexpected-response.js +3 -0
- package/dist/errors/unprocessable-content.d.ts +5 -0
- package/dist/errors/unprocessable-content.js +4 -0
- package/dist/errors/unsupported-media-type.d.ts +6 -0
- package/dist/errors/unsupported-media-type.js +4 -0
- package/dist/errors/user-suspended.d.ts +1 -0
- package/dist/errors/user-suspended.js +3 -0
- package/dist/errors/value-out-of-range.d.ts +6 -0
- package/dist/errors/value-out-of-range.js +14 -0
- package/dist/errors/value-too-long.d.ts +6 -0
- package/dist/errors/value-too-long.js +14 -0
- package/dist/extensions.js +0 -4
- package/dist/flows.js +6 -8
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/messenger.js +4 -4
- package/dist/middleware/authenticate.js +1 -1
- package/dist/middleware/check-ip.js +2 -2
- package/dist/middleware/collection-exists.js +2 -2
- package/dist/middleware/error-handler.js +7 -7
- package/dist/middleware/graphql.js +11 -9
- package/dist/middleware/rate-limiter-global.d.ts +2 -2
- package/dist/middleware/rate-limiter-global.js +2 -3
- package/dist/middleware/rate-limiter-ip.d.ts +2 -2
- package/dist/middleware/rate-limiter-ip.js +2 -3
- package/dist/middleware/validate-batch.js +3 -4
- package/dist/rate-limiter.js +2 -9
- package/dist/services/activity.js +3 -2
- package/dist/services/assets.js +9 -10
- package/dist/services/authentication.js +12 -11
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +16 -16
- package/dist/services/collections.js +17 -16
- package/dist/services/fields.js +16 -14
- package/dist/services/files.js +7 -6
- package/dist/services/graphql/errors/execution.d.ts +6 -0
- package/dist/services/graphql/errors/execution.js +2 -0
- package/dist/services/graphql/errors/index.d.ts +2 -0
- package/dist/services/graphql/errors/index.js +2 -0
- package/dist/services/graphql/errors/validation.d.ts +6 -0
- package/dist/services/graphql/errors/validation.js +2 -0
- package/dist/services/graphql/index.d.ts +2 -2
- package/dist/services/graphql/index.js +30 -12
- package/dist/services/graphql/utils/process-error.js +3 -3
- package/dist/services/import-export.js +7 -7
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.js +14 -13
- package/dist/services/mail/index.js +3 -3
- package/dist/services/meta.js +3 -3
- package/dist/services/payload.js +11 -7
- package/dist/services/relations.js +32 -22
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +10 -9
- package/dist/services/schema.js +5 -5
- package/dist/services/shares.js +4 -4
- package/dist/services/tfa.js +6 -6
- package/dist/services/translations.d.ts +2 -2
- package/dist/services/translations.js +4 -4
- package/dist/services/users.js +26 -29
- package/dist/services/utils.js +4 -4
- package/dist/synchronization.js +3 -3
- package/dist/types/items.d.ts +2 -2
- package/dist/utils/apply-diff.js +2 -2
- package/dist/utils/apply-query.js +17 -7
- package/dist/utils/get-accountability-for-role.js +1 -2
- package/dist/utils/get-accountability-for-token.js +3 -3
- package/dist/utils/get-column-path.js +5 -3
- package/dist/utils/get-column.js +3 -3
- package/dist/utils/get-service.d.ts +1 -1
- package/dist/utils/get-service.js +1 -1
- package/dist/utils/jwt.js +5 -5
- package/dist/utils/validate-diff.js +23 -9
- package/dist/utils/validate-keys.js +3 -3
- package/dist/utils/validate-query.d.ts +2 -0
- package/dist/utils/validate-query.js +27 -21
- package/dist/utils/validate-snapshot.js +11 -5
- package/dist/websocket/authenticate.js +12 -15
- package/dist/websocket/controllers/base.js +18 -15
- package/dist/websocket/controllers/graphql.js +2 -2
- package/dist/websocket/controllers/index.js +3 -7
- package/dist/websocket/controllers/rest.js +3 -3
- package/dist/websocket/{exceptions.d.ts → errors.d.ts} +5 -5
- package/dist/websocket/{exceptions.js → errors.js} +10 -10
- package/dist/websocket/handlers/items.js +5 -5
- package/dist/websocket/handlers/subscribe.js +8 -8
- package/package.json +15 -15
- package/dist/exceptions/content-too-large.d.ts +0 -4
- package/dist/exceptions/content-too-large.js +0 -6
- package/dist/exceptions/database/contains-null-values.d.ts +0 -9
- package/dist/exceptions/database/contains-null-values.js +0 -6
- package/dist/exceptions/database/invalid-foreign-key.d.ts +0 -10
- package/dist/exceptions/database/invalid-foreign-key.js +0 -11
- package/dist/exceptions/database/not-null-violation.d.ts +0 -9
- package/dist/exceptions/database/not-null-violation.js +0 -6
- package/dist/exceptions/database/record-not-unique.d.ts +0 -10
- package/dist/exceptions/database/record-not-unique.js +0 -11
- package/dist/exceptions/database/value-out-of-range.d.ts +0 -10
- package/dist/exceptions/database/value-out-of-range.js +0 -11
- package/dist/exceptions/database/value-too-long.d.ts +0 -9
- package/dist/exceptions/database/value-too-long.js +0 -11
- package/dist/exceptions/forbidden.d.ts +0 -6
- package/dist/exceptions/forbidden.js +0 -13
- package/dist/exceptions/graphql-validation.d.ts +0 -4
- package/dist/exceptions/graphql-validation.js +0 -6
- package/dist/exceptions/hit-rate-limit.d.ts +0 -9
- package/dist/exceptions/hit-rate-limit.js +0 -6
- package/dist/exceptions/illegal-asset-transformation.d.ts +0 -4
- package/dist/exceptions/illegal-asset-transformation.js +0 -6
- package/dist/exceptions/index.d.ts +0 -21
- package/dist/exceptions/index.js +0 -21
- package/dist/exceptions/invalid-config.d.ts +0 -4
- package/dist/exceptions/invalid-config.js +0 -6
- package/dist/exceptions/invalid-credentials.d.ts +0 -4
- package/dist/exceptions/invalid-credentials.js +0 -6
- package/dist/exceptions/invalid-ip.d.ts +0 -4
- package/dist/exceptions/invalid-ip.js +0 -6
- package/dist/exceptions/invalid-otp.d.ts +0 -4
- package/dist/exceptions/invalid-otp.js +0 -6
- package/dist/exceptions/invalid-payload.d.ts +0 -4
- package/dist/exceptions/invalid-payload.js +0 -6
- package/dist/exceptions/invalid-provider.d.ts +0 -4
- package/dist/exceptions/invalid-provider.js +0 -6
- package/dist/exceptions/invalid-query.d.ts +0 -4
- package/dist/exceptions/invalid-query.js +0 -6
- package/dist/exceptions/invalid-token.d.ts +0 -4
- package/dist/exceptions/invalid-token.js +0 -6
- package/dist/exceptions/method-not-allowed.d.ts +0 -8
- package/dist/exceptions/method-not-allowed.js +0 -6
- package/dist/exceptions/range-not-satisfiable.d.ts +0 -5
- package/dist/exceptions/range-not-satisfiable.js +0 -9
- package/dist/exceptions/route-not-found.d.ts +0 -4
- package/dist/exceptions/route-not-found.js +0 -6
- package/dist/exceptions/service-unavailable.d.ts +0 -9
- package/dist/exceptions/service-unavailable.js +0 -6
- package/dist/exceptions/token-expired.d.ts +0 -4
- package/dist/exceptions/token-expired.js +0 -6
- package/dist/exceptions/unexpected-response.d.ts +0 -4
- package/dist/exceptions/unexpected-response.js +0 -6
- package/dist/exceptions/unprocessable-entity.d.ts +0 -4
- package/dist/exceptions/unprocessable-entity.js +0 -6
- package/dist/exceptions/unsupported-media-type.d.ts +0 -4
- package/dist/exceptions/unsupported-media-type.js +0 -6
- package/dist/exceptions/user-suspended.d.ts +0 -4
- package/dist/exceptions/user-suspended.js +0 -6
- /package/dist/{exceptions/database → database/errors}/dialects/mssql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/mysql.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/oracle.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/postgres.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/sqlite.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.d.ts +0 -0
- /package/dist/{exceptions/database → database/errors}/dialects/types.js +0 -0
- /package/dist/{exceptions/database → database/errors}/translate.d.ts +0 -0
|
@@ -5,13 +5,13 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
5
5
|
import { fromZodError } from 'zod-validation-error';
|
|
6
6
|
import emitter from '../../emitter.js';
|
|
7
7
|
import env from '../../env.js';
|
|
8
|
-
import {
|
|
8
|
+
import { InvalidProviderConfigError, TokenExpiredError } from '../../errors/index.js';
|
|
9
9
|
import logger from '../../logger.js';
|
|
10
10
|
import { createRateLimiter } from '../../rate-limiter.js';
|
|
11
11
|
import { getAccountabilityForToken } from '../../utils/get-accountability-for-token.js';
|
|
12
12
|
import { toBoolean } from '../../utils/to-boolean.js';
|
|
13
13
|
import { authenticateConnection, authenticationSuccess } from '../authenticate.js';
|
|
14
|
-
import {
|
|
14
|
+
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
15
15
|
import { AuthMode, WebSocketAuthMessage, WebSocketMessage } from '../messages.js';
|
|
16
16
|
import { getExpiresAtForToken } from '../utils/get-expires-at-for-token.js';
|
|
17
17
|
import { getMessageType } from '../utils/message.js';
|
|
@@ -45,7 +45,10 @@ export default class SocketController {
|
|
|
45
45
|
const authTimeout = Number(env[`${configPrefix}_AUTH_TIMEOUT`]) * 1000;
|
|
46
46
|
const maxConnections = `${configPrefix}_CONN_LIMIT` in env ? Number(env[`${configPrefix}_CONN_LIMIT`]) : Number.POSITIVE_INFINITY;
|
|
47
47
|
if (!authMode.success) {
|
|
48
|
-
throw new
|
|
48
|
+
throw new InvalidProviderConfigError({
|
|
49
|
+
provider: 'ws',
|
|
50
|
+
reason: fromZodError(authMode.error, { prefix: `${configPrefix}_AUTH` }).message,
|
|
51
|
+
});
|
|
49
52
|
}
|
|
50
53
|
return {
|
|
51
54
|
endpoint,
|
|
@@ -122,8 +125,8 @@ export default class SocketController {
|
|
|
122
125
|
}
|
|
123
126
|
catch {
|
|
124
127
|
logger.debug('WebSocket authentication handshake failed');
|
|
125
|
-
const error = new
|
|
126
|
-
|
|
128
|
+
const error = new WebSocketError('auth', 'AUTH_FAILED', 'Authentication handshake failed.');
|
|
129
|
+
handleWebSocketError(ws, error, 'auth');
|
|
127
130
|
ws.close();
|
|
128
131
|
}
|
|
129
132
|
});
|
|
@@ -141,8 +144,8 @@ export default class SocketController {
|
|
|
141
144
|
}
|
|
142
145
|
catch (limit) {
|
|
143
146
|
const timeout = limit?.msBeforeNext ?? this.rateLimiter.msDuration;
|
|
144
|
-
const error = new
|
|
145
|
-
|
|
147
|
+
const error = new WebSocketError('server', 'REQUESTS_EXCEEDED', `Too many messages, retry after ${timeout}ms.`);
|
|
148
|
+
handleWebSocketError(client, error, 'server');
|
|
146
149
|
logger.debug(`WebSocket#${client.uid} is rate limited`);
|
|
147
150
|
return;
|
|
148
151
|
}
|
|
@@ -152,7 +155,7 @@ export default class SocketController {
|
|
|
152
155
|
message = this.parseMessage(data.toString());
|
|
153
156
|
}
|
|
154
157
|
catch (err) {
|
|
155
|
-
|
|
158
|
+
handleWebSocketError(client, err, 'server');
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
158
161
|
if (getMessageType(message) === 'auth') {
|
|
@@ -198,7 +201,7 @@ export default class SocketController {
|
|
|
198
201
|
message = WebSocketMessage.parse(parseJSON(data));
|
|
199
202
|
}
|
|
200
203
|
catch (err) {
|
|
201
|
-
throw new
|
|
204
|
+
throw new WebSocketError('server', 'INVALID_PAYLOAD', 'Unable to parse the incoming message.');
|
|
202
205
|
}
|
|
203
206
|
return message;
|
|
204
207
|
}
|
|
@@ -217,10 +220,10 @@ export default class SocketController {
|
|
|
217
220
|
emitter.emitAction('websocket.auth.failure', { client });
|
|
218
221
|
client.accountability = null;
|
|
219
222
|
client.expires_at = null;
|
|
220
|
-
const _error = error instanceof
|
|
223
|
+
const _error = error instanceof WebSocketError
|
|
221
224
|
? error
|
|
222
|
-
: new
|
|
223
|
-
|
|
225
|
+
: new WebSocketError('auth', 'AUTH_FAILED', 'Authentication failed.', message.uid);
|
|
226
|
+
handleWebSocketError(client, _error, 'auth');
|
|
224
227
|
if (this.authentication.mode !== 'public') {
|
|
225
228
|
client.close();
|
|
226
229
|
}
|
|
@@ -240,10 +243,10 @@ export default class SocketController {
|
|
|
240
243
|
client.auth_timer = setTimeout(() => {
|
|
241
244
|
client.accountability = null;
|
|
242
245
|
client.expires_at = null;
|
|
243
|
-
|
|
246
|
+
handleWebSocketError(client, new TokenExpiredError(), 'auth');
|
|
244
247
|
waitForMessageType(client, 'auth', this.authentication.timeout).catch((msg) => {
|
|
245
|
-
const error = new
|
|
246
|
-
|
|
248
|
+
const error = new WebSocketError('auth', 'AUTH_TIMEOUT', 'Authentication timed out.', msg?.uid);
|
|
249
|
+
handleWebSocketError(client, error, 'auth');
|
|
247
250
|
if (this.authentication.mode !== 'public') {
|
|
248
251
|
client.close();
|
|
249
252
|
}
|
|
@@ -5,7 +5,7 @@ import { bindPubSub } from '../../services/graphql/subscription.js';
|
|
|
5
5
|
import { GraphQLService } from '../../services/index.js';
|
|
6
6
|
import { getSchema } from '../../utils/get-schema.js';
|
|
7
7
|
import { authenticateConnection, refreshAccountability } from '../authenticate.js';
|
|
8
|
-
import {
|
|
8
|
+
import { handleWebSocketError } from '../errors.js';
|
|
9
9
|
import { ConnectionParams, WebSocketMessage } from '../messages.js';
|
|
10
10
|
import { getMessageType } from '../utils/message.js';
|
|
11
11
|
import SocketController from './base.js';
|
|
@@ -68,7 +68,7 @@ export class GraphQLSubscriptionController extends SocketController {
|
|
|
68
68
|
await cb(JSON.stringify(message));
|
|
69
69
|
}
|
|
70
70
|
catch (error) {
|
|
71
|
-
|
|
71
|
+
handleWebSocketError(client, error, MessageType.Error);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import env from '../../env.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ServiceUnavailableError } from '../../errors/index.js';
|
|
3
3
|
import { toBoolean } from '../../utils/to-boolean.js';
|
|
4
4
|
import { GraphQLSubscriptionController } from './graphql.js';
|
|
5
5
|
import { WebSocketController } from './rest.js';
|
|
@@ -12,14 +12,10 @@ export function createWebSocketController(server) {
|
|
|
12
12
|
}
|
|
13
13
|
export function getWebSocketController() {
|
|
14
14
|
if (!toBoolean(env['WEBSOCKETS_ENABLED']) || !toBoolean(env['WEBSOCKETS_REST_ENABLED'])) {
|
|
15
|
-
throw new
|
|
16
|
-
service: 'get-websocket-controller',
|
|
17
|
-
});
|
|
15
|
+
throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is disabled' });
|
|
18
16
|
}
|
|
19
17
|
if (!websocketController) {
|
|
20
|
-
throw new
|
|
21
|
-
service: 'get-websocket-controller',
|
|
22
|
-
});
|
|
18
|
+
throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is not initialized' });
|
|
23
19
|
}
|
|
24
20
|
return websocketController;
|
|
25
21
|
}
|
|
@@ -3,7 +3,7 @@ import emitter from '../../emitter.js';
|
|
|
3
3
|
import env from '../../env.js';
|
|
4
4
|
import logger from '../../logger.js';
|
|
5
5
|
import { refreshAccountability } from '../authenticate.js';
|
|
6
|
-
import {
|
|
6
|
+
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
7
7
|
import { WebSocketMessage } from '../messages.js';
|
|
8
8
|
import SocketController from './base.js';
|
|
9
9
|
export class WebSocketController extends SocketController {
|
|
@@ -22,7 +22,7 @@ export class WebSocketController extends SocketController {
|
|
|
22
22
|
emitter.emitAction('websocket.message', { message, client });
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
25
|
-
|
|
25
|
+
handleWebSocketError(client, error, 'server');
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
});
|
|
@@ -40,7 +40,7 @@ export class WebSocketController extends SocketController {
|
|
|
40
40
|
message = parseJSON(data);
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
throw new
|
|
43
|
+
throw new WebSocketError('server', 'INVALID_PAYLOAD', 'Unable to parse the incoming message.');
|
|
44
44
|
}
|
|
45
45
|
return message;
|
|
46
46
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DirectusError } from '@directus/errors';
|
|
2
2
|
import type { WebSocket } from 'ws';
|
|
3
3
|
import { ZodError } from 'zod';
|
|
4
4
|
import type { WebSocketResponse } from './messages.js';
|
|
5
5
|
import type { WebSocketClient } from './types.js';
|
|
6
|
-
export declare class
|
|
6
|
+
export declare class WebSocketError extends Error {
|
|
7
7
|
type: string;
|
|
8
8
|
code: string;
|
|
9
9
|
uid: string | number | undefined;
|
|
10
10
|
constructor(type: string, code: string, message: string, uid?: string | number);
|
|
11
11
|
toJSON(): WebSocketResponse;
|
|
12
12
|
toMessage(): string;
|
|
13
|
-
static
|
|
14
|
-
static fromZodError(error: ZodError, type?: string):
|
|
13
|
+
static fromError(error: DirectusError<unknown>, type?: string): WebSocketError;
|
|
14
|
+
static fromZodError(error: ZodError, type?: string): WebSocketError;
|
|
15
15
|
}
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function handleWebSocketError(client: WebSocketClient | WebSocket, error: unknown, type?: string): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDirectusError } from '@directus/errors';
|
|
2
2
|
import { ZodError } from 'zod';
|
|
3
3
|
import { fromZodError } from 'zod-validation-error';
|
|
4
4
|
import logger from '../logger.js';
|
|
5
|
-
export class
|
|
5
|
+
export class WebSocketError extends Error {
|
|
6
6
|
type;
|
|
7
7
|
code;
|
|
8
8
|
uid;
|
|
@@ -29,25 +29,25 @@ export class WebSocketException extends Error {
|
|
|
29
29
|
toMessage() {
|
|
30
30
|
return JSON.stringify(this.toJSON());
|
|
31
31
|
}
|
|
32
|
-
static
|
|
33
|
-
return new
|
|
32
|
+
static fromError(error, type = 'unknown') {
|
|
33
|
+
return new WebSocketError(type, error.code, error.message);
|
|
34
34
|
}
|
|
35
35
|
static fromZodError(error, type = 'unknown') {
|
|
36
36
|
const zError = fromZodError(error);
|
|
37
|
-
return new
|
|
37
|
+
return new WebSocketError(type, 'INVALID_PAYLOAD', zError.message);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
export function
|
|
41
|
-
if (error
|
|
42
|
-
client.send(
|
|
40
|
+
export function handleWebSocketError(client, error, type) {
|
|
41
|
+
if (isDirectusError(error)) {
|
|
42
|
+
client.send(WebSocketError.fromError(error, type).toMessage());
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
if (error instanceof
|
|
45
|
+
if (error instanceof WebSocketError) {
|
|
46
46
|
client.send(error.toMessage());
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
if (error instanceof ZodError) {
|
|
50
|
-
client.send(
|
|
50
|
+
client.send(WebSocketError.fromZodError(error, type).toMessage());
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
// unhandled exceptions
|
|
@@ -2,7 +2,7 @@ import emitter from '../../emitter.js';
|
|
|
2
2
|
import { ItemsService, MetaService } from '../../services/index.js';
|
|
3
3
|
import { getSchema } from '../../utils/get-schema.js';
|
|
4
4
|
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
5
|
-
import {
|
|
5
|
+
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
6
6
|
import { WebSocketItemsMessage } from '../messages.js';
|
|
7
7
|
import { fmtMessage, getMessageType } from '../utils/message.js';
|
|
8
8
|
export class ItemsHandler {
|
|
@@ -14,11 +14,11 @@ export class ItemsHandler {
|
|
|
14
14
|
const parsedMessage = WebSocketItemsMessage.parse(message);
|
|
15
15
|
this.onMessage(client, parsedMessage).catch((err) => {
|
|
16
16
|
// this catch is required because the async onMessage function is not awaited
|
|
17
|
-
|
|
17
|
+
handleWebSocketError(client, err, 'items');
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
catch (err) {
|
|
21
|
-
|
|
21
|
+
handleWebSocketError(client, err, 'items');
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
}
|
|
@@ -27,7 +27,7 @@ export class ItemsHandler {
|
|
|
27
27
|
const accountability = client.accountability;
|
|
28
28
|
const schema = await getSchema();
|
|
29
29
|
if (!schema.collections[message.collection] || message.collection.startsWith('directus_')) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new WebSocketError('items', 'INVALID_COLLECTION', 'The provided collection does not exists or is not accessible.', uid);
|
|
31
31
|
}
|
|
32
32
|
const isSingleton = !!schema.collections[message.collection]?.singleton;
|
|
33
33
|
const service = new ItemsService(message.collection, { schema, accountability });
|
|
@@ -95,7 +95,7 @@ export class ItemsHandler {
|
|
|
95
95
|
result = await service.deleteByQuery(query);
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
|
-
throw new
|
|
98
|
+
throw new WebSocketError('items', 'INVALID_PAYLOAD', "Either 'ids', 'id' or 'query' is required for a DELETE request.", uid);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
client.send(fmtMessage('items', { data: result, ...(meta ? { meta } : {}) }, uid));
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import emitter from '../../emitter.js';
|
|
2
|
-
import {
|
|
2
|
+
import { InvalidPayloadError } from '../../errors/index.js';
|
|
3
3
|
import { getMessenger } from '../../messenger.js';
|
|
4
4
|
import { CollectionsService, FieldsService, MetaService } from '../../services/index.js';
|
|
5
5
|
import { getSchema } from '../../utils/get-schema.js';
|
|
6
6
|
import { getService } from '../../utils/get-service.js';
|
|
7
7
|
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
8
8
|
import { refreshAccountability } from '../authenticate.js';
|
|
9
|
-
import {
|
|
9
|
+
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
10
10
|
import { WebSocketSubscribeMessage } from '../messages.js';
|
|
11
11
|
import { fmtMessage, getMessageType } from '../utils/message.js';
|
|
12
12
|
/**
|
|
@@ -46,7 +46,7 @@ export class SubscribeHandler {
|
|
|
46
46
|
this.onMessage(client, WebSocketSubscribeMessage.parse(message));
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
|
-
|
|
49
|
+
handleWebSocketError(client, error, 'subscribe');
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
// unsubscribe when a connection drops
|
|
@@ -60,7 +60,7 @@ export class SubscribeHandler {
|
|
|
60
60
|
subscribe(subscription) {
|
|
61
61
|
const { collection } = subscription;
|
|
62
62
|
if ('item' in subscription && ['directus_fields', 'directus_relations'].includes(collection)) {
|
|
63
|
-
throw new
|
|
63
|
+
throw new InvalidPayloadError({ reason: `Cannot subscribe to a specific item in the ${collection} collection.` });
|
|
64
64
|
}
|
|
65
65
|
if (!this.subscriptions[collection]) {
|
|
66
66
|
this.subscriptions[collection] = new Set();
|
|
@@ -115,7 +115,7 @@ export class SubscribeHandler {
|
|
|
115
115
|
client.send(fmtMessage('subscription', result, subscription.uid));
|
|
116
116
|
}
|
|
117
117
|
catch (err) {
|
|
118
|
-
|
|
118
|
+
handleWebSocketError(client, err, 'subscribe');
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -129,7 +129,7 @@ export class SubscribeHandler {
|
|
|
129
129
|
const accountability = client.accountability;
|
|
130
130
|
const schema = await getSchema();
|
|
131
131
|
if (!accountability?.admin && !schema.collections[collection]) {
|
|
132
|
-
throw new
|
|
132
|
+
throw new WebSocketError('subscribe', 'INVALID_COLLECTION', 'The provided collection does not exists or is not accessible.', message.uid);
|
|
133
133
|
}
|
|
134
134
|
const subscription = {
|
|
135
135
|
client,
|
|
@@ -164,7 +164,7 @@ export class SubscribeHandler {
|
|
|
164
164
|
client.send(fmtMessage('subscription', data, subscription.uid));
|
|
165
165
|
}
|
|
166
166
|
catch (err) {
|
|
167
|
-
|
|
167
|
+
handleWebSocketError(client, err, 'subscribe');
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
if (getMessageType(message) === 'unsubscribe') {
|
|
@@ -173,7 +173,7 @@ export class SubscribeHandler {
|
|
|
173
173
|
client.send(fmtMessage('subscription', { event: 'unsubscribe' }, message.uid));
|
|
174
174
|
}
|
|
175
175
|
catch (err) {
|
|
176
|
-
|
|
176
|
+
handleWebSocketError(client, err, 'unsubscribe');
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -143,20 +143,21 @@
|
|
|
143
143
|
"ws": "8.12.1",
|
|
144
144
|
"zod": "3.21.4",
|
|
145
145
|
"zod-validation-error": "1.0.1",
|
|
146
|
-
"@directus/app": "10.
|
|
146
|
+
"@directus/app": "10.4.0",
|
|
147
147
|
"@directus/constants": "10.2.1",
|
|
148
|
-
"@directus/
|
|
149
|
-
"@directus/extensions-sdk": "10.1.
|
|
150
|
-
"@directus/pressure": "1.0.
|
|
148
|
+
"@directus/errors": "0.0.1",
|
|
149
|
+
"@directus/extensions-sdk": "10.1.5",
|
|
150
|
+
"@directus/pressure": "1.0.5",
|
|
151
151
|
"@directus/schema": "10.0.1",
|
|
152
152
|
"@directus/specs": "10.1.1",
|
|
153
|
-
"@directus/storage": "10.0.
|
|
154
|
-
"@directus/storage-driver-azure": "10.0.
|
|
155
|
-
"@directus/storage-driver-cloudinary": "10.0.
|
|
156
|
-
"@directus/storage-driver-gcs": "10.0.
|
|
157
|
-
"@directus/storage-driver-local": "10.0.
|
|
158
|
-
"@directus/storage-driver-s3": "10.0.
|
|
159
|
-
"@directus/utils": "10.0.
|
|
153
|
+
"@directus/storage": "10.0.4",
|
|
154
|
+
"@directus/storage-driver-azure": "10.0.6",
|
|
155
|
+
"@directus/storage-driver-cloudinary": "10.0.6",
|
|
156
|
+
"@directus/storage-driver-gcs": "10.0.6",
|
|
157
|
+
"@directus/storage-driver-local": "10.0.6",
|
|
158
|
+
"@directus/storage-driver-s3": "10.0.6",
|
|
159
|
+
"@directus/utils": "10.0.6",
|
|
160
|
+
"@directus/validation": "0.0.1"
|
|
160
161
|
},
|
|
161
162
|
"devDependencies": {
|
|
162
163
|
"@ngneat/falso": "6.4.0",
|
|
@@ -203,13 +204,12 @@
|
|
|
203
204
|
"supertest": "6.3.3",
|
|
204
205
|
"typescript": "5.0.4",
|
|
205
206
|
"vitest": "0.31.1",
|
|
207
|
+
"@directus/random": "0.2.1",
|
|
206
208
|
"@directus/tsconfig": "0.0.7",
|
|
207
|
-
"@directus/types": "10.1.
|
|
209
|
+
"@directus/types": "10.1.2"
|
|
208
210
|
},
|
|
209
211
|
"optionalDependencies": {
|
|
210
212
|
"@keyv/redis": "2.5.8",
|
|
211
|
-
"keyv-memcache": "1.3.3",
|
|
212
|
-
"memcached": "2.2.2",
|
|
213
213
|
"mysql": "2.18.1",
|
|
214
214
|
"nodemailer-mailgun-transport": "2.1.5",
|
|
215
215
|
"nodemailer-sendgrid": "1.0.3",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Exceptions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string;
|
|
5
|
-
};
|
|
6
|
-
export declare class ContainsNullValuesException extends BaseException {
|
|
7
|
-
constructor(field: string, exceptions?: Exceptions);
|
|
8
|
-
}
|
|
9
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Extensions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string;
|
|
5
|
-
invalid?: string;
|
|
6
|
-
};
|
|
7
|
-
export declare class InvalidForeignKeyException extends BaseException {
|
|
8
|
-
constructor(field: string | null, extensions?: Extensions);
|
|
9
|
-
}
|
|
10
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
export class InvalidForeignKeyException extends BaseException {
|
|
3
|
-
constructor(field, extensions) {
|
|
4
|
-
if (field) {
|
|
5
|
-
super(`Invalid foreign key in field "${field}".`, 400, 'INVALID_FOREIGN_KEY', extensions);
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
super(`Invalid foreign key.`, 400, 'INVALID_FOREIGN_KEY', extensions);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Exceptions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string;
|
|
5
|
-
};
|
|
6
|
-
export declare class NotNullViolationException extends BaseException {
|
|
7
|
-
constructor(field: string, exceptions?: Exceptions);
|
|
8
|
-
}
|
|
9
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Extensions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string | null;
|
|
5
|
-
invalid?: string | undefined;
|
|
6
|
-
};
|
|
7
|
-
export declare class RecordNotUniqueException extends BaseException {
|
|
8
|
-
constructor(field: string | null, extensions?: Extensions);
|
|
9
|
-
}
|
|
10
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
export class RecordNotUniqueException extends BaseException {
|
|
3
|
-
constructor(field, extensions) {
|
|
4
|
-
if (field) {
|
|
5
|
-
super(`Field "${field}" has to be unique.`, 400, 'RECORD_NOT_UNIQUE', extensions);
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
super(`Field has to be unique.`, 400, 'RECORD_NOT_UNIQUE', extensions);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Exceptions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string | null;
|
|
5
|
-
invalid?: string;
|
|
6
|
-
};
|
|
7
|
-
export declare class ValueOutOfRangeException extends BaseException {
|
|
8
|
-
constructor(field: string | null, exceptions?: Exceptions);
|
|
9
|
-
}
|
|
10
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
export class ValueOutOfRangeException extends BaseException {
|
|
3
|
-
constructor(field, exceptions) {
|
|
4
|
-
if (field) {
|
|
5
|
-
super(`Numeric value in field "${field ?? ''}" is out of range.`, 400, 'VALUE_OUT_OF_RANGE', exceptions);
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
super(`Numeric value is out of range.`, 400, 'VALUE_OUT_OF_RANGE', exceptions);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
type Extensions = {
|
|
3
|
-
collection: string;
|
|
4
|
-
field: string | null;
|
|
5
|
-
};
|
|
6
|
-
export declare class ValueTooLongException extends BaseException {
|
|
7
|
-
constructor(field: string | null, extensions?: Extensions);
|
|
8
|
-
}
|
|
9
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { BaseException } from '@directus/exceptions';
|
|
2
|
-
export class ValueTooLongException extends BaseException {
|
|
3
|
-
constructor(field, extensions) {
|
|
4
|
-
if (field) {
|
|
5
|
-
super(`Value for field "${field}" is too long.`, 400, 'VALUE_TOO_LONG', extensions);
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
super(`Value is too long.`, 400, 'VALUE_TOO_LONG', extensions);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as exceptions from '@directus/exceptions';
|
|
2
|
-
const { BaseException } = exceptions;
|
|
3
|
-
export class ForbiddenException extends BaseException {
|
|
4
|
-
constructor() {
|
|
5
|
-
super(`You don't have permission to access this.`, 403, 'FORBIDDEN');
|
|
6
|
-
/**
|
|
7
|
-
* We currently don't show the reason for a forbidden exception in the API output, as that
|
|
8
|
-
* has the potential to leak schema information (eg a "No permission" vs "No permission to files"
|
|
9
|
-
* would leak that a thing called "files" exists.
|
|
10
|
-
* Ref https://github.com/directus/directus/discussions/4368
|
|
11
|
-
*/
|
|
12
|
-
}
|
|
13
|
-
}
|