@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,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/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
|
}
|
package/dist/services/users.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { FailedValidationException } from '@directus/exceptions';
|
|
2
1
|
import { getSimpleHash, toArray } from '@directus/utils';
|
|
2
|
+
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
3
3
|
import jwt from 'jsonwebtoken';
|
|
4
4
|
import { cloneDeep, isEmpty } from 'lodash-es';
|
|
5
5
|
import { performance } from 'perf_hooks';
|
|
6
6
|
import getDatabase from '../database/index.js';
|
|
7
7
|
import env from '../env.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { ForbiddenError } from '../errors/forbidden.js';
|
|
9
|
+
import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '../errors/index.js';
|
|
10
10
|
import isUrlAllowed from '../utils/is-url-allowed.js';
|
|
11
11
|
import { verifyJWT } from '../utils/jwt.js';
|
|
12
12
|
import { stall } from '../utils/stall.js';
|
|
@@ -29,10 +29,9 @@ export class UsersService extends ItemsService {
|
|
|
29
29
|
emails = emails.map((email) => email.toLowerCase());
|
|
30
30
|
const duplicates = emails.filter((value, index, array) => array.indexOf(value) !== index);
|
|
31
31
|
if (duplicates.length) {
|
|
32
|
-
throw new
|
|
32
|
+
throw new RecordNotUniqueError({
|
|
33
33
|
collection: 'directus_users',
|
|
34
34
|
field: 'email',
|
|
35
|
-
invalid: duplicates[0],
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
37
|
const query = this.knex
|
|
@@ -44,10 +43,9 @@ export class UsersService extends ItemsService {
|
|
|
44
43
|
}
|
|
45
44
|
const results = await query;
|
|
46
45
|
if (results.length) {
|
|
47
|
-
throw new
|
|
46
|
+
throw new RecordNotUniqueError({
|
|
48
47
|
collection: 'directus_users',
|
|
49
48
|
field: 'email',
|
|
50
|
-
invalid: results[0].email,
|
|
51
49
|
});
|
|
52
50
|
}
|
|
53
51
|
}
|
|
@@ -70,14 +68,14 @@ export class UsersService extends ItemsService {
|
|
|
70
68
|
const regex = new RegExp(wrapped ? policyRegExString.slice(1, -1) : policyRegExString);
|
|
71
69
|
for (const password of passwords) {
|
|
72
70
|
if (!regex.test(password)) {
|
|
73
|
-
throw new
|
|
71
|
+
throw new FailedValidationError(joiValidationErrorItemToErrorExtensions({
|
|
74
72
|
message: `Provided password doesn't match password policy`,
|
|
75
73
|
path: ['password'],
|
|
76
74
|
type: 'custom.pattern.base',
|
|
77
75
|
context: {
|
|
78
76
|
value: password,
|
|
79
77
|
},
|
|
80
|
-
});
|
|
78
|
+
}));
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
}
|
|
@@ -92,7 +90,7 @@ export class UsersService extends ItemsService {
|
|
|
92
90
|
.first();
|
|
93
91
|
const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
|
|
94
92
|
if (otherAdminUsersCount === 0) {
|
|
95
|
-
throw new
|
|
93
|
+
throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the role` });
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
/**
|
|
@@ -109,7 +107,7 @@ export class UsersService extends ItemsService {
|
|
|
109
107
|
.first();
|
|
110
108
|
const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
|
|
111
109
|
if (otherAdminUsersCount === 0) {
|
|
112
|
-
throw new
|
|
110
|
+
throw new UnprocessableContentError({ reason: `You can't change the active status of the last admin user` });
|
|
113
111
|
}
|
|
114
112
|
}
|
|
115
113
|
/**
|
|
@@ -154,7 +152,7 @@ export class UsersService extends ItemsService {
|
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
catch (err) {
|
|
157
|
-
(opts || (opts = {})).
|
|
155
|
+
(opts || (opts = {})).preMutationError = err;
|
|
158
156
|
}
|
|
159
157
|
return await super.createMany(data, opts);
|
|
160
158
|
}
|
|
@@ -185,7 +183,7 @@ export class UsersService extends ItemsService {
|
|
|
185
183
|
});
|
|
186
184
|
for (const item of data) {
|
|
187
185
|
if (!item[primaryKeyField])
|
|
188
|
-
throw new
|
|
186
|
+
throw new InvalidPayloadError({ reason: `User in update misses primary key` });
|
|
189
187
|
keys.push(await service.updateOne(item[primaryKeyField], item, opts));
|
|
190
188
|
}
|
|
191
189
|
});
|
|
@@ -209,10 +207,9 @@ export class UsersService extends ItemsService {
|
|
|
209
207
|
}
|
|
210
208
|
if (data['email']) {
|
|
211
209
|
if (keys.length > 1) {
|
|
212
|
-
throw new
|
|
210
|
+
throw new RecordNotUniqueError({
|
|
213
211
|
collection: 'directus_users',
|
|
214
212
|
field: 'email',
|
|
215
|
-
invalid: data['email'],
|
|
216
213
|
});
|
|
217
214
|
}
|
|
218
215
|
await this.checkUniqueEmails([data['email']], keys[0]);
|
|
@@ -221,23 +218,23 @@ export class UsersService extends ItemsService {
|
|
|
221
218
|
await this.checkPasswordPolicy([data['password']]);
|
|
222
219
|
}
|
|
223
220
|
if (data['tfa_secret'] !== undefined) {
|
|
224
|
-
throw new
|
|
221
|
+
throw new InvalidPayloadError({ reason: `You can't change the "tfa_secret" value manually` });
|
|
225
222
|
}
|
|
226
223
|
if (data['provider'] !== undefined) {
|
|
227
224
|
if (this.accountability && this.accountability.admin !== true) {
|
|
228
|
-
throw new
|
|
225
|
+
throw new InvalidPayloadError({ reason: `You can't change the "provider" value manually` });
|
|
229
226
|
}
|
|
230
227
|
data['auth_data'] = null;
|
|
231
228
|
}
|
|
232
229
|
if (data['external_identifier'] !== undefined) {
|
|
233
230
|
if (this.accountability && this.accountability.admin !== true) {
|
|
234
|
-
throw new
|
|
231
|
+
throw new InvalidPayloadError({ reason: `You can't change the "external_identifier" value manually` });
|
|
235
232
|
}
|
|
236
233
|
data['auth_data'] = null;
|
|
237
234
|
}
|
|
238
235
|
}
|
|
239
236
|
catch (err) {
|
|
240
|
-
(opts || (opts = {})).
|
|
237
|
+
(opts || (opts = {})).preMutationError = err;
|
|
241
238
|
}
|
|
242
239
|
return await super.updateMany(keys, data, opts);
|
|
243
240
|
}
|
|
@@ -256,7 +253,7 @@ export class UsersService extends ItemsService {
|
|
|
256
253
|
await this.checkRemainingAdminExistence(keys);
|
|
257
254
|
}
|
|
258
255
|
catch (err) {
|
|
259
|
-
(opts || (opts = {})).
|
|
256
|
+
(opts || (opts = {})).preMutationError = err;
|
|
260
257
|
}
|
|
261
258
|
await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
|
|
262
259
|
await super.deleteMany(keys, opts);
|
|
@@ -281,11 +278,11 @@ export class UsersService extends ItemsService {
|
|
|
281
278
|
const opts = {};
|
|
282
279
|
try {
|
|
283
280
|
if (url && isUrlAllowed(url, env['USER_INVITE_URL_ALLOW_LIST']) === false) {
|
|
284
|
-
throw new
|
|
281
|
+
throw new InvalidPayloadError({ reason: `Url "${url}" can't be used to invite users` });
|
|
285
282
|
}
|
|
286
283
|
}
|
|
287
284
|
catch (err) {
|
|
288
|
-
opts.
|
|
285
|
+
opts.preMutationError = err;
|
|
289
286
|
}
|
|
290
287
|
const emails = toArray(email);
|
|
291
288
|
const mailService = new MailService({
|
|
@@ -323,10 +320,10 @@ export class UsersService extends ItemsService {
|
|
|
323
320
|
async acceptInvite(token, password) {
|
|
324
321
|
const { email, scope } = verifyJWT(token, env['SECRET']);
|
|
325
322
|
if (scope !== 'invite')
|
|
326
|
-
throw new
|
|
323
|
+
throw new ForbiddenError();
|
|
327
324
|
const user = await this.getUserByEmail(email);
|
|
328
325
|
if (user?.status !== 'invited') {
|
|
329
|
-
throw new
|
|
326
|
+
throw new InvalidPayloadError({ reason: `Email address ${email} hasn't been invited` });
|
|
330
327
|
}
|
|
331
328
|
// Allow unauthenticated update
|
|
332
329
|
const service = new UsersService({
|
|
@@ -341,10 +338,10 @@ export class UsersService extends ItemsService {
|
|
|
341
338
|
const user = await this.getUserByEmail(email);
|
|
342
339
|
if (user?.status !== 'active') {
|
|
343
340
|
await stall(STALL_TIME, timeStart);
|
|
344
|
-
throw new
|
|
341
|
+
throw new ForbiddenError();
|
|
345
342
|
}
|
|
346
343
|
if (url && isUrlAllowed(url, env['PASSWORD_RESET_URL_ALLOW_LIST']) === false) {
|
|
347
|
-
throw new
|
|
344
|
+
throw new InvalidPayloadError({ reason: `Url "${url}" can't be used to reset passwords` });
|
|
348
345
|
}
|
|
349
346
|
const mailService = new MailService({
|
|
350
347
|
schema: this.schema,
|
|
@@ -373,17 +370,17 @@ export class UsersService extends ItemsService {
|
|
|
373
370
|
async resetPassword(token, password) {
|
|
374
371
|
const { email, scope, hash } = jwt.verify(token, env['SECRET'], { issuer: 'directus' });
|
|
375
372
|
if (scope !== 'password-reset' || !hash)
|
|
376
|
-
throw new
|
|
373
|
+
throw new ForbiddenError();
|
|
377
374
|
const opts = {};
|
|
378
375
|
try {
|
|
379
376
|
await this.checkPasswordPolicy([password]);
|
|
380
377
|
}
|
|
381
378
|
catch (err) {
|
|
382
|
-
opts.
|
|
379
|
+
opts.preMutationError = err;
|
|
383
380
|
}
|
|
384
381
|
const user = await this.getUserByEmail(email);
|
|
385
382
|
if (user?.status !== 'active' || hash !== getSimpleHash('' + user.password)) {
|
|
386
|
-
throw new
|
|
383
|
+
throw new ForbiddenError();
|
|
387
384
|
}
|
|
388
385
|
// Allow unauthenticated update
|
|
389
386
|
const service = new UsersService({
|
package/dist/services/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
2
|
import { systemCollectionRows } from '../database/system-data/collections/index.js';
|
|
3
3
|
import emitter from '../emitter.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
|
|
5
5
|
export class UtilsService {
|
|
6
6
|
knex;
|
|
7
7
|
accountability;
|
|
@@ -16,18 +16,18 @@ export class UtilsService {
|
|
|
16
16
|
systemCollectionRows;
|
|
17
17
|
const sortField = sortFieldResponse?.sort_field;
|
|
18
18
|
if (!sortField) {
|
|
19
|
-
throw new
|
|
19
|
+
throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't have a sort field` });
|
|
20
20
|
}
|
|
21
21
|
if (this.accountability?.admin !== true) {
|
|
22
22
|
const permissions = this.accountability?.permissions?.find((permission) => {
|
|
23
23
|
return permission.collection === collection && permission.action === 'update';
|
|
24
24
|
});
|
|
25
25
|
if (!permissions) {
|
|
26
|
-
throw new
|
|
26
|
+
throw new ForbiddenError();
|
|
27
27
|
}
|
|
28
28
|
const allowedFields = permissions.fields ?? [];
|
|
29
29
|
if (allowedFields[0] !== '*' && allowedFields.includes(sortField) === false) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new ForbiddenError();
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
const primaryKeyField = this.schema.collections[collection].primary;
|
package/dist/synchronization.js
CHANGED
|
@@ -73,9 +73,9 @@ class SynchronizationManagerRedis {
|
|
|
73
73
|
namespace;
|
|
74
74
|
client;
|
|
75
75
|
constructor() {
|
|
76
|
-
const config = getConfigFromEnv('
|
|
77
|
-
this.client = new Redis(env['
|
|
78
|
-
this.namespace = env['SYNCHRONIZATION_NAMESPACE'] ?? 'directus';
|
|
76
|
+
const config = getConfigFromEnv('REDIS');
|
|
77
|
+
this.client = new Redis(env['REDIS'] ?? config);
|
|
78
|
+
this.namespace = env['SYNCHRONIZATION_NAMESPACE'] ?? 'directus-sync';
|
|
79
79
|
this.client.defineCommand('setGreaterThan', {
|
|
80
80
|
numberOfKeys: 1,
|
|
81
81
|
lua: SET_GREATER_THAN_SCRIPT,
|
package/dist/types/items.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* I know this looks a little silly, but it allows us to explicitly differentiate between when we're
|
|
3
3
|
* expecting an item vs any other generic object.
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { DirectusError } from '@directus/errors';
|
|
6
6
|
import type { EventContext } from '@directus/types';
|
|
7
7
|
import type { MutationTracker } from '../services/items.js';
|
|
8
8
|
export type Item = Record<string, any>;
|
|
@@ -46,7 +46,7 @@ export type MutationOptions = {
|
|
|
46
46
|
* To keep track of mutation limits
|
|
47
47
|
*/
|
|
48
48
|
mutationTracker?: MutationTracker | undefined;
|
|
49
|
-
|
|
49
|
+
preMutationError?: DirectusError | undefined;
|
|
50
50
|
};
|
|
51
51
|
export type ActionEventParams = {
|
|
52
52
|
event: string | string[];
|