@directus/api 29.1.1 → 31.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 +5 -0
- package/dist/auth/drivers/oauth2.js +17 -3
- package/dist/auth/drivers/openid.js +17 -3
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +9 -1
- package/dist/controllers/items.js +3 -4
- package/dist/controllers/mcp.d.ts +2 -0
- package/dist/controllers/mcp.js +33 -0
- package/dist/controllers/users.js +17 -7
- package/dist/controllers/versions.js +3 -2
- package/dist/database/errors/dialects/mssql.d.ts +1 -1
- package/dist/database/errors/dialects/mssql.js +18 -10
- package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
- package/dist/database/migrations/20250813A-add-mcp.js +18 -0
- package/dist/database/run-ast/README.md +46 -0
- package/dist/mailer.js +3 -3
- package/dist/mcp/define.d.ts +2 -0
- package/dist/mcp/define.js +3 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/schema.d.ts +485 -0
- package/dist/mcp/schema.js +219 -0
- package/dist/mcp/server.d.ts +97 -0
- package/dist/mcp/server.js +310 -0
- package/dist/mcp/tools/assets.d.ts +3 -0
- package/dist/mcp/tools/assets.js +54 -0
- package/dist/mcp/tools/collections.d.ts +84 -0
- package/dist/mcp/tools/collections.js +90 -0
- package/dist/mcp/tools/fields.d.ts +101 -0
- package/dist/mcp/tools/fields.js +157 -0
- package/dist/mcp/tools/files.d.ts +235 -0
- package/dist/mcp/tools/files.js +103 -0
- package/dist/mcp/tools/flows.d.ts +323 -0
- package/dist/mcp/tools/flows.js +85 -0
- package/dist/mcp/tools/folders.d.ts +95 -0
- package/dist/mcp/tools/folders.js +96 -0
- package/dist/mcp/tools/index.d.ts +15 -0
- package/dist/mcp/tools/index.js +29 -0
- package/dist/mcp/tools/items.d.ts +87 -0
- package/dist/mcp/tools/items.js +141 -0
- package/dist/mcp/tools/operations.d.ts +171 -0
- package/dist/mcp/tools/operations.js +77 -0
- package/dist/mcp/tools/prompts/assets.md +8 -0
- package/dist/mcp/tools/prompts/collections.md +336 -0
- package/dist/mcp/tools/prompts/fields.md +521 -0
- package/dist/mcp/tools/prompts/files.md +180 -0
- package/dist/mcp/tools/prompts/flows.md +495 -0
- package/dist/mcp/tools/prompts/folders.md +34 -0
- package/dist/mcp/tools/prompts/index.d.ts +16 -0
- package/dist/mcp/tools/prompts/index.js +19 -0
- package/dist/mcp/tools/prompts/items.md +317 -0
- package/dist/mcp/tools/prompts/operations.md +721 -0
- package/dist/mcp/tools/prompts/relations.md +386 -0
- package/dist/mcp/tools/prompts/schema.md +130 -0
- package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
- package/dist/mcp/tools/prompts/system-prompt.md +44 -0
- package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
- package/dist/mcp/tools/relations.d.ts +73 -0
- package/dist/mcp/tools/relations.js +93 -0
- package/dist/mcp/tools/schema.d.ts +54 -0
- package/dist/mcp/tools/schema.js +317 -0
- package/dist/mcp/tools/system.d.ts +3 -0
- package/dist/mcp/tools/system.js +22 -0
- package/dist/mcp/tools/trigger-flow.d.ts +8 -0
- package/dist/mcp/tools/trigger-flow.js +48 -0
- package/dist/mcp/transport.d.ts +13 -0
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/types.d.ts +56 -0
- package/dist/mcp/types.js +1 -0
- package/dist/middleware/respond.js +2 -2
- package/dist/services/authentication.js +36 -0
- package/dist/services/fields.js +4 -4
- package/dist/services/graphql/index.d.ts +2 -2
- package/dist/services/graphql/index.js +6 -5
- package/dist/services/graphql/resolvers/query.js +4 -39
- package/dist/services/items.js +38 -12
- package/dist/services/payload.d.ts +7 -3
- package/dist/services/payload.js +70 -12
- package/dist/services/server.js +1 -0
- package/dist/services/tfa.d.ts +1 -1
- package/dist/services/tfa.js +20 -5
- package/dist/services/versions.d.ts +6 -4
- package/dist/services/versions.js +84 -25
- package/dist/types/auth.d.ts +2 -1
- package/dist/utils/deep-map-response.d.ts +17 -0
- package/dist/utils/deep-map-response.js +61 -0
- package/dist/utils/get-relation-info.d.ts +1 -2
- package/dist/utils/permissions-cacheable.d.ts +8 -0
- package/dist/utils/{permissions-cachable.js → permissions-cacheable.js} +8 -6
- package/dist/utils/transaction.d.ts +1 -1
- package/dist/utils/transaction.js +18 -2
- package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
- package/dist/utils/versioning/deep-map-with-schema.js +81 -0
- package/dist/utils/versioning/handle-version.d.ts +3 -0
- package/dist/utils/versioning/handle-version.js +96 -0
- package/dist/utils/versioning/merge-version-data.d.ts +2 -0
- package/dist/utils/versioning/merge-version-data.js +10 -0
- package/dist/utils/versioning/split-recursive.d.ts +4 -0
- package/dist/utils/versioning/split-recursive.js +27 -0
- package/package.json +31 -30
- package/dist/middleware/merge-content-versions.d.ts +0 -2
- package/dist/middleware/merge-content-versions.js +0 -26
- package/dist/utils/merge-version-data.d.ts +0 -3
- package/dist/utils/merge-version-data.js +0 -134
- package/dist/utils/permissions-cachable.d.ts +0 -8
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { parseFilterFunctionPath } from '@directus/utils';
|
|
2
2
|
import { omit } from 'lodash-es';
|
|
3
|
-
import { mergeVersionsRaw, mergeVersionsRecursive } from '../../../utils/merge-version-data.js';
|
|
4
|
-
import { VersionsService } from '../../versions.js';
|
|
5
3
|
import { parseArgs } from '../schema/parse-args.js';
|
|
6
4
|
import { getQuery } from '../schema/parse-query.js';
|
|
7
5
|
import { getAggregateQuery } from '../utils/aggregate-query.js';
|
|
@@ -19,7 +17,6 @@ export async function resolveQuery(gql, info) {
|
|
|
19
17
|
return null;
|
|
20
18
|
const args = parseArgs(info.fieldNodes[0].arguments || [], info.variableValues);
|
|
21
19
|
let query;
|
|
22
|
-
let versionRaw = false;
|
|
23
20
|
const isAggregate = collection.endsWith('_aggregated') && collection in gql.schema.collections === false;
|
|
24
21
|
if (isAggregate) {
|
|
25
22
|
query = await getAggregateQuery(args, selections, gql.schema, gql.accountability);
|
|
@@ -32,50 +29,18 @@ export async function resolveQuery(gql, info) {
|
|
|
32
29
|
}
|
|
33
30
|
if (collection.endsWith('_by_version') && collection in gql.schema.collections === false) {
|
|
34
31
|
collection = collection.slice(0, -11);
|
|
35
|
-
versionRaw = true;
|
|
32
|
+
query.versionRaw = true;
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
|
-
if (args['id']) {
|
|
39
|
-
query.filter = {
|
|
40
|
-
_and: [
|
|
41
|
-
query.filter || {},
|
|
42
|
-
{
|
|
43
|
-
[gql.schema.collections[collection].primary]: {
|
|
44
|
-
_eq: args['id'],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
query.limit = 1;
|
|
50
|
-
}
|
|
51
35
|
// Transform count(a.b.c) into a.b.count(c)
|
|
52
36
|
if (query.fields?.length) {
|
|
53
37
|
for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
|
|
54
38
|
query.fields[fieldIndex] = parseFilterFunctionPath(query.fields[fieldIndex]);
|
|
55
39
|
}
|
|
56
40
|
}
|
|
57
|
-
const result = await gql.read(collection, query);
|
|
58
|
-
if (args['
|
|
59
|
-
|
|
60
|
-
const saves = await versionsService.getVersionSaves(args['version'], collection, args['id']);
|
|
61
|
-
if (saves) {
|
|
62
|
-
if (gql.schema.collections[collection].singleton) {
|
|
63
|
-
return versionRaw
|
|
64
|
-
? mergeVersionsRaw(result, saves)
|
|
65
|
-
: mergeVersionsRecursive(result, saves, collection, gql.schema);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
if (result?.[0] === undefined)
|
|
69
|
-
return null;
|
|
70
|
-
return versionRaw
|
|
71
|
-
? mergeVersionsRaw(result[0], saves)
|
|
72
|
-
: mergeVersionsRecursive(result[0], saves, collection, gql.schema);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (args['id']) {
|
|
77
|
-
return result?.[0] || null;
|
|
78
|
-
}
|
|
41
|
+
const result = await gql.read(collection, query, args['id']);
|
|
42
|
+
if (args['id'])
|
|
43
|
+
return result;
|
|
79
44
|
if (query.group) {
|
|
80
45
|
// for every entry in result add a group field based on query.group;
|
|
81
46
|
const aggregateKeys = Object.keys(query.aggregate ?? {});
|
package/dist/services/items.js
CHANGED
|
@@ -18,6 +18,7 @@ import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
|
18
18
|
import { transaction } from '../utils/transaction.js';
|
|
19
19
|
import { validateKeys } from '../utils/validate-keys.js';
|
|
20
20
|
import { validateUserCountIntegrity } from '../utils/validate-user-count-integrity.js';
|
|
21
|
+
import { handleVersion } from '../utils/versioning/handle-version.js';
|
|
21
22
|
import { PayloadService } from './payload.js';
|
|
22
23
|
const env = useEnv();
|
|
23
24
|
export class ItemsService {
|
|
@@ -96,8 +97,6 @@ export class ItemsService {
|
|
|
96
97
|
if (!opts.bypassLimits) {
|
|
97
98
|
opts.mutationTracker.trackMutations(1);
|
|
98
99
|
}
|
|
99
|
-
const { ActivityService } = await import('./activity.js');
|
|
100
|
-
const { RevisionsService } = await import('./revisions.js');
|
|
101
100
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
102
101
|
const fields = Object.keys(this.schema.collections[this.collection].fields);
|
|
103
102
|
const aliases = Object.values(this.schema.collections[this.collection].fields)
|
|
@@ -149,6 +148,7 @@ export class ItemsService {
|
|
|
149
148
|
knex: trx,
|
|
150
149
|
schema: this.schema,
|
|
151
150
|
nested: this.nested,
|
|
151
|
+
overwriteDefaults: opts.overwriteDefaults,
|
|
152
152
|
});
|
|
153
153
|
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
154
154
|
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
@@ -226,7 +226,11 @@ export class ItemsService {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
229
|
-
if (
|
|
229
|
+
if (opts.skipTracking !== true &&
|
|
230
|
+
this.accountability &&
|
|
231
|
+
this.schema.collections[this.collection].accountability !== null) {
|
|
232
|
+
const { ActivityService } = await import('./activity.js');
|
|
233
|
+
const { RevisionsService } = await import('./revisions.js');
|
|
230
234
|
const activityService = new ActivityService({
|
|
231
235
|
knex: trx,
|
|
232
236
|
schema: this.schema,
|
|
@@ -267,6 +271,9 @@ export class ItemsService {
|
|
|
267
271
|
if (autoIncrementSequenceNeedsToBeReset) {
|
|
268
272
|
await getHelpers(trx).sequence.resetAutoIncrementSequence(this.collection, primaryKeyField);
|
|
269
273
|
}
|
|
274
|
+
if (opts.onItemCreate) {
|
|
275
|
+
opts.onItemCreate(this.collection, primaryKey);
|
|
276
|
+
}
|
|
270
277
|
return primaryKey;
|
|
271
278
|
});
|
|
272
279
|
if (opts.emitEvents !== false) {
|
|
@@ -332,6 +339,7 @@ export class ItemsService {
|
|
|
332
339
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
333
340
|
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
|
334
341
|
mutationTracker: opts.mutationTracker,
|
|
342
|
+
overwriteDefaults: opts.overwriteDefaults?.[index],
|
|
335
343
|
bypassAutoIncrementSequenceReset,
|
|
336
344
|
});
|
|
337
345
|
primaryKeys.push(primaryKey);
|
|
@@ -427,7 +435,13 @@ export class ItemsService {
|
|
|
427
435
|
validateKeys(this.schema, this.collection, primaryKeyField, key);
|
|
428
436
|
const filterWithKey = assign({}, query.filter, { [primaryKeyField]: { _eq: key } });
|
|
429
437
|
const queryWithKey = assign({}, query, { filter: filterWithKey });
|
|
430
|
-
|
|
438
|
+
let results = [];
|
|
439
|
+
if (query.version) {
|
|
440
|
+
results = [await handleVersion(this, key, queryWithKey, opts)];
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
results = await this.readByQuery(queryWithKey, opts);
|
|
444
|
+
}
|
|
431
445
|
if (results.length === 0) {
|
|
432
446
|
throw new ForbiddenError();
|
|
433
447
|
}
|
|
@@ -485,13 +499,15 @@ export class ItemsService {
|
|
|
485
499
|
await transaction(this.knex, async (knex) => {
|
|
486
500
|
const service = this.fork({ knex });
|
|
487
501
|
let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
|
|
488
|
-
for (const
|
|
502
|
+
for (const index in data) {
|
|
503
|
+
const item = data[index];
|
|
489
504
|
const primaryKey = item[primaryKeyField];
|
|
490
505
|
if (!primaryKey)
|
|
491
506
|
throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
|
|
492
507
|
const combinedOpts = {
|
|
493
508
|
autoPurgeCache: false,
|
|
494
509
|
...opts,
|
|
510
|
+
overwriteDefaults: opts.overwriteDefaults?.[index],
|
|
495
511
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
496
512
|
};
|
|
497
513
|
keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
|
|
@@ -522,8 +538,6 @@ export class ItemsService {
|
|
|
522
538
|
if (!opts.bypassLimits) {
|
|
523
539
|
opts.mutationTracker.trackMutations(keys.length);
|
|
524
540
|
}
|
|
525
|
-
const { ActivityService } = await import('./activity.js');
|
|
526
|
-
const { RevisionsService } = await import('./revisions.js');
|
|
527
541
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
528
542
|
validateKeys(this.schema, this.collection, primaryKeyField, keys);
|
|
529
543
|
const fields = Object.keys(this.schema.collections[this.collection].fields);
|
|
@@ -581,6 +595,7 @@ export class ItemsService {
|
|
|
581
595
|
knex: trx,
|
|
582
596
|
schema: this.schema,
|
|
583
597
|
nested: this.nested,
|
|
598
|
+
overwriteDefaults: opts.overwriteDefaults,
|
|
584
599
|
});
|
|
585
600
|
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
586
601
|
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
@@ -616,7 +631,11 @@ export class ItemsService {
|
|
|
616
631
|
}
|
|
617
632
|
}
|
|
618
633
|
// If this is an authenticated action, and accountability tracking is enabled, save activity row
|
|
619
|
-
if (
|
|
634
|
+
if (opts.skipTracking !== true &&
|
|
635
|
+
this.accountability &&
|
|
636
|
+
this.schema.collections[this.collection].accountability !== null) {
|
|
637
|
+
const { ActivityService } = await import('./activity.js');
|
|
638
|
+
const { RevisionsService } = await import('./revisions.js');
|
|
620
639
|
const activityService = new ActivityService({
|
|
621
640
|
knex: trx,
|
|
622
641
|
schema: this.schema,
|
|
@@ -738,8 +757,13 @@ export class ItemsService {
|
|
|
738
757
|
const primaryKeys = await transaction(this.knex, async (knex) => {
|
|
739
758
|
const service = this.fork({ knex });
|
|
740
759
|
const primaryKeys = [];
|
|
741
|
-
for (const
|
|
742
|
-
const
|
|
760
|
+
for (const index in payloads) {
|
|
761
|
+
const payload = payloads[index];
|
|
762
|
+
const primaryKey = await service.upsertOne(payload, {
|
|
763
|
+
...(opts || {}),
|
|
764
|
+
overwriteDefaults: opts.overwriteDefaults?.[index],
|
|
765
|
+
autoPurgeCache: false,
|
|
766
|
+
});
|
|
743
767
|
primaryKeys.push(primaryKey);
|
|
744
768
|
}
|
|
745
769
|
return primaryKeys;
|
|
@@ -780,7 +804,6 @@ export class ItemsService {
|
|
|
780
804
|
if (!opts.bypassLimits) {
|
|
781
805
|
opts.mutationTracker.trackMutations(keys.length);
|
|
782
806
|
}
|
|
783
|
-
const { ActivityService } = await import('./activity.js');
|
|
784
807
|
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
785
808
|
validateKeys(this.schema, this.collection, primaryKeyField, keys);
|
|
786
809
|
if (this.accountability) {
|
|
@@ -816,7 +839,10 @@ export class ItemsService {
|
|
|
816
839
|
await validateUserCountIntegrity({ flags: opts.userIntegrityCheckFlags, knex: trx });
|
|
817
840
|
}
|
|
818
841
|
}
|
|
819
|
-
if (
|
|
842
|
+
if (opts.skipTracking !== true &&
|
|
843
|
+
this.accountability &&
|
|
844
|
+
this.schema.collections[this.collection].accountability !== null) {
|
|
845
|
+
const { ActivityService } = await import('./activity.js');
|
|
820
846
|
const activityService = new ActivityService({
|
|
821
847
|
knex: trx,
|
|
822
848
|
schema: this.schema,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AbstractServiceOptions, Accountability, Aggregate, FieldOverview, Item, MutationOptions, PayloadAction, PayloadServiceProcessRelationResult, PrimaryKey, SchemaOverview } from '@directus/types';
|
|
1
|
+
import type { AbstractServiceOptions, Accountability, Aggregate, DefaultOverwrite, FieldOverview, Item, MutationOptions, PayloadAction, PayloadServiceProcessRelationResult, PrimaryKey, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { Helpers } from '../database/helpers/index.js';
|
|
4
4
|
type Transformers = {
|
|
@@ -9,6 +9,7 @@ type Transformers = {
|
|
|
9
9
|
accountability: Accountability | null;
|
|
10
10
|
specials: string[];
|
|
11
11
|
helpers: Helpers;
|
|
12
|
+
overwriteDefaults: DefaultOverwrite | undefined;
|
|
12
13
|
}) => Promise<any>;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
@@ -22,7 +23,10 @@ export declare class PayloadService {
|
|
|
22
23
|
collection: string;
|
|
23
24
|
schema: SchemaOverview;
|
|
24
25
|
nested: string[];
|
|
25
|
-
|
|
26
|
+
overwriteDefaults: DefaultOverwrite | undefined;
|
|
27
|
+
constructor(collection: string, options: AbstractServiceOptions & {
|
|
28
|
+
overwriteDefaults?: DefaultOverwrite | undefined;
|
|
29
|
+
});
|
|
26
30
|
transformers: Transformers;
|
|
27
31
|
processValues(action: PayloadAction, payloads: Partial<Item>[]): Promise<Partial<Item>[]>;
|
|
28
32
|
processValues(action: PayloadAction, payload: Partial<Item>): Promise<Partial<Item>>;
|
|
@@ -62,6 +66,6 @@ export declare class PayloadService {
|
|
|
62
66
|
* Transforms the input partial payload to match the output structure, to have consistency
|
|
63
67
|
* between delta and data
|
|
64
68
|
*/
|
|
65
|
-
prepareDelta(
|
|
69
|
+
prepareDelta(delta: Partial<Item>): Promise<string | null>;
|
|
66
70
|
}
|
|
67
71
|
export {};
|
package/dist/services/payload.js
CHANGED
|
@@ -21,6 +21,7 @@ export class PayloadService {
|
|
|
21
21
|
collection;
|
|
22
22
|
schema;
|
|
23
23
|
nested;
|
|
24
|
+
overwriteDefaults;
|
|
24
25
|
constructor(collection, options) {
|
|
25
26
|
this.accountability = options.accountability || null;
|
|
26
27
|
this.knex = options.knex || getDatabase();
|
|
@@ -28,6 +29,7 @@ export class PayloadService {
|
|
|
28
29
|
this.collection = collection;
|
|
29
30
|
this.schema = options.schema;
|
|
30
31
|
this.nested = options.nested ?? [];
|
|
32
|
+
this.overwriteDefaults = options.overwriteDefaults;
|
|
31
33
|
return this;
|
|
32
34
|
}
|
|
33
35
|
transformers = {
|
|
@@ -77,14 +79,14 @@ export class PayloadService {
|
|
|
77
79
|
return value ? '**********' : null;
|
|
78
80
|
return value;
|
|
79
81
|
},
|
|
80
|
-
async 'user-created'({ action, value, accountability }) {
|
|
82
|
+
async 'user-created'({ action, value, accountability, overwriteDefaults }) {
|
|
81
83
|
if (action === 'create')
|
|
82
|
-
return accountability?.user
|
|
84
|
+
return (overwriteDefaults ? overwriteDefaults._user : accountability?.user) ?? null;
|
|
83
85
|
return value;
|
|
84
86
|
},
|
|
85
|
-
async 'user-updated'({ action, value, accountability }) {
|
|
87
|
+
async 'user-updated'({ action, value, accountability, overwriteDefaults }) {
|
|
86
88
|
if (action === 'update')
|
|
87
|
-
return accountability?.user
|
|
89
|
+
return (overwriteDefaults ? overwriteDefaults._user : accountability?.user) ?? null;
|
|
88
90
|
return value;
|
|
89
91
|
},
|
|
90
92
|
async 'role-created'({ action, value, accountability }) {
|
|
@@ -97,14 +99,14 @@ export class PayloadService {
|
|
|
97
99
|
return accountability?.role || null;
|
|
98
100
|
return value;
|
|
99
101
|
},
|
|
100
|
-
async 'date-created'({ action, value, helpers }) {
|
|
102
|
+
async 'date-created'({ action, value, helpers, overwriteDefaults }) {
|
|
101
103
|
if (action === 'create')
|
|
102
|
-
return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
|
|
104
|
+
return new Date(overwriteDefaults ? overwriteDefaults._date : helpers.date.writeTimestamp(new Date().toISOString()));
|
|
103
105
|
return value;
|
|
104
106
|
},
|
|
105
|
-
async 'date-updated'({ action, value, helpers }) {
|
|
107
|
+
async 'date-updated'({ action, value, helpers, overwriteDefaults }) {
|
|
106
108
|
if (action === 'update')
|
|
107
|
-
return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
|
|
109
|
+
return new Date(overwriteDefaults ? overwriteDefaults._date : helpers.date.writeTimestamp(new Date().toISOString()));
|
|
108
110
|
return value;
|
|
109
111
|
},
|
|
110
112
|
async 'cast-csv'({ action, value }) {
|
|
@@ -212,6 +214,7 @@ export class PayloadService {
|
|
|
212
214
|
accountability,
|
|
213
215
|
specials: fieldSpecials,
|
|
214
216
|
helpers: this.helpers,
|
|
217
|
+
overwriteDefaults: this.overwriteDefaults,
|
|
215
218
|
});
|
|
216
219
|
}
|
|
217
220
|
}
|
|
@@ -402,6 +405,11 @@ export class PayloadService {
|
|
|
402
405
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
403
406
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
404
407
|
emitEvents: opts?.emitEvents,
|
|
408
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
409
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
410
|
+
skipTracking: opts?.skipTracking,
|
|
411
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
|
|
412
|
+
onItemCreate: opts?.onItemCreate,
|
|
405
413
|
mutationTracker: opts?.mutationTracker,
|
|
406
414
|
});
|
|
407
415
|
}
|
|
@@ -412,6 +420,11 @@ export class PayloadService {
|
|
|
412
420
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
413
421
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
414
422
|
emitEvents: opts?.emitEvents,
|
|
423
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
424
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
425
|
+
skipTracking: opts?.skipTracking,
|
|
426
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
|
|
427
|
+
onItemCreate: opts?.onItemCreate,
|
|
415
428
|
mutationTracker: opts?.mutationTracker,
|
|
416
429
|
});
|
|
417
430
|
}
|
|
@@ -468,6 +481,11 @@ export class PayloadService {
|
|
|
468
481
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
469
482
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
470
483
|
emitEvents: opts?.emitEvents,
|
|
484
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
485
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
486
|
+
skipTracking: opts?.skipTracking,
|
|
487
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
|
|
488
|
+
onItemCreate: opts?.onItemCreate,
|
|
471
489
|
mutationTracker: opts?.mutationTracker,
|
|
472
490
|
});
|
|
473
491
|
}
|
|
@@ -478,6 +496,11 @@ export class PayloadService {
|
|
|
478
496
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
479
497
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
480
498
|
emitEvents: opts?.emitEvents,
|
|
499
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
500
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
501
|
+
skipTracking: opts?.skipTracking,
|
|
502
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
|
|
503
|
+
onItemCreate: opts?.onItemCreate,
|
|
481
504
|
mutationTracker: opts?.mutationTracker,
|
|
482
505
|
});
|
|
483
506
|
}
|
|
@@ -570,6 +593,11 @@ export class PayloadService {
|
|
|
570
593
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
571
594
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
572
595
|
emitEvents: opts?.emitEvents,
|
|
596
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
597
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
598
|
+
skipTracking: opts?.skipTracking,
|
|
599
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
|
|
600
|
+
onItemCreate: opts?.onItemCreate,
|
|
573
601
|
mutationTracker: opts?.mutationTracker,
|
|
574
602
|
})));
|
|
575
603
|
const query = {
|
|
@@ -596,6 +624,11 @@ export class PayloadService {
|
|
|
596
624
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
597
625
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
598
626
|
emitEvents: opts?.emitEvents,
|
|
627
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
628
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
629
|
+
skipTracking: opts?.skipTracking,
|
|
630
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
|
|
631
|
+
onItemCreate: opts?.onItemCreate,
|
|
599
632
|
mutationTracker: opts?.mutationTracker,
|
|
600
633
|
});
|
|
601
634
|
}
|
|
@@ -605,6 +638,11 @@ export class PayloadService {
|
|
|
605
638
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
606
639
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
607
640
|
emitEvents: opts?.emitEvents,
|
|
641
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
642
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
643
|
+
skipTracking: opts?.skipTracking,
|
|
644
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
|
|
645
|
+
onItemCreate: opts?.onItemCreate,
|
|
608
646
|
mutationTracker: opts?.mutationTracker,
|
|
609
647
|
});
|
|
610
648
|
}
|
|
@@ -648,12 +686,17 @@ export class PayloadService {
|
|
|
648
686
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
649
687
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
650
688
|
emitEvents: opts?.emitEvents,
|
|
689
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
690
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
691
|
+
skipTracking: opts?.skipTracking,
|
|
692
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['create'],
|
|
693
|
+
onItemCreate: opts?.onItemCreate,
|
|
651
694
|
mutationTracker: opts?.mutationTracker,
|
|
652
695
|
});
|
|
653
696
|
}
|
|
654
697
|
if (alterations.update) {
|
|
655
|
-
for (const
|
|
656
|
-
const { [relatedPrimaryKeyField]: key, ...record } =
|
|
698
|
+
for (const index in alterations.update) {
|
|
699
|
+
const { [relatedPrimaryKeyField]: key, ...record } = alterations.update[index];
|
|
657
700
|
const existingRecord = await this.knex
|
|
658
701
|
.select(relatedPrimaryKeyField, relation.field)
|
|
659
702
|
.from(relation.collection)
|
|
@@ -667,6 +710,11 @@ export class PayloadService {
|
|
|
667
710
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
668
711
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
669
712
|
emitEvents: opts?.emitEvents,
|
|
713
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
714
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
715
|
+
skipTracking: opts?.skipTracking,
|
|
716
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['update'][index],
|
|
717
|
+
onItemCreate: opts?.onItemCreate,
|
|
670
718
|
mutationTracker: opts?.mutationTracker,
|
|
671
719
|
});
|
|
672
720
|
}
|
|
@@ -694,6 +742,11 @@ export class PayloadService {
|
|
|
694
742
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
695
743
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
696
744
|
emitEvents: opts?.emitEvents,
|
|
745
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
746
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
747
|
+
skipTracking: opts?.skipTracking,
|
|
748
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['delete'],
|
|
749
|
+
onItemCreate: opts?.onItemCreate,
|
|
697
750
|
mutationTracker: opts?.mutationTracker,
|
|
698
751
|
});
|
|
699
752
|
}
|
|
@@ -703,6 +756,11 @@ export class PayloadService {
|
|
|
703
756
|
onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
|
|
704
757
|
bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
|
|
705
758
|
emitEvents: opts?.emitEvents,
|
|
759
|
+
autoPurgeCache: opts?.autoPurgeCache,
|
|
760
|
+
autoPurgeSystemCache: opts?.autoPurgeSystemCache,
|
|
761
|
+
skipTracking: opts?.skipTracking,
|
|
762
|
+
overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['delete'],
|
|
763
|
+
onItemCreate: opts?.onItemCreate,
|
|
706
764
|
mutationTracker: opts?.mutationTracker,
|
|
707
765
|
});
|
|
708
766
|
}
|
|
@@ -715,8 +773,8 @@ export class PayloadService {
|
|
|
715
773
|
* Transforms the input partial payload to match the output structure, to have consistency
|
|
716
774
|
* between delta and data
|
|
717
775
|
*/
|
|
718
|
-
async prepareDelta(
|
|
719
|
-
let payload = cloneDeep(
|
|
776
|
+
async prepareDelta(delta) {
|
|
777
|
+
let payload = cloneDeep(delta);
|
|
720
778
|
for (const key in payload) {
|
|
721
779
|
if (payload[key]?.isRawInstance) {
|
|
722
780
|
payload[key] = payload[key].bindings[0];
|
package/dist/services/server.js
CHANGED
package/dist/services/tfa.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare class TFAService {
|
|
|
6
6
|
itemsService: ItemsService;
|
|
7
7
|
constructor(options: AbstractServiceOptions);
|
|
8
8
|
verifyOTP(key: PrimaryKey, otp: string, secret?: string): Promise<boolean>;
|
|
9
|
-
generateTFA(key: PrimaryKey): Promise<Record<string, string>>;
|
|
9
|
+
generateTFA(key: PrimaryKey, requiresPassword?: boolean): Promise<Record<string, string>>;
|
|
10
10
|
enableTFA(key: PrimaryKey, otp: string, secret: string): Promise<void>;
|
|
11
11
|
disableTFA(key: PrimaryKey): Promise<void>;
|
|
12
12
|
}
|
package/dist/services/tfa.js
CHANGED
|
@@ -2,6 +2,7 @@ import { InvalidPayloadError } from '@directus/errors';
|
|
|
2
2
|
import { authenticator } from 'otplib';
|
|
3
3
|
import getDatabase from '../database/index.js';
|
|
4
4
|
import { ItemsService } from './items.js';
|
|
5
|
+
import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
|
|
5
6
|
export class TFAService {
|
|
6
7
|
knex;
|
|
7
8
|
itemsService;
|
|
@@ -19,26 +20,40 @@ export class TFAService {
|
|
|
19
20
|
}
|
|
20
21
|
return authenticator.check(otp, user.tfa_secret);
|
|
21
22
|
}
|
|
22
|
-
async generateTFA(key) {
|
|
23
|
-
const user = await this.knex
|
|
23
|
+
async generateTFA(key, requiresPassword = true) {
|
|
24
|
+
const user = await this.knex
|
|
25
|
+
.select('email', 'tfa_secret', 'provider', 'external_identifier')
|
|
26
|
+
.from('directus_users')
|
|
27
|
+
.where({ id: key })
|
|
28
|
+
.first();
|
|
24
29
|
if (user?.tfa_secret !== null) {
|
|
25
30
|
throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
|
|
26
31
|
}
|
|
27
|
-
|
|
32
|
+
// Only require email for non-OAuth users
|
|
33
|
+
if (user?.provider === DEFAULT_AUTH_PROVIDER && !user?.email) {
|
|
28
34
|
throw new InvalidPayloadError({ reason: 'User must have a valid email to enable TFA' });
|
|
29
35
|
}
|
|
36
|
+
if (!requiresPassword && user?.provider === DEFAULT_AUTH_PROVIDER) {
|
|
37
|
+
throw new InvalidPayloadError({ reason: 'This method is only available for OAuth users' });
|
|
38
|
+
}
|
|
30
39
|
const secret = authenticator.generateSecret();
|
|
31
40
|
const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
|
|
41
|
+
// For OAuth users without email, use external_identifier as fallback
|
|
42
|
+
const accountName = user.email || user.external_identifier || `user_${key}`;
|
|
32
43
|
return {
|
|
33
44
|
secret,
|
|
34
|
-
url: authenticator.keyuri(
|
|
45
|
+
url: authenticator.keyuri(accountName, project?.project_name || 'Directus', secret),
|
|
35
46
|
};
|
|
36
47
|
}
|
|
37
48
|
async enableTFA(key, otp, secret) {
|
|
38
|
-
const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
|
|
49
|
+
const user = await this.knex.select('tfa_secret', 'provider').from('directus_users').where({ id: key }).first();
|
|
50
|
+
const requiresPassword = user?.['provider'] === DEFAULT_AUTH_PROVIDER;
|
|
39
51
|
if (user?.tfa_secret !== null) {
|
|
40
52
|
throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
|
|
41
53
|
}
|
|
54
|
+
if (!requiresPassword && user?.provider === DEFAULT_AUTH_PROVIDER) {
|
|
55
|
+
throw new InvalidPayloadError({ reason: 'This method is only available for OAuth users' });
|
|
56
|
+
}
|
|
42
57
|
if (!authenticator.check(otp, secret)) {
|
|
43
58
|
throw new InvalidPayloadError({ reason: `"otp" is invalid` });
|
|
44
59
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey, Query } from '@directus/types';
|
|
1
|
+
import type { AbstractServiceOptions, ContentVersion, Item, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
|
|
2
2
|
import { ItemsService } from './items.js';
|
|
3
|
-
export declare class VersionsService extends ItemsService {
|
|
3
|
+
export declare class VersionsService extends ItemsService<ContentVersion> {
|
|
4
4
|
constructor(options: AbstractServiceOptions);
|
|
5
5
|
private validateCreateData;
|
|
6
6
|
getMainItem(collection: string, item: PrimaryKey, query?: Query): Promise<Item>;
|
|
@@ -8,10 +8,12 @@ export declare class VersionsService extends ItemsService {
|
|
|
8
8
|
outdated: boolean;
|
|
9
9
|
mainHash: string;
|
|
10
10
|
}>;
|
|
11
|
-
|
|
11
|
+
getVersionSave(key: string, collection: string, item: string, mapDelta?: boolean): Promise<ContentVersion | undefined>;
|
|
12
12
|
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
13
|
+
readOne(key: PrimaryKey, query?: Query, opts?: QueryOptions): Promise<ContentVersion>;
|
|
13
14
|
createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
14
15
|
updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
15
|
-
save(key: PrimaryKey,
|
|
16
|
+
save(key: PrimaryKey, delta: Partial<Item>): Promise<Partial<Item>>;
|
|
16
17
|
promote(version: PrimaryKey, mainHash: string, fields?: string[]): Promise<PrimaryKey>;
|
|
18
|
+
private mapDelta;
|
|
17
19
|
}
|