@wbce-d9/api 12.4.4 → 12.5.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/auth/drivers/saml.js +5 -2
- package/dist/cli/commands/init/index.js +1 -1
- package/dist/database/migrations/20260617A-add-date-created-to-users.d.ts +3 -0
- package/dist/database/migrations/20260617A-add-date-created-to-users.js +12 -0
- package/dist/database/system-data/fields/users.yaml +20 -0
- package/dist/database/system-data/relations/relations.yaml +11 -0
- package/dist/services/collections.js +1 -1
- package/dist/services/files.js +1 -1
- package/dist/services/graphql/index.js +6 -2
- package/dist/services/relations.d.ts +8 -0
- package/dist/services/relations.js +23 -1
- package/dist/services/users.js +5 -3
- package/package.json +16 -16
|
@@ -118,9 +118,12 @@ export function createSAMLAuthRouter(providerName) {
|
|
|
118
118
|
const relayState = req.body?.RelayState;
|
|
119
119
|
try {
|
|
120
120
|
const { sp, idp } = getAuthProvider(providerName);
|
|
121
|
-
const { extract } = await sp.parseLoginResponse(idp, 'post',
|
|
121
|
+
const { extract } = await sp.parseLoginResponse(idp, 'post', {
|
|
122
|
+
query: req.query,
|
|
123
|
+
body: req.body,
|
|
124
|
+
});
|
|
122
125
|
const authService = new AuthenticationService({ accountability: req.accountability, schema: req.schema });
|
|
123
|
-
const { accessToken, refreshToken, expires } = await authService.login(providerName, extract.attributes);
|
|
126
|
+
const { accessToken, refreshToken, expires } = await authService.login(providerName, extract.attributes ?? {});
|
|
124
127
|
res.locals['payload'] = {
|
|
125
128
|
data: {
|
|
126
129
|
access_token: accessToken,
|
|
@@ -97,6 +97,6 @@ export default async function init() {
|
|
|
97
97
|
process.stdout.write(`\nThe configuration can be found in ${chalk.green(rootPath + '/.env')}\n`);
|
|
98
98
|
process.stdout.write(`\nStart Directus by running:\n`);
|
|
99
99
|
process.stdout.write(` ${chalk.blue('cd')} ${rootPath}\n`);
|
|
100
|
-
process.stdout.write(` ${chalk.blue('npx
|
|
100
|
+
process.stdout.write(` ${chalk.blue('npx d9')} start\n`);
|
|
101
101
|
process.exit(0);
|
|
102
102
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
3
|
+
table.timestamp('date_created').nullable();
|
|
4
|
+
table.timestamp('date_updated').nullable();
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export async function down(knex) {
|
|
8
|
+
await knex.schema.alterTable('directus_users', (table) => {
|
|
9
|
+
table.dropColumn('date_created');
|
|
10
|
+
table.dropColumn('date_updated');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -164,6 +164,26 @@ fields:
|
|
|
164
164
|
display_options:
|
|
165
165
|
relative: true
|
|
166
166
|
|
|
167
|
+
- field: date_created
|
|
168
|
+
special:
|
|
169
|
+
- date-created
|
|
170
|
+
- cast-timestamp
|
|
171
|
+
width: half
|
|
172
|
+
display: datetime
|
|
173
|
+
readonly: true
|
|
174
|
+
display_options:
|
|
175
|
+
relative: true
|
|
176
|
+
|
|
177
|
+
- field: date_updated
|
|
178
|
+
special:
|
|
179
|
+
- date-updated
|
|
180
|
+
- cast-timestamp
|
|
181
|
+
width: half
|
|
182
|
+
display: datetime
|
|
183
|
+
readonly: true
|
|
184
|
+
display_options:
|
|
185
|
+
relative: true
|
|
186
|
+
|
|
167
187
|
- field: provider
|
|
168
188
|
width: half
|
|
169
189
|
interface: select-dropdown
|
|
@@ -26,6 +26,17 @@ data:
|
|
|
26
26
|
many_field: activity
|
|
27
27
|
one_collection: directus_activity
|
|
28
28
|
one_field: revisions
|
|
29
|
+
# `item` + `collection` form an any-to-one (A2O) relation: `item` holds the
|
|
30
|
+
# referenced record's primary key, `collection` names its collection.
|
|
31
|
+
# `one_allowed_collections` is filled at runtime in RelationsService.stitchRelations.
|
|
32
|
+
- many_collection: directus_revisions
|
|
33
|
+
many_field: item
|
|
34
|
+
one_collection_field: collection
|
|
35
|
+
one_allowed_collections: []
|
|
36
|
+
- many_collection: directus_activity
|
|
37
|
+
many_field: item
|
|
38
|
+
one_collection_field: collection
|
|
39
|
+
one_allowed_collections: []
|
|
29
40
|
- many_collection: directus_presets
|
|
30
41
|
many_field: user
|
|
31
42
|
one_collection: directus_users
|
|
@@ -508,7 +508,7 @@ export class CollectionsService {
|
|
|
508
508
|
}
|
|
509
509
|
}
|
|
510
510
|
const a2oRelationsThatIncludeThisCollection = this.schema.relations.filter((relation) => {
|
|
511
|
-
return relation.meta?.one_allowed_collections?.includes(collectionKey);
|
|
511
|
+
return !relation.meta?.system && relation.meta?.one_allowed_collections?.includes(collectionKey);
|
|
512
512
|
});
|
|
513
513
|
for (const relation of a2oRelationsThatIncludeThisCollection) {
|
|
514
514
|
const newAllowedCollections = relation
|
package/dist/services/files.js
CHANGED
|
@@ -224,7 +224,7 @@ export class FilesService extends ItemsService {
|
|
|
224
224
|
const payload = {
|
|
225
225
|
filename_download: filename,
|
|
226
226
|
storage: toArray(env['STORAGE_LOCATIONS'])[0],
|
|
227
|
-
type: fileResponse.headers['content-type'],
|
|
227
|
+
type: fileResponse.headers['content-type'] ?? null,
|
|
228
228
|
title: formatTitle(filename),
|
|
229
229
|
...(body || {}),
|
|
230
230
|
};
|
|
@@ -369,7 +369,9 @@ export class GraphQLService {
|
|
|
369
369
|
[relation.field]: {
|
|
370
370
|
type: new GraphQLUnionType({
|
|
371
371
|
name: `${relation.collection}_${relation.field}_union`,
|
|
372
|
-
types: relation.meta.one_allowed_collections
|
|
372
|
+
types: relation.meta.one_allowed_collections
|
|
373
|
+
.filter((collection) => collection in CollectionTypes)
|
|
374
|
+
.map((collection) => CollectionTypes[collection].getType()),
|
|
373
375
|
resolveType(_value, context, info) {
|
|
374
376
|
let path = [];
|
|
375
377
|
let currentPath = info.path;
|
|
@@ -383,7 +385,7 @@ export class GraphQLService {
|
|
|
383
385
|
parent = parent[pathPart];
|
|
384
386
|
}
|
|
385
387
|
const collection = parent[relation.meta.one_collection_field];
|
|
386
|
-
return CollectionTypes[collection]
|
|
388
|
+
return CollectionTypes[collection]?.getType().name;
|
|
387
389
|
},
|
|
388
390
|
}),
|
|
389
391
|
resolve: (obj, _, __, info) => {
|
|
@@ -921,6 +923,8 @@ export class GraphQLService {
|
|
|
921
923
|
else if (relation.meta?.one_allowed_collections) {
|
|
922
924
|
ReadableCollectionFilterTypes[relation.collection]?.removeField('item');
|
|
923
925
|
for (const collection of relation.meta.one_allowed_collections) {
|
|
926
|
+
if (!(collection in ReadableCollectionFilterTypes))
|
|
927
|
+
continue;
|
|
924
928
|
ReadableCollectionFilterTypes[relation.collection]?.addFields({
|
|
925
929
|
[`item__${collection}`]: ReadableCollectionFilterTypes[collection],
|
|
926
930
|
});
|
|
@@ -8,6 +8,14 @@ import type { QueryOptions } from './items.js';
|
|
|
8
8
|
import { ItemsService } from './items.js';
|
|
9
9
|
import { PermissionsService } from './permissions.js';
|
|
10
10
|
export declare class RelationsService {
|
|
11
|
+
/**
|
|
12
|
+
* A2O relations whose `one_allowed_collections` is filled with all collections at read time
|
|
13
|
+
* (see stitchRelations). Identified by collection + field rather than a sentinel value.
|
|
14
|
+
*/
|
|
15
|
+
static readonly expandAllRelations: {
|
|
16
|
+
collection: string;
|
|
17
|
+
field: string;
|
|
18
|
+
}[];
|
|
11
19
|
knex: Knex;
|
|
12
20
|
permissionsService: PermissionsService;
|
|
13
21
|
schemaInspector: SchemaInspector;
|
|
@@ -10,7 +10,15 @@ 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';
|
|
12
12
|
import { PermissionsService } from './permissions.js';
|
|
13
|
-
|
|
13
|
+
class RelationsService {
|
|
14
|
+
/**
|
|
15
|
+
* A2O relations whose `one_allowed_collections` is filled with all collections at read time
|
|
16
|
+
* (see stitchRelations). Identified by collection + field rather than a sentinel value.
|
|
17
|
+
*/
|
|
18
|
+
static expandAllRelations = [
|
|
19
|
+
{ collection: 'directus_activity', field: 'item' },
|
|
20
|
+
{ collection: 'directus_revisions', field: 'item' },
|
|
21
|
+
];
|
|
14
22
|
knex;
|
|
15
23
|
permissionsService;
|
|
16
24
|
schemaInspector;
|
|
@@ -383,6 +391,19 @@ export class RelationsService {
|
|
|
383
391
|
};
|
|
384
392
|
});
|
|
385
393
|
results.push(...remainingMetaRows);
|
|
394
|
+
/**
|
|
395
|
+
* `directus_activity.item` and `directus_revisions.item` are any-to-one (A2O) relations whose
|
|
396
|
+
* `item` can reference any collection. We declare them as system relations (see relations.yaml)
|
|
397
|
+
* but their `one_allowed_collections` can't be hardcoded, so we fill it with the current set of
|
|
398
|
+
* collections here, in memory, on every read. This powers A2O filtering/permissions and the
|
|
399
|
+
* app's per-collection filter drill-down without persisting an ever-stale list.
|
|
400
|
+
*/
|
|
401
|
+
for (const relation of results) {
|
|
402
|
+
const isExpandAll = RelationsService.expandAllRelations.some((r) => r.collection === relation.collection && r.field === relation.field);
|
|
403
|
+
if (isExpandAll && relation.meta) {
|
|
404
|
+
relation.meta.one_allowed_collections = Object.keys(this.schema.collections);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
386
407
|
return results;
|
|
387
408
|
}
|
|
388
409
|
/**
|
|
@@ -445,3 +466,4 @@ export class RelationsService {
|
|
|
445
466
|
}
|
|
446
467
|
}
|
|
447
468
|
}
|
|
469
|
+
export { RelationsService };
|
package/dist/services/users.js
CHANGED
|
@@ -294,17 +294,19 @@ export class UsersService extends ItemsService {
|
|
|
294
294
|
});
|
|
295
295
|
for (const email of emails) {
|
|
296
296
|
// Check if user is known
|
|
297
|
-
|
|
297
|
+
let user = await this.getUserByEmail(email);
|
|
298
|
+
const isEmptyUser = isEmpty(user);
|
|
298
299
|
// Create user first to verify uniqueness if unknown
|
|
299
|
-
if (
|
|
300
|
+
if (isEmptyUser) {
|
|
300
301
|
await this.createOne({ email, role, status: 'invited' }, opts);
|
|
302
|
+
user = await this.getUserByEmail(email);
|
|
301
303
|
// For known users update role if changed
|
|
302
304
|
}
|
|
303
305
|
else if (user.status === 'invited' && user.role !== role) {
|
|
304
306
|
await this.updateOne(user.id, { role }, opts);
|
|
305
307
|
}
|
|
306
308
|
// Send invite for new and already invited users
|
|
307
|
-
if (
|
|
309
|
+
if (isEmptyUser || user.status === 'invited') {
|
|
308
310
|
const subjectLine = subject ?? "You've been invited";
|
|
309
311
|
await mailService.send({
|
|
310
312
|
to: user.email,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wbce-d9/api",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.5.0",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"@rollup/plugin-node-resolve": "15.0.2",
|
|
67
67
|
"@rollup/plugin-virtual": "3.0.1",
|
|
68
68
|
"@wbce-d9/format-title": "10.0.0",
|
|
69
|
-
"axios": "^1.
|
|
69
|
+
"axios": "^1.16.0",
|
|
70
70
|
"chalk": "5.2.0",
|
|
71
71
|
"commander": "10.0.1",
|
|
72
72
|
"date-fns": "2.29.3",
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"express": "^4.21.2",
|
|
75
75
|
"fs-extra": "11.1.1",
|
|
76
76
|
"inquirer": "9.1.5",
|
|
77
|
-
"joi": "
|
|
78
|
-
"js-yaml": "4.
|
|
77
|
+
"joi": "18.2.1",
|
|
78
|
+
"js-yaml": "4.2.0",
|
|
79
79
|
"json2csv": "5.0.7",
|
|
80
80
|
"knex": "3.1.0",
|
|
81
81
|
"lodash-es": "4.18.1",
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"ora": "6.3.0",
|
|
87
87
|
"pino": "8.11.0",
|
|
88
88
|
"rollup": "3.30.0",
|
|
89
|
-
"uuid": "
|
|
89
|
+
"uuid": "14.0.0",
|
|
90
90
|
"wellknown": "0.5.0",
|
|
91
91
|
"@authenio/samlify-node-xmllint": "2.0.0",
|
|
92
|
-
"@aws-sdk/client-sesv2": "3.
|
|
92
|
+
"@aws-sdk/client-sesv2": "3.1062.0",
|
|
93
93
|
"@godaddy/terminus": "4.12.0",
|
|
94
94
|
"@rollup/plugin-alias": "5.0.0",
|
|
95
95
|
"argon2": "0.44.0",
|
|
@@ -120,21 +120,21 @@
|
|
|
120
120
|
"jsonwebtoken": "9.0.0",
|
|
121
121
|
"keyv": "4.5.2",
|
|
122
122
|
"ldapjs": "2.3.3",
|
|
123
|
-
"liquidjs": "10.
|
|
123
|
+
"liquidjs": "10.26.0",
|
|
124
124
|
"mime-types": "2.1.35",
|
|
125
125
|
"ms": "2.1.3",
|
|
126
126
|
"node-cron": "3.0.2",
|
|
127
127
|
"node-machine-id": "1.1.12",
|
|
128
|
-
"nodemailer": "
|
|
128
|
+
"nodemailer": "8.0.5",
|
|
129
129
|
"object-hash": "3.0.0",
|
|
130
130
|
"openid-client": "6.8.2",
|
|
131
131
|
"otplib": "12.0.1",
|
|
132
132
|
"pino-http": "8.3.3",
|
|
133
133
|
"pino-http-print": "3.1.0",
|
|
134
134
|
"pino-pretty": "10.0.0",
|
|
135
|
-
"qs": "6.
|
|
135
|
+
"qs": "6.15.2",
|
|
136
136
|
"rate-limiter-flexible": "2.4.1",
|
|
137
|
-
"samlify": "2.
|
|
137
|
+
"samlify": "2.13.0",
|
|
138
138
|
"sanitize-html": "2.12.1",
|
|
139
139
|
"sharp": "0.32.6",
|
|
140
140
|
"snappy": "7.2.2",
|
|
@@ -143,19 +143,19 @@
|
|
|
143
143
|
"tmp-promise": "3.0.3",
|
|
144
144
|
"tsx": "3.12.6",
|
|
145
145
|
"uuid-validate": "0.0.3",
|
|
146
|
-
"@wbce-d9/app": "10.0
|
|
147
|
-
"@wbce-d9/constants": "10.0.0",
|
|
146
|
+
"@wbce-d9/app": "10.1.0",
|
|
148
147
|
"@wbce-d9/exceptions": "10.0.0",
|
|
149
|
-
"@wbce-d9/extensions-sdk": "10.0.2",
|
|
150
148
|
"@wbce-d9/schema": "10.0.0",
|
|
151
149
|
"@wbce-d9/specs": "9.26.5",
|
|
152
|
-
"@wbce-d9/storage": "10.0.0",
|
|
153
150
|
"@wbce-d9/storage-driver-azure": "10.0.1",
|
|
154
|
-
"@wbce-d9/
|
|
151
|
+
"@wbce-d9/extensions-sdk": "10.0.2",
|
|
152
|
+
"@wbce-d9/constants": "10.0.0",
|
|
155
153
|
"@wbce-d9/storage-driver-cloudinary": "10.0.2",
|
|
154
|
+
"@wbce-d9/storage-driver-gcs": "10.0.1",
|
|
156
155
|
"@wbce-d9/storage-driver-local": "10.0.1",
|
|
157
156
|
"@wbce-d9/storage-driver-s3": "10.0.1",
|
|
158
157
|
"@wbce-d9/utils": "10.0.1",
|
|
158
|
+
"@wbce-d9/storage": "10.0.0",
|
|
159
159
|
"@wbce-d9/update-check": "10.0.2"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
"@types/stream-json": "1.7.3",
|
|
203
203
|
"@types/uuid-validate": "0.0.1",
|
|
204
204
|
"copyfiles": "2.4.1",
|
|
205
|
-
"form-data": "4.0.
|
|
205
|
+
"form-data": "4.0.6",
|
|
206
206
|
"knex-mock-client": "3.0.2",
|
|
207
207
|
"@wbce-d9/types": "10.1.0"
|
|
208
208
|
},
|