@directus/api 11.0.1 → 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/emitter.d.ts +3 -2
- package/dist/emitter.js +12 -4
- package/dist/env.js +21 -17
- 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.d.ts +3 -3
- package/dist/messenger.js +18 -9
- package/dist/middleware/authenticate.js +2 -38
- 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/server.js +10 -0
- 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 -8
- package/dist/services/graphql/index.js +125 -66
- package/dist/services/graphql/subscription.d.ts +16 -0
- package/dist/services/graphql/subscription.js +77 -0
- 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/server.js +24 -0
- 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/services/websocket.d.ts +14 -0
- package/dist/services/websocket.js +26 -0
- package/dist/synchronization.js +3 -3
- package/dist/types/items.d.ts +2 -2
- package/dist/utils/apply-diff.js +13 -4
- package/dist/utils/apply-query.js +22 -13
- package/dist/utils/get-accountability-for-role.js +1 -2
- package/dist/utils/get-accountability-for-token.d.ts +2 -0
- package/dist/utils/get-accountability-for-token.js +50 -0
- package/dist/utils/get-column-path.js +5 -3
- package/dist/utils/get-column.js +3 -3
- package/dist/utils/get-service.d.ts +7 -0
- package/dist/utils/get-service.js +49 -0
- package/dist/utils/jwt.js +5 -5
- package/dist/utils/redact.d.ts +4 -0
- package/dist/utils/redact.js +15 -1
- package/dist/utils/to-boolean.d.ts +4 -0
- package/dist/utils/to-boolean.js +6 -0
- 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.d.ts +6 -0
- package/dist/websocket/authenticate.js +59 -0
- package/dist/websocket/controllers/base.d.ts +42 -0
- package/dist/websocket/controllers/base.js +279 -0
- package/dist/websocket/controllers/graphql.d.ts +12 -0
- package/dist/websocket/controllers/graphql.js +102 -0
- package/dist/websocket/controllers/hooks.d.ts +1 -0
- package/dist/websocket/controllers/hooks.js +122 -0
- package/dist/websocket/controllers/index.d.ts +10 -0
- package/dist/websocket/controllers/index.js +31 -0
- package/dist/websocket/controllers/rest.d.ts +9 -0
- package/dist/websocket/controllers/rest.js +47 -0
- package/dist/websocket/errors.d.ts +16 -0
- package/dist/websocket/errors.js +55 -0
- package/dist/websocket/handlers/heartbeat.d.ts +11 -0
- package/dist/websocket/handlers/heartbeat.js +72 -0
- package/dist/websocket/handlers/index.d.ts +4 -0
- package/dist/websocket/handlers/index.js +11 -0
- package/dist/websocket/handlers/items.d.ts +6 -0
- package/dist/websocket/handlers/items.js +103 -0
- package/dist/websocket/handlers/subscribe.d.ts +43 -0
- package/dist/websocket/handlers/subscribe.js +278 -0
- package/dist/websocket/messages.d.ts +311 -0
- package/dist/websocket/messages.js +96 -0
- package/dist/websocket/types.d.ts +34 -0
- package/dist/websocket/types.js +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.d.ts +1 -0
- package/dist/websocket/utils/get-expires-at-for-token.js +8 -0
- package/dist/websocket/utils/message.d.ts +4 -0
- package/dist/websocket/utils/message.js +27 -0
- package/dist/websocket/utils/wait-for-message.d.ts +4 -0
- package/dist/websocket/utils/wait-for-message.js +45 -0
- package/package.json +21 -16
- 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
package/dist/services/payload.js
CHANGED
|
@@ -7,7 +7,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
7
7
|
import { parse as wktToGeoJSON } from 'wellknown';
|
|
8
8
|
import { getHelpers } from '../database/helpers/index.js';
|
|
9
9
|
import getDatabase from '../database/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
|
|
11
11
|
import { generateHash } from '../utils/generate-hash.js';
|
|
12
12
|
import { ItemsService } from './items.js';
|
|
13
13
|
/**
|
|
@@ -262,14 +262,14 @@ export class PayloadService {
|
|
|
262
262
|
if (dateColumn.type === 'date') {
|
|
263
263
|
const parsedDate = parseISO(value);
|
|
264
264
|
if (!isValid(parsedDate)) {
|
|
265
|
-
throw new
|
|
265
|
+
throw new InvalidPayloadError({ reason: `Invalid Date format in field "${dateColumn.field}"` });
|
|
266
266
|
}
|
|
267
267
|
payload[name] = parsedDate;
|
|
268
268
|
}
|
|
269
269
|
if (dateColumn.type === 'dateTime') {
|
|
270
270
|
const parsedDate = parseISO(value);
|
|
271
271
|
if (!isValid(parsedDate)) {
|
|
272
|
-
throw new
|
|
272
|
+
throw new InvalidPayloadError({ reason: `Invalid DateTime format in field "${dateColumn.field}"` });
|
|
273
273
|
}
|
|
274
274
|
payload[name] = parsedDate;
|
|
275
275
|
}
|
|
@@ -318,11 +318,15 @@ export class PayloadService {
|
|
|
318
318
|
continue;
|
|
319
319
|
const relatedCollection = payload[relation.meta.one_collection_field];
|
|
320
320
|
if (!relatedCollection) {
|
|
321
|
-
throw new
|
|
321
|
+
throw new InvalidPayloadError({
|
|
322
|
+
reason: `Can't update nested record "${relation.collection}.${relation.field}" without field "${relation.collection}.${relation.meta.one_collection_field}" being set`,
|
|
323
|
+
});
|
|
322
324
|
}
|
|
323
325
|
const allowedCollections = relation.meta.one_allowed_collections;
|
|
324
326
|
if (allowedCollections.includes(relatedCollection) === false) {
|
|
325
|
-
throw new
|
|
327
|
+
throw new InvalidPayloadError({
|
|
328
|
+
reason: `"${relation.collection}.${relation.field}" can't be linked to collection "${relatedCollection}"`,
|
|
329
|
+
});
|
|
326
330
|
}
|
|
327
331
|
const itemsService = new ItemsService(relatedCollection, {
|
|
328
332
|
accountability: this.accountability,
|
|
@@ -474,7 +478,7 @@ export class PayloadService {
|
|
|
474
478
|
.where({ [relatedPrimaryKeyField]: record })
|
|
475
479
|
.first();
|
|
476
480
|
if (!!existingRecord === false) {
|
|
477
|
-
throw new
|
|
481
|
+
throw new ForbiddenError();
|
|
478
482
|
}
|
|
479
483
|
// If the related item is already associated to the current item, and there's no
|
|
480
484
|
// other updates (which is indicated by the fact that this is just the PK, we can
|
|
@@ -542,7 +546,7 @@ export class PayloadService {
|
|
|
542
546
|
const alterations = field;
|
|
543
547
|
const { error } = nestedUpdateSchema.validate(alterations);
|
|
544
548
|
if (error)
|
|
545
|
-
throw new
|
|
549
|
+
throw new InvalidPayloadError({ reason: `Invalid one-to-many update structure: ${error.message}` });
|
|
546
550
|
if (alterations.create) {
|
|
547
551
|
const sortField = relation.meta.sort_field;
|
|
548
552
|
let createPayload;
|
|
@@ -5,7 +5,7 @@ import { getHelpers } from '../database/helpers/index.js';
|
|
|
5
5
|
import getDatabase, { getSchemaInspector } from '../database/index.js';
|
|
6
6
|
import { systemRelationRows } from '../database/system-data/relations/index.js';
|
|
7
7
|
import emitter from '../emitter.js';
|
|
8
|
-
import {
|
|
8
|
+
import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
|
|
9
9
|
import { getDefaultIndexName } from '../utils/get-default-index-name.js';
|
|
10
10
|
import { getSchema } from '../utils/get-schema.js';
|
|
11
11
|
import { ItemsService } from './items.js';
|
|
@@ -37,7 +37,7 @@ export class RelationsService {
|
|
|
37
37
|
}
|
|
38
38
|
async readAll(collection, opts) {
|
|
39
39
|
if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
|
|
40
|
-
throw new
|
|
40
|
+
throw new ForbiddenError();
|
|
41
41
|
}
|
|
42
42
|
const metaReadQuery = {
|
|
43
43
|
limit: -1,
|
|
@@ -64,17 +64,17 @@ export class RelationsService {
|
|
|
64
64
|
async readOne(collection, field) {
|
|
65
65
|
if (this.accountability && this.accountability.admin !== true) {
|
|
66
66
|
if (this.hasReadAccess === false) {
|
|
67
|
-
throw new
|
|
67
|
+
throw new ForbiddenError();
|
|
68
68
|
}
|
|
69
69
|
const permissions = this.accountability.permissions?.find((permission) => {
|
|
70
70
|
return permission.action === 'read' && permission.collection === collection;
|
|
71
71
|
});
|
|
72
72
|
if (!permissions || !permissions.fields)
|
|
73
|
-
throw new
|
|
73
|
+
throw new ForbiddenError();
|
|
74
74
|
if (permissions.fields.includes('*') === false) {
|
|
75
75
|
const allowedFields = permissions.fields;
|
|
76
76
|
if (allowedFields.includes(field) === false)
|
|
77
|
-
throw new
|
|
77
|
+
throw new ForbiddenError();
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
const metaRow = await this.relationsItemService.readByQuery({
|
|
@@ -98,7 +98,7 @@ export class RelationsService {
|
|
|
98
98
|
const stitched = this.stitchRelations(metaRow, schemaRow ? [schemaRow] : []);
|
|
99
99
|
const results = await this.filterForbidden(stitched);
|
|
100
100
|
if (results.length === 0) {
|
|
101
|
-
throw new
|
|
101
|
+
throw new ForbiddenError();
|
|
102
102
|
}
|
|
103
103
|
return results[0];
|
|
104
104
|
}
|
|
@@ -107,30 +107,36 @@ export class RelationsService {
|
|
|
107
107
|
*/
|
|
108
108
|
async createOne(relation, opts) {
|
|
109
109
|
if (this.accountability && this.accountability.admin !== true) {
|
|
110
|
-
throw new
|
|
110
|
+
throw new ForbiddenError();
|
|
111
111
|
}
|
|
112
112
|
if (!relation.collection) {
|
|
113
|
-
throw new
|
|
113
|
+
throw new InvalidPayloadError({ reason: '"collection" is required' });
|
|
114
114
|
}
|
|
115
115
|
if (!relation.field) {
|
|
116
|
-
throw new
|
|
116
|
+
throw new InvalidPayloadError({ reason: '"field" is required' });
|
|
117
117
|
}
|
|
118
118
|
if (relation.collection in this.schema.collections === false) {
|
|
119
|
-
throw new
|
|
119
|
+
throw new InvalidPayloadError({ reason: `Collection "${relation.collection}" doesn't exist` });
|
|
120
120
|
}
|
|
121
121
|
if (relation.field in this.schema.collections[relation.collection].fields === false) {
|
|
122
|
-
throw new
|
|
122
|
+
throw new InvalidPayloadError({
|
|
123
|
+
reason: `Field "${relation.field}" doesn't exist in collection "${relation.collection}"`,
|
|
124
|
+
});
|
|
123
125
|
}
|
|
124
126
|
// A primary key should not be a foreign key
|
|
125
127
|
if (this.schema.collections[relation.collection].primary === relation.field) {
|
|
126
|
-
throw new
|
|
128
|
+
throw new InvalidPayloadError({
|
|
129
|
+
reason: `Field "${relation.field}" in collection "${relation.collection}" is a primary key`,
|
|
130
|
+
});
|
|
127
131
|
}
|
|
128
132
|
if (relation.related_collection && relation.related_collection in this.schema.collections === false) {
|
|
129
|
-
throw new
|
|
133
|
+
throw new InvalidPayloadError({ reason: `Collection "${relation.related_collection}" doesn't exist` });
|
|
130
134
|
}
|
|
131
135
|
const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === relation.collection && existingRelation.field === relation.field);
|
|
132
136
|
if (existingRelation) {
|
|
133
|
-
throw new
|
|
137
|
+
throw new InvalidPayloadError({
|
|
138
|
+
reason: `Field "${relation.field}" in collection "${relation.collection}" already has an associated relationship`,
|
|
139
|
+
});
|
|
134
140
|
}
|
|
135
141
|
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
136
142
|
this.helpers.schema.preRelationChange(relation);
|
|
@@ -190,17 +196,19 @@ export class RelationsService {
|
|
|
190
196
|
*/
|
|
191
197
|
async updateOne(collection, field, relation, opts) {
|
|
192
198
|
if (this.accountability && this.accountability.admin !== true) {
|
|
193
|
-
throw new
|
|
199
|
+
throw new ForbiddenError();
|
|
194
200
|
}
|
|
195
201
|
if (collection in this.schema.collections === false) {
|
|
196
|
-
throw new
|
|
202
|
+
throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
|
|
197
203
|
}
|
|
198
204
|
if (field in this.schema.collections[collection].fields === false) {
|
|
199
|
-
throw new
|
|
205
|
+
throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
|
|
200
206
|
}
|
|
201
207
|
const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
|
|
202
208
|
if (!existingRelation) {
|
|
203
|
-
throw new
|
|
209
|
+
throw new InvalidPayloadError({
|
|
210
|
+
reason: `Field "${field}" in collection "${collection}" doesn't have a relationship.`,
|
|
211
|
+
});
|
|
204
212
|
}
|
|
205
213
|
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
206
214
|
this.helpers.schema.preRelationChange(relation);
|
|
@@ -273,17 +281,19 @@ export class RelationsService {
|
|
|
273
281
|
*/
|
|
274
282
|
async deleteOne(collection, field, opts) {
|
|
275
283
|
if (this.accountability && this.accountability.admin !== true) {
|
|
276
|
-
throw new
|
|
284
|
+
throw new ForbiddenError();
|
|
277
285
|
}
|
|
278
286
|
if (collection in this.schema.collections === false) {
|
|
279
|
-
throw new
|
|
287
|
+
throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't exist` });
|
|
280
288
|
}
|
|
281
289
|
if (field in this.schema.collections[collection].fields === false) {
|
|
282
|
-
throw new
|
|
290
|
+
throw new InvalidPayloadError({ reason: `Field "${field}" doesn't exist in collection "${collection}"` });
|
|
283
291
|
}
|
|
284
292
|
const existingRelation = this.schema.relations.find((existingRelation) => existingRelation.collection === collection && existingRelation.field === field);
|
|
285
293
|
if (!existingRelation) {
|
|
286
|
-
throw new
|
|
294
|
+
throw new InvalidPayloadError({
|
|
295
|
+
reason: `Field "${field}" in collection "${collection}" doesn't have a relationship.`,
|
|
296
|
+
});
|
|
287
297
|
}
|
|
288
298
|
const runPostColumnChange = await this.helpers.schema.preColumnChange();
|
|
289
299
|
const nestedActionEvents = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
|
|
2
2
|
import { ItemsService } from './items.js';
|
|
3
3
|
export class RevisionsService extends ItemsService {
|
|
4
4
|
constructor(options) {
|
|
@@ -7,9 +7,9 @@ export class RevisionsService extends ItemsService {
|
|
|
7
7
|
async revert(pk) {
|
|
8
8
|
const revision = await super.readOne(pk);
|
|
9
9
|
if (!revision)
|
|
10
|
-
throw new
|
|
10
|
+
throw new ForbiddenError();
|
|
11
11
|
if (!revision['data'])
|
|
12
|
-
throw new
|
|
12
|
+
throw new InvalidPayloadError({ reason: `Revision doesn't contain data to revert to` });
|
|
13
13
|
const service = new ItemsService(revision['collection'], {
|
|
14
14
|
accountability: this.accountability,
|
|
15
15
|
knex: this.knex,
|
package/dist/services/roles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ForbiddenError, UnprocessableContentError } from '../errors/index.js';
|
|
2
2
|
import { ItemsService } from './items.js';
|
|
3
3
|
import { PermissionsService } from './permissions.js';
|
|
4
4
|
import { PresetsService } from './presets.js';
|
|
@@ -16,13 +16,14 @@ export class RolesService extends ItemsService {
|
|
|
16
16
|
.andWhere({ admin_access: true })
|
|
17
17
|
.first();
|
|
18
18
|
const otherAdminRolesCount = +(otherAdminRoles?.count || 0);
|
|
19
|
-
if (otherAdminRolesCount === 0)
|
|
20
|
-
throw new
|
|
19
|
+
if (otherAdminRolesCount === 0) {
|
|
20
|
+
throw new UnprocessableContentError({ reason: `You can't delete the last admin role` });
|
|
21
|
+
}
|
|
21
22
|
}
|
|
22
23
|
async checkForOtherAdminUsers(key, users) {
|
|
23
24
|
const role = await this.knex.select('admin_access').from('directus_roles').where('id', '=', key).first();
|
|
24
25
|
if (!role)
|
|
25
|
-
throw new
|
|
26
|
+
throw new ForbiddenError();
|
|
26
27
|
// The users that will now be in this new non-admin role
|
|
27
28
|
let userKeys = [];
|
|
28
29
|
if (Array.isArray(users)) {
|
|
@@ -48,7 +49,7 @@ export class RolesService extends ItemsService {
|
|
|
48
49
|
.first();
|
|
49
50
|
const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
|
|
50
51
|
if (otherAdminUsersCount === 0) {
|
|
51
|
-
throw new
|
|
52
|
+
throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the admin role` });
|
|
52
53
|
}
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
@@ -59,7 +60,7 @@ export class RolesService extends ItemsService {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
catch (err) {
|
|
62
|
-
(opts || (opts = {})).
|
|
63
|
+
(opts || (opts = {})).preMutationError = err;
|
|
63
64
|
}
|
|
64
65
|
return super.updateOne(key, data, opts);
|
|
65
66
|
}
|
|
@@ -73,7 +74,7 @@ export class RolesService extends ItemsService {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
catch (err) {
|
|
76
|
-
(opts || (opts = {})).
|
|
77
|
+
(opts || (opts = {})).preMutationError = err;
|
|
77
78
|
}
|
|
78
79
|
return super.updateBatch(data, opts);
|
|
79
80
|
}
|
|
@@ -84,7 +85,7 @@ export class RolesService extends ItemsService {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
catch (err) {
|
|
87
|
-
(opts || (opts = {})).
|
|
88
|
+
(opts || (opts = {})).preMutationError = err;
|
|
88
89
|
}
|
|
89
90
|
return super.updateMany(keys, data, opts);
|
|
90
91
|
}
|
|
@@ -98,7 +99,7 @@ export class RolesService extends ItemsService {
|
|
|
98
99
|
await this.checkForOtherAdminRoles(keys);
|
|
99
100
|
}
|
|
100
101
|
catch (err) {
|
|
101
|
-
opts.
|
|
102
|
+
opts.preMutationError = err;
|
|
102
103
|
}
|
|
103
104
|
await this.knex.transaction(async (trx) => {
|
|
104
105
|
const itemsService = new ItemsService('directus_roles', {
|
package/dist/services/schema.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ForbiddenError } from '../errors/index.js';
|
|
3
3
|
import { applyDiff } from '../utils/apply-diff.js';
|
|
4
|
-
import { getSnapshot } from '../utils/get-snapshot.js';
|
|
5
4
|
import { getSnapshotDiff } from '../utils/get-snapshot-diff.js';
|
|
5
|
+
import { getSnapshot } from '../utils/get-snapshot.js';
|
|
6
6
|
import { getVersionedHash } from '../utils/get-versioned-hash.js';
|
|
7
7
|
import { validateApplyDiff } from '../utils/validate-diff.js';
|
|
8
8
|
import { validateSnapshot } from '../utils/validate-snapshot.js';
|
|
@@ -15,13 +15,13 @@ export class SchemaService {
|
|
|
15
15
|
}
|
|
16
16
|
async snapshot() {
|
|
17
17
|
if (this.accountability?.admin !== true)
|
|
18
|
-
throw new
|
|
18
|
+
throw new ForbiddenError();
|
|
19
19
|
const currentSnapshot = await getSnapshot({ database: this.knex });
|
|
20
20
|
return currentSnapshot;
|
|
21
21
|
}
|
|
22
22
|
async apply(payload) {
|
|
23
23
|
if (this.accountability?.admin !== true)
|
|
24
|
-
throw new
|
|
24
|
+
throw new ForbiddenError();
|
|
25
25
|
const currentSnapshot = await this.snapshot();
|
|
26
26
|
const snapshotWithHash = this.getHashedSnapshot(currentSnapshot);
|
|
27
27
|
if (!validateApplyDiff(payload, snapshotWithHash))
|
|
@@ -30,7 +30,7 @@ export class SchemaService {
|
|
|
30
30
|
}
|
|
31
31
|
async diff(snapshot, options) {
|
|
32
32
|
if (this.accountability?.admin !== true)
|
|
33
|
-
throw new
|
|
33
|
+
throw new ForbiddenError();
|
|
34
34
|
validateSnapshot(snapshot, options?.force);
|
|
35
35
|
const currentSnapshot = options?.currentSnapshot ?? (await getSnapshot({ database: this.knex }));
|
|
36
36
|
const diff = getSnapshotDiff(currentSnapshot, snapshot);
|
package/dist/services/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { SERVER_ONLINE } from '../server.js';
|
|
|
13
13
|
import { getStorage } from '../storage/index.js';
|
|
14
14
|
import { version } from '../utils/package.js';
|
|
15
15
|
import { SettingsService } from './settings.js';
|
|
16
|
+
import { toBoolean } from '../utils/to-boolean.js';
|
|
16
17
|
export class ServerService {
|
|
17
18
|
knex;
|
|
18
19
|
accountability;
|
|
@@ -67,6 +68,29 @@ export class ServerService {
|
|
|
67
68
|
max: Number.isFinite(env['QUERY_LIMIT_MAX']) ? env['QUERY_LIMIT_MAX'] : -1,
|
|
68
69
|
};
|
|
69
70
|
}
|
|
71
|
+
if (this.accountability?.user) {
|
|
72
|
+
if (toBoolean(env['WEBSOCKETS_ENABLED'])) {
|
|
73
|
+
info['websocket'] = {};
|
|
74
|
+
info['websocket'].rest = toBoolean(env['WEBSOCKETS_REST_ENABLED'])
|
|
75
|
+
? {
|
|
76
|
+
authentication: env['WEBSOCKETS_REST_AUTH'],
|
|
77
|
+
path: env['WEBSOCKETS_REST_PATH'],
|
|
78
|
+
}
|
|
79
|
+
: false;
|
|
80
|
+
info['websocket'].graphql = toBoolean(env['WEBSOCKETS_GRAPHQL_ENABLED'])
|
|
81
|
+
? {
|
|
82
|
+
authentication: env['WEBSOCKETS_GRAPHQL_AUTH'],
|
|
83
|
+
path: env['WEBSOCKETS_GRAPHQL_PATH'],
|
|
84
|
+
}
|
|
85
|
+
: false;
|
|
86
|
+
info['websocket'].heartbeat = toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED'])
|
|
87
|
+
? env['WEBSOCKETS_HEARTBEAT_PERIOD']
|
|
88
|
+
: false;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
info['websocket'] = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
70
94
|
return info;
|
|
71
95
|
}
|
|
72
96
|
async health() {
|
package/dist/services/shares.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argon2 from 'argon2';
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
import env from '../env.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ForbiddenError, InvalidCredentialsError } from '../errors/index.js';
|
|
5
5
|
import { getMilliseconds } from '../utils/get-milliseconds.js';
|
|
6
6
|
import { md } from '../utils/md.js';
|
|
7
7
|
import { Url } from '../utils/url.js';
|
|
@@ -51,10 +51,10 @@ export class SharesService extends ItemsService {
|
|
|
51
51
|
})
|
|
52
52
|
.first();
|
|
53
53
|
if (!record) {
|
|
54
|
-
throw new
|
|
54
|
+
throw new InvalidCredentialsError();
|
|
55
55
|
}
|
|
56
56
|
if (record.share_password && !(await argon2.verify(record.share_password, payload['password']))) {
|
|
57
|
-
throw new
|
|
57
|
+
throw new InvalidCredentialsError();
|
|
58
58
|
}
|
|
59
59
|
await this.knex('directus_shares')
|
|
60
60
|
.update({ times_used: record.share_times_used + 1 })
|
|
@@ -96,7 +96,7 @@ export class SharesService extends ItemsService {
|
|
|
96
96
|
*/
|
|
97
97
|
async invite(payload) {
|
|
98
98
|
if (!this.accountability?.user)
|
|
99
|
-
throw new
|
|
99
|
+
throw new ForbiddenError();
|
|
100
100
|
const share = await this.readOne(payload.share, { fields: ['collection'] });
|
|
101
101
|
const usersService = new UsersService({
|
|
102
102
|
knex: this.knex,
|
package/dist/services/tfa.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { authenticator } from 'otplib';
|
|
2
2
|
import getDatabase from '../database/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
4
4
|
import { ItemsService } from './items.js';
|
|
5
5
|
export class TFAService {
|
|
6
6
|
knex;
|
|
@@ -15,17 +15,17 @@ export class TFAService {
|
|
|
15
15
|
}
|
|
16
16
|
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
|
|
17
17
|
if (!user?.tfa_secret) {
|
|
18
|
-
throw new
|
|
18
|
+
throw new InvalidPayloadError({ reason: `User "${key}" doesn't have TFA enabled` });
|
|
19
19
|
}
|
|
20
20
|
return authenticator.check(otp, user.tfa_secret);
|
|
21
21
|
}
|
|
22
22
|
async generateTFA(key) {
|
|
23
23
|
const user = await this.knex.select('email', 'tfa_secret').from('directus_users').where({ id: key }).first();
|
|
24
24
|
if (user?.tfa_secret !== null) {
|
|
25
|
-
throw new
|
|
25
|
+
throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
|
|
26
26
|
}
|
|
27
27
|
if (!user?.email) {
|
|
28
|
-
throw new
|
|
28
|
+
throw new InvalidPayloadError({ reason: 'User must have a valid email to enable TFA' });
|
|
29
29
|
}
|
|
30
30
|
const secret = authenticator.generateSecret();
|
|
31
31
|
const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
|
|
@@ -37,10 +37,10 @@ export class TFAService {
|
|
|
37
37
|
async enableTFA(key, otp, secret) {
|
|
38
38
|
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
|
|
39
39
|
if (user?.tfa_secret !== null) {
|
|
40
|
-
throw new
|
|
40
|
+
throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
|
|
41
41
|
}
|
|
42
42
|
if (!authenticator.check(otp, secret)) {
|
|
43
|
-
throw new
|
|
43
|
+
throw new InvalidPayloadError({ reason: `"otp" is invalid` });
|
|
44
44
|
}
|
|
45
45
|
await this.itemsService.updateOne(key, { tfa_secret: secret });
|
|
46
46
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Item, PrimaryKey } from '@directus/types';
|
|
2
|
-
import { ItemsService } from './items.js';
|
|
3
|
-
import type { AbstractServiceOptions } from '../types/services.js';
|
|
4
2
|
import type { MutationOptions } from '../types/items.js';
|
|
3
|
+
import type { AbstractServiceOptions } from '../types/services.js';
|
|
4
|
+
import { ItemsService } from './items.js';
|
|
5
5
|
export declare class TranslationsService extends ItemsService {
|
|
6
6
|
constructor(options: AbstractServiceOptions);
|
|
7
7
|
private translationKeyExists;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
+
import { InvalidPayloadError } from '../errors/index.js';
|
|
2
3
|
import { ItemsService } from './items.js';
|
|
3
|
-
import { InvalidPayloadException } from '../index.js';
|
|
4
4
|
export class TranslationsService extends ItemsService {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
super('directus_translations', options);
|
|
@@ -14,20 +14,20 @@ export class TranslationsService extends ItemsService {
|
|
|
14
14
|
}
|
|
15
15
|
async createOne(data, opts) {
|
|
16
16
|
if (await this.translationKeyExists(data['key'], data['language'])) {
|
|
17
|
-
throw new
|
|
17
|
+
throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
|
|
18
18
|
}
|
|
19
19
|
return await super.createOne(data, opts);
|
|
20
20
|
}
|
|
21
21
|
async updateMany(keys, data, opts) {
|
|
22
22
|
if (keys.length > 0 && 'key' in data && 'language' in data) {
|
|
23
|
-
throw new
|
|
23
|
+
throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
|
|
24
24
|
}
|
|
25
25
|
else if ('key' in data || 'language' in data) {
|
|
26
26
|
const items = await this.readMany(keys);
|
|
27
27
|
for (const item of items) {
|
|
28
28
|
const updatedData = { ...item, ...data };
|
|
29
29
|
if (await this.translationKeyExists(updatedData['key'], updatedData['language'])) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new InvalidPayloadError({ reason: 'Duplicate key and language combination' });
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
}
|