@directus/api 11.1.0 → 12.0.1
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 +34 -18
- package/dist/services/fields.js +24 -16
- 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 -4
- 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
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/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({
|