directus 9.3.0 → 9.4.3
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 +25 -4
- package/dist/auth/auth.d.ts +4 -6
- package/dist/auth/auth.js +5 -9
- package/dist/auth/drivers/ldap.d.ts +3 -3
- package/dist/auth/drivers/ldap.js +2 -3
- package/dist/auth/drivers/local.d.ts +2 -2
- package/dist/auth/drivers/local.js +7 -13
- package/dist/auth/drivers/oauth2.d.ts +3 -3
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.d.ts +3 -3
- package/dist/auth/drivers/openid.js +4 -4
- package/dist/cache.js +1 -3
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +16 -2
- package/dist/controllers/activity.js +2 -1
- package/dist/controllers/auth.js +5 -4
- package/dist/controllers/extensions.js +1 -1
- package/dist/controllers/shares.d.ts +2 -0
- package/dist/controllers/shares.js +212 -0
- package/dist/controllers/users.js +21 -9
- package/dist/database/index.js +3 -0
- package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
- package/dist/database/migrations/20211211A-add-shares.js +38 -0
- package/dist/database/migrations/20211230A-add-project-descriptor.d.ts +3 -0
- package/dist/database/migrations/20211230A-add-project-descriptor.js +15 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.js +5 -5
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
- package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
- package/dist/database/system-data/app-access-permissions/index.js +4 -2
- package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
- package/dist/database/system-data/collections/collections.yaml +3 -0
- package/dist/database/system-data/fields/_defaults.yaml +2 -0
- package/dist/database/system-data/fields/sessions.yaml +1 -1
- package/dist/database/system-data/fields/settings.yaml +20 -1
- package/dist/database/system-data/fields/shares.yaml +77 -0
- package/dist/database/system-data/fields/users.yaml +1 -1
- package/dist/database/system-data/relations/relations.yaml +15 -0
- package/dist/env.js +4 -1
- package/dist/extensions.d.ts +11 -6
- package/dist/extensions.js +97 -42
- package/dist/middleware/authenticate.js +7 -16
- package/dist/middleware/check-ip.js +9 -6
- package/dist/middleware/rate-limiter.js +2 -1
- package/dist/middleware/respond.js +4 -1
- package/dist/services/activity.d.ts +2 -1
- package/dist/services/activity.js +2 -2
- package/dist/services/assets.js +3 -3
- package/dist/services/authentication.d.ts +2 -7
- package/dist/services/authentication.js +84 -41
- package/dist/services/authorization.js +3 -3
- package/dist/services/collections.d.ts +1 -2
- package/dist/services/collections.js +2 -2
- package/dist/services/files.d.ts +2 -2
- package/dist/services/graphql.d.ts +1 -1
- package/dist/services/graphql.js +51 -10
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.d.ts +1 -15
- package/dist/services/notifications.d.ts +2 -2
- package/dist/services/permissions.d.ts +2 -2
- package/dist/services/roles.d.ts +2 -2
- package/dist/services/server.js +1 -0
- package/dist/services/shares.d.ts +17 -0
- package/dist/services/shares.js +135 -0
- package/dist/services/specifications.js +1 -1
- package/dist/services/users.d.ts +2 -2
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/types/ast.d.ts +3 -3
- package/dist/types/auth.d.ts +31 -0
- package/dist/types/items.d.ts +14 -0
- package/dist/utils/apply-query.d.ts +0 -38
- package/dist/utils/apply-query.js +66 -67
- package/dist/utils/get-ast-from-query.js +3 -3
- package/dist/utils/get-default-value.js +3 -1
- package/dist/utils/get-ip-from-req.d.ts +2 -0
- package/dist/utils/get-ip-from-req.js +24 -0
- package/dist/utils/get-local-type.js +1 -1
- package/dist/utils/get-permissions.js +15 -7
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-relation-type.js +1 -1
- package/dist/utils/merge-permissions-for-share.d.ts +5 -0
- package/dist/utils/merge-permissions-for-share.js +116 -0
- package/dist/utils/merge-permissions.d.ts +13 -1
- package/dist/utils/merge-permissions.js +27 -19
- package/dist/utils/reduce-schema.d.ts +2 -2
- package/dist/utils/reduce-schema.js +7 -7
- package/dist/utils/user-name.js +3 -0
- package/example.env +1 -1
- package/package.json +15 -13
|
@@ -21,7 +21,6 @@ const settings_1 = require("./settings");
|
|
|
21
21
|
const lodash_1 = require("lodash");
|
|
22
22
|
const perf_hooks_1 = require("perf_hooks");
|
|
23
23
|
const stall_1 = require("../utils/stall");
|
|
24
|
-
const logger_1 = __importDefault(require("../logger"));
|
|
25
24
|
const loginAttemptsLimiter = (0, rate_limiter_1.createRateLimiter)({ duration: 0 });
|
|
26
25
|
class AuthenticationService {
|
|
27
26
|
constructor(options) {
|
|
@@ -42,10 +41,11 @@ class AuthenticationService {
|
|
|
42
41
|
const timeStart = perf_hooks_1.performance.now();
|
|
43
42
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
44
43
|
const user = await this.knex
|
|
45
|
-
.select('id', 'first_name', 'last_name', 'email', 'password', 'status', 'role', 'tfa_secret', 'provider', 'external_identifier', 'auth_data')
|
|
46
|
-
.from('directus_users')
|
|
47
|
-
.
|
|
48
|
-
.
|
|
44
|
+
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'r.admin_access', 'r.app_access', 'u.tfa_secret', 'u.provider', 'u.external_identifier', 'u.auth_data')
|
|
45
|
+
.from('directus_users as u')
|
|
46
|
+
.leftJoin('directus_roles as r', 'u.role', 'r.id')
|
|
47
|
+
.where('u.id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
|
|
48
|
+
.andWhere('u.provider', providerName)
|
|
49
49
|
.first();
|
|
50
50
|
const updatedPayload = await emitter_1.default.emitFilter('auth.login', payload, {
|
|
51
51
|
status: 'pending',
|
|
@@ -98,9 +98,8 @@ class AuthenticationService {
|
|
|
98
98
|
await loginAttemptsLimiter.set(user.id, 0, 0);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
let sessionData = null;
|
|
102
101
|
try {
|
|
103
|
-
|
|
102
|
+
await provider.login((0, lodash_1.clone)(user), (0, lodash_1.cloneDeep)(updatedPayload));
|
|
104
103
|
}
|
|
105
104
|
catch (e) {
|
|
106
105
|
emitStatus('fail');
|
|
@@ -123,6 +122,9 @@ class AuthenticationService {
|
|
|
123
122
|
}
|
|
124
123
|
const tokenPayload = {
|
|
125
124
|
id: user.id,
|
|
125
|
+
role: user.role,
|
|
126
|
+
app_access: user.app_access,
|
|
127
|
+
admin_access: user.admin_access,
|
|
126
128
|
};
|
|
127
129
|
const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
|
|
128
130
|
status: 'pending',
|
|
@@ -146,7 +148,6 @@ class AuthenticationService {
|
|
|
146
148
|
expires: refreshTokenExpiration,
|
|
147
149
|
ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
|
|
148
150
|
user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
|
|
149
|
-
data: sessionData && JSON.stringify(sessionData),
|
|
150
151
|
});
|
|
151
152
|
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
|
152
153
|
if (this.accountability) {
|
|
@@ -177,33 +178,83 @@ class AuthenticationService {
|
|
|
177
178
|
throw new exceptions_1.InvalidCredentialsException();
|
|
178
179
|
}
|
|
179
180
|
const record = await this.knex
|
|
180
|
-
.select(
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
.select({
|
|
182
|
+
session_expires: 's.expires',
|
|
183
|
+
user_id: 'u.id',
|
|
184
|
+
user_first_name: 'u.first_name',
|
|
185
|
+
user_last_name: 'u.last_name',
|
|
186
|
+
user_email: 'u.email',
|
|
187
|
+
user_password: 'u.password',
|
|
188
|
+
user_status: 'u.status',
|
|
189
|
+
user_provider: 'u.provider',
|
|
190
|
+
user_external_identifier: 'u.external_identifier',
|
|
191
|
+
user_auth_data: 'u.auth_data',
|
|
192
|
+
role_id: 'r.id',
|
|
193
|
+
role_admin_access: 'r.admin_access',
|
|
194
|
+
role_app_access: 'r.app_access',
|
|
195
|
+
share_id: 'd.id',
|
|
196
|
+
share_item: 'd.item',
|
|
197
|
+
share_role: 'd.role',
|
|
198
|
+
share_collection: 'd.collection',
|
|
199
|
+
share_start: 'd.date_start',
|
|
200
|
+
share_end: 'd.date_end',
|
|
201
|
+
share_times_used: 'd.times_used',
|
|
202
|
+
share_max_uses: 'd.max_uses',
|
|
203
|
+
})
|
|
204
|
+
.from('directus_sessions AS s')
|
|
205
|
+
.leftJoin('directus_users AS u', 's.user', 'u.id')
|
|
206
|
+
.leftJoin('directus_shares AS d', 's.share', 'd.id')
|
|
207
|
+
.joinRaw('LEFT JOIN directus_roles AS r ON r.id IN (u.role, d.role)')
|
|
183
208
|
.where('s.token', refreshToken)
|
|
209
|
+
.andWhere('s.expires', '>=', new Date())
|
|
210
|
+
.andWhere((subQuery) => {
|
|
211
|
+
subQuery.whereNull('d.date_end').orWhere('d.date_end', '>=', new Date());
|
|
212
|
+
})
|
|
213
|
+
.andWhere((subQuery) => {
|
|
214
|
+
subQuery.whereNull('d.date_start').orWhere('d.date_start', '<=', new Date());
|
|
215
|
+
})
|
|
184
216
|
.first();
|
|
185
|
-
if (!record || record.
|
|
217
|
+
if (!record || (!record.share_id && !record.user_id)) {
|
|
186
218
|
throw new exceptions_1.InvalidCredentialsException();
|
|
187
219
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
220
|
+
if (record.user_id) {
|
|
221
|
+
const provider = (0, auth_1.getAuthProvider)(record.user_provider);
|
|
222
|
+
await provider.refresh({
|
|
223
|
+
id: record.user_id,
|
|
224
|
+
first_name: record.user_first_name,
|
|
225
|
+
last_name: record.user_last_name,
|
|
226
|
+
email: record.user_email,
|
|
227
|
+
password: record.user_password,
|
|
228
|
+
status: record.user_status,
|
|
229
|
+
provider: record.user_provider,
|
|
230
|
+
external_identifier: record.user_external_identifier,
|
|
231
|
+
auth_data: record.user_auth_data,
|
|
232
|
+
role: record.role_id,
|
|
233
|
+
app_access: record.role_app_access,
|
|
234
|
+
admin_access: record.role_admin_access,
|
|
235
|
+
});
|
|
197
236
|
}
|
|
198
|
-
const provider = (0, auth_1.getAuthProvider)(user.provider);
|
|
199
|
-
const newSessionData = await provider.refresh((0, lodash_1.clone)(user), sessionData);
|
|
200
237
|
const tokenPayload = {
|
|
201
|
-
id:
|
|
238
|
+
id: record.user_id,
|
|
239
|
+
role: record.role_id,
|
|
240
|
+
app_access: record.role_app_access,
|
|
241
|
+
admin_access: record.role_admin_access,
|
|
202
242
|
};
|
|
243
|
+
if (record.share_id) {
|
|
244
|
+
tokenPayload.share = record.share_id;
|
|
245
|
+
tokenPayload.role = record.share_role;
|
|
246
|
+
tokenPayload.share_scope = {
|
|
247
|
+
collection: record.share_collection,
|
|
248
|
+
item: record.share_item,
|
|
249
|
+
};
|
|
250
|
+
tokenPayload.app_access = false;
|
|
251
|
+
tokenPayload.admin_access = false;
|
|
252
|
+
delete tokenPayload.id;
|
|
253
|
+
}
|
|
203
254
|
const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
|
|
204
255
|
status: 'pending',
|
|
205
|
-
user:
|
|
206
|
-
provider:
|
|
256
|
+
user: record.user_id,
|
|
257
|
+
provider: record.user_provider,
|
|
207
258
|
type: 'refresh',
|
|
208
259
|
}, {
|
|
209
260
|
database: this.knex,
|
|
@@ -220,37 +271,29 @@ class AuthenticationService {
|
|
|
220
271
|
.update({
|
|
221
272
|
token: newRefreshToken,
|
|
222
273
|
expires: refreshTokenExpiration,
|
|
223
|
-
data: newSessionData && JSON.stringify(newSessionData),
|
|
224
274
|
})
|
|
225
275
|
.where({ token: refreshToken });
|
|
226
|
-
|
|
276
|
+
if (record.user_id) {
|
|
277
|
+
await this.knex('directus_users').update({ last_access: new Date() }).where({ id: record.user_id });
|
|
278
|
+
}
|
|
227
279
|
return {
|
|
228
280
|
accessToken,
|
|
229
281
|
refreshToken: newRefreshToken,
|
|
230
282
|
expires: (0, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
|
|
231
|
-
id:
|
|
283
|
+
id: record.user_id,
|
|
232
284
|
};
|
|
233
285
|
}
|
|
234
286
|
async logout(refreshToken) {
|
|
235
287
|
const record = await this.knex
|
|
236
|
-
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'u.provider', 'u.external_identifier', 'u.auth_data'
|
|
288
|
+
.select('u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'u.provider', 'u.external_identifier', 'u.auth_data')
|
|
237
289
|
.from('directus_sessions as s')
|
|
238
290
|
.innerJoin('directus_users as u', 's.user', 'u.id')
|
|
239
291
|
.where('s.token', refreshToken)
|
|
240
292
|
.first();
|
|
241
293
|
if (record) {
|
|
242
|
-
|
|
243
|
-
const user = (0, lodash_1.omit)(record, 'data');
|
|
244
|
-
if (typeof sessionData === 'string') {
|
|
245
|
-
try {
|
|
246
|
-
sessionData = JSON.parse(sessionData);
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
logger_1.default.warn(`Session data isn't valid JSON: ${sessionData}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
294
|
+
const user = record;
|
|
252
295
|
const provider = (0, auth_1.getAuthProvider)(user.provider);
|
|
253
|
-
await provider.logout((0, lodash_1.clone)(user)
|
|
296
|
+
await provider.logout((0, lodash_1.clone)(user));
|
|
254
297
|
await this.knex.delete().from('directus_sessions').where('token', refreshToken);
|
|
255
298
|
}
|
|
256
299
|
}
|
|
@@ -41,7 +41,7 @@ class AuthorizationService {
|
|
|
41
41
|
*/
|
|
42
42
|
function getCollectionsFromAST(ast) {
|
|
43
43
|
const collections = [];
|
|
44
|
-
if (ast.type === '
|
|
44
|
+
if (ast.type === 'a2o') {
|
|
45
45
|
collections.push(...ast.names.map((name) => ({ collection: name, field: ast.fieldKey })));
|
|
46
46
|
for (const children of Object.values(ast.children)) {
|
|
47
47
|
for (const nestedNode of children) {
|
|
@@ -67,7 +67,7 @@ class AuthorizationService {
|
|
|
67
67
|
function validateFields(ast) {
|
|
68
68
|
var _a, _b, _c;
|
|
69
69
|
if (ast.type !== 'field') {
|
|
70
|
-
if (ast.type === '
|
|
70
|
+
if (ast.type === 'a2o') {
|
|
71
71
|
for (const [collection, children] of Object.entries(ast.children)) {
|
|
72
72
|
checkFields(collection, children, (_b = (_a = ast.query) === null || _a === void 0 ? void 0 : _a[collection]) === null || _b === void 0 ? void 0 : _b.aggregate);
|
|
73
73
|
}
|
|
@@ -106,7 +106,7 @@ class AuthorizationService {
|
|
|
106
106
|
}
|
|
107
107
|
function applyFilters(ast, accountability) {
|
|
108
108
|
if (ast.type !== 'field') {
|
|
109
|
-
if (ast.type === '
|
|
109
|
+
if (ast.type === 'a2o') {
|
|
110
110
|
const collections = Object.keys(ast.children);
|
|
111
111
|
for (const collection of collections) {
|
|
112
112
|
updateFilterQuery(collection, ast.query[collection]);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import SchemaInspector from '@directus/schema';
|
|
2
2
|
import { Knex } from 'knex';
|
|
3
|
-
import { MutationOptions } from '../services/items';
|
|
4
3
|
import Keyv from 'keyv';
|
|
5
|
-
import { AbstractServiceOptions, Collection, CollectionMeta, SchemaOverview } from '../types';
|
|
4
|
+
import { AbstractServiceOptions, Collection, CollectionMeta, SchemaOverview, MutationOptions } from '../types';
|
|
6
5
|
import { Accountability, RawField } from '@directus/shared/types';
|
|
7
6
|
import { Table } from 'knex-schema-inspector/dist/types/table';
|
|
8
7
|
export declare type RawCollection = {
|
|
@@ -359,11 +359,11 @@ class CollectionsService {
|
|
|
359
359
|
await fieldsService.deleteField(relation.collection, relation.field);
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
-
const
|
|
362
|
+
const a2oRelationsThatIncludeThisCollection = this.schema.relations.filter((relation) => {
|
|
363
363
|
var _a, _b;
|
|
364
364
|
return (_b = (_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_allowed_collections) === null || _b === void 0 ? void 0 : _b.includes(collectionKey);
|
|
365
365
|
});
|
|
366
|
-
for (const relation of
|
|
366
|
+
for (const relation of a2oRelationsThatIncludeThisCollection) {
|
|
367
367
|
const newAllowedCollections = relation
|
|
368
368
|
.meta.one_allowed_collections.filter((collection) => collectionKey !== collection)
|
|
369
369
|
.join(',');
|
package/dist/services/files.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { AbstractServiceOptions, File, PrimaryKey } from '../types';
|
|
3
|
-
import { ItemsService
|
|
2
|
+
import { AbstractServiceOptions, File, PrimaryKey, MutationOptions } from '../types';
|
|
3
|
+
import { ItemsService } from './items';
|
|
4
4
|
export declare class FilesService extends ItemsService {
|
|
5
5
|
constructor(options: AbstractServiceOptions);
|
|
6
6
|
/**
|
|
@@ -70,7 +70,7 @@ export declare class GraphQLService {
|
|
|
70
70
|
* Effectively merges the selections with the fragments used in those selections
|
|
71
71
|
*/
|
|
72
72
|
replaceFragmentsInSelections(selections: readonly SelectionNode[] | undefined, fragments: Record<string, FragmentDefinitionNode>): readonly SelectionNode[] | null;
|
|
73
|
-
injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, DeleteCollectionTypes, }: {
|
|
73
|
+
injectSystemResolvers(schemaComposer: SchemaComposer<GraphQLParams['contextValue']>, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }: {
|
|
74
74
|
CreateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
75
75
|
ReadCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
76
76
|
UpdateCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
package/dist/services/graphql.js
CHANGED
|
@@ -34,6 +34,7 @@ const revisions_1 = require("./revisions");
|
|
|
34
34
|
const roles_1 = require("./roles");
|
|
35
35
|
const server_1 = require("./server");
|
|
36
36
|
const settings_1 = require("./settings");
|
|
37
|
+
const shares_1 = require("./shares");
|
|
37
38
|
const specifications_1 = require("./specifications");
|
|
38
39
|
const tfa_1 = require("./tfa");
|
|
39
40
|
const users_1 = require("./users");
|
|
@@ -112,15 +113,23 @@ class GraphQLService {
|
|
|
112
113
|
return formattedResult;
|
|
113
114
|
}
|
|
114
115
|
getSchema(type = 'schema') {
|
|
115
|
-
var _a, _b, _c, _d;
|
|
116
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
116
117
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
117
118
|
const self = this;
|
|
118
119
|
const schemaComposer = new graphql_compose_1.SchemaComposer();
|
|
119
120
|
const schema = {
|
|
120
|
-
read: ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) === true
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
read: ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) === true
|
|
122
|
+
? this.schema
|
|
123
|
+
: (0, reduce_schema_1.reduceSchema)(this.schema, ((_b = this.accountability) === null || _b === void 0 ? void 0 : _b.permissions) || null, ['read']),
|
|
124
|
+
create: ((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.admin) === true
|
|
125
|
+
? this.schema
|
|
126
|
+
: (0, reduce_schema_1.reduceSchema)(this.schema, ((_d = this.accountability) === null || _d === void 0 ? void 0 : _d.permissions) || null, ['create']),
|
|
127
|
+
update: ((_e = this.accountability) === null || _e === void 0 ? void 0 : _e.admin) === true
|
|
128
|
+
? this.schema
|
|
129
|
+
: (0, reduce_schema_1.reduceSchema)(this.schema, ((_f = this.accountability) === null || _f === void 0 ? void 0 : _f.permissions) || null, ['update']),
|
|
130
|
+
delete: ((_g = this.accountability) === null || _g === void 0 ? void 0 : _g.admin) === true
|
|
131
|
+
? this.schema
|
|
132
|
+
: (0, reduce_schema_1.reduceSchema)(this.schema, ((_h = this.accountability) === null || _h === void 0 ? void 0 : _h.permissions) || null, ['delete']),
|
|
124
133
|
};
|
|
125
134
|
const { ReadCollectionTypes } = getReadableTypes();
|
|
126
135
|
const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes();
|
|
@@ -325,6 +334,8 @@ class GraphQLService {
|
|
|
325
334
|
}
|
|
326
335
|
for (const relation of schema[action].relations) {
|
|
327
336
|
if (relation.related_collection) {
|
|
337
|
+
if (SYSTEM_DENY_LIST.includes(relation.related_collection))
|
|
338
|
+
continue;
|
|
328
339
|
(_a = CollectionTypes[relation.collection]) === null || _a === void 0 ? void 0 : _a.addFields({
|
|
329
340
|
[relation.field]: {
|
|
330
341
|
type: CollectionTypes[relation.related_collection],
|
|
@@ -759,6 +770,8 @@ class GraphQLService {
|
|
|
759
770
|
}
|
|
760
771
|
for (const relation of schema.read.relations) {
|
|
761
772
|
if (relation.related_collection) {
|
|
773
|
+
if (SYSTEM_DENY_LIST.includes(relation.related_collection))
|
|
774
|
+
continue;
|
|
762
775
|
(_a = ReadableCollectionFilterTypes[relation.collection]) === null || _a === void 0 ? void 0 : _a.addFields({
|
|
763
776
|
[relation.field]: ReadableCollectionFilterTypes[relation.related_collection],
|
|
764
777
|
});
|
|
@@ -1268,6 +1281,8 @@ class GraphQLService {
|
|
|
1268
1281
|
return new users_1.UsersService(opts);
|
|
1269
1282
|
case 'directus_webhooks':
|
|
1270
1283
|
return new webhooks_1.WebhooksService(opts);
|
|
1284
|
+
case 'directus_shares':
|
|
1285
|
+
return new shares_1.SharesService(opts);
|
|
1271
1286
|
default:
|
|
1272
1287
|
return new items_1.ItemsService(collection, opts);
|
|
1273
1288
|
}
|
|
@@ -1292,7 +1307,7 @@ class GraphQLService {
|
|
|
1292
1307
|
})).filter((s) => s);
|
|
1293
1308
|
return result;
|
|
1294
1309
|
}
|
|
1295
|
-
injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, DeleteCollectionTypes, }, schema) {
|
|
1310
|
+
injectSystemResolvers(schemaComposer, { CreateCollectionTypes, ReadCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes, }, schema) {
|
|
1296
1311
|
var _a, _b, _c, _d, _e;
|
|
1297
1312
|
const AuthTokens = schemaComposer.createObjectTC({
|
|
1298
1313
|
name: 'auth_tokens',
|
|
@@ -1382,10 +1397,10 @@ class GraphQLService {
|
|
|
1382
1397
|
resolve: async () => {
|
|
1383
1398
|
const extensionManager = (0, extensions_1.getExtensionManager)();
|
|
1384
1399
|
return {
|
|
1385
|
-
interfaces: extensionManager.
|
|
1386
|
-
displays: extensionManager.
|
|
1387
|
-
layouts: extensionManager.
|
|
1388
|
-
modules: extensionManager.
|
|
1400
|
+
interfaces: extensionManager.getExtensionsList('interface'),
|
|
1401
|
+
displays: extensionManager.getExtensionsList('display'),
|
|
1402
|
+
layouts: extensionManager.getExtensionsList('layout'),
|
|
1403
|
+
modules: extensionManager.getExtensionsList('module'),
|
|
1389
1404
|
};
|
|
1390
1405
|
},
|
|
1391
1406
|
},
|
|
@@ -2116,6 +2131,32 @@ class GraphQLService {
|
|
|
2116
2131
|
},
|
|
2117
2132
|
});
|
|
2118
2133
|
}
|
|
2134
|
+
if ('directus_users' in schema.update.collections) {
|
|
2135
|
+
schemaComposer.Mutation.addFields({
|
|
2136
|
+
update_users_me: {
|
|
2137
|
+
type: ReadCollectionTypes['directus_users'],
|
|
2138
|
+
args: {
|
|
2139
|
+
data: (0, graphql_compose_1.toInputObjectType)(UpdateCollectionTypes['directus_users']),
|
|
2140
|
+
},
|
|
2141
|
+
resolve: async (_, args, __, info) => {
|
|
2142
|
+
var _a, _b, _c;
|
|
2143
|
+
if (!((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.user))
|
|
2144
|
+
return null;
|
|
2145
|
+
const service = new users_1.UsersService({
|
|
2146
|
+
schema: this.schema,
|
|
2147
|
+
accountability: this.accountability,
|
|
2148
|
+
});
|
|
2149
|
+
await service.updateOne(this.accountability.user, args.data);
|
|
2150
|
+
if ('directus_users' in ReadCollectionTypes) {
|
|
2151
|
+
const selections = this.replaceFragmentsInSelections((_c = (_b = info.fieldNodes[0]) === null || _b === void 0 ? void 0 : _b.selectionSet) === null || _c === void 0 ? void 0 : _c.selections, info.fragments);
|
|
2152
|
+
const query = this.getQuery(args, selections || [], info.variableValues);
|
|
2153
|
+
return await service.readOne(this.accountability.user, query);
|
|
2154
|
+
}
|
|
2155
|
+
return true;
|
|
2156
|
+
},
|
|
2157
|
+
},
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2119
2160
|
if ('directus_activity' in schema.create.collections) {
|
|
2120
2161
|
schemaComposer.Mutation.addFields({
|
|
2121
2162
|
create_comment: {
|
package/dist/services/index.d.ts
CHANGED
package/dist/services/index.js
CHANGED
package/dist/services/items.d.ts
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
import { Knex } from 'knex';
|
|
2
2
|
import Keyv from 'keyv';
|
|
3
3
|
import { Accountability, Query, PermissionsAction } from '@directus/shared/types';
|
|
4
|
-
import { AbstractService, AbstractServiceOptions, Item as AnyItem, PrimaryKey, SchemaOverview } from '../types';
|
|
4
|
+
import { AbstractService, AbstractServiceOptions, Item as AnyItem, PrimaryKey, SchemaOverview, MutationOptions } from '../types';
|
|
5
5
|
export declare type QueryOptions = {
|
|
6
6
|
stripNonRequested?: boolean;
|
|
7
7
|
permissionsAction?: PermissionsAction;
|
|
8
8
|
};
|
|
9
|
-
export declare type MutationOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* Callback function that's fired whenever a revision is made in the mutation
|
|
12
|
-
*/
|
|
13
|
-
onRevisionCreate?: (pk: PrimaryKey) => void;
|
|
14
|
-
/**
|
|
15
|
-
* Flag to disable the auto purging of the cache. Is ignored when CACHE_AUTO_PURGE isn't enabled.
|
|
16
|
-
*/
|
|
17
|
-
autoPurgeCache?: false;
|
|
18
|
-
/**
|
|
19
|
-
* Allow disabling the emitting of hooks. Useful if a custom hook is fired (like files.upload)
|
|
20
|
-
*/
|
|
21
|
-
emitEvents?: boolean;
|
|
22
|
-
};
|
|
23
9
|
export declare class ItemsService<Item extends AnyItem = AnyItem> implements AbstractService {
|
|
24
10
|
collection: string;
|
|
25
11
|
knex: Knex;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UsersService, MailService } from '.';
|
|
2
|
-
import { AbstractServiceOptions, PrimaryKey } from '../types';
|
|
3
|
-
import { ItemsService
|
|
2
|
+
import { AbstractServiceOptions, PrimaryKey, MutationOptions } from '../types';
|
|
3
|
+
import { ItemsService } from './items';
|
|
4
4
|
import { Notification } from '@directus/shared/types';
|
|
5
5
|
export declare class NotificationsService extends ItemsService {
|
|
6
6
|
usersService: UsersService;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ItemsService, QueryOptions
|
|
2
|
-
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
|
|
1
|
+
import { ItemsService, QueryOptions } from '../services/items';
|
|
2
|
+
import { AbstractServiceOptions, Item, PrimaryKey, MutationOptions } from '../types';
|
|
3
3
|
import { Query, PermissionsAction } from '@directus/shared/types';
|
|
4
4
|
import Keyv from 'keyv';
|
|
5
5
|
export declare class PermissionsService extends ItemsService {
|
package/dist/services/roles.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AbstractServiceOptions, PrimaryKey } from '../types';
|
|
1
|
+
import { AbstractServiceOptions, MutationOptions, PrimaryKey } from '../types';
|
|
2
2
|
import { Query } from '@directus/shared/types';
|
|
3
|
-
import { ItemsService
|
|
3
|
+
import { ItemsService } from './items';
|
|
4
4
|
export declare class RolesService extends ItemsService {
|
|
5
5
|
constructor(options: AbstractServiceOptions);
|
|
6
6
|
private checkForOtherAdminRoles;
|
package/dist/services/server.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AbstractServiceOptions, LoginResult, Item, PrimaryKey, MutationOptions } from '../types';
|
|
2
|
+
import { ItemsService } from './items';
|
|
3
|
+
import { AuthorizationService } from './authorization';
|
|
4
|
+
export declare class SharesService extends ItemsService {
|
|
5
|
+
authorizationService: AuthorizationService;
|
|
6
|
+
constructor(options: AbstractServiceOptions);
|
|
7
|
+
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
8
|
+
login(payload: Record<string, any>): Promise<LoginResult>;
|
|
9
|
+
/**
|
|
10
|
+
* Send a link to the given share ID to the given email(s). Note: you can only send a link to a share
|
|
11
|
+
* if you have read access to that particular share
|
|
12
|
+
*/
|
|
13
|
+
invite(payload: {
|
|
14
|
+
emails: string[];
|
|
15
|
+
share: PrimaryKey;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SharesService = void 0;
|
|
7
|
+
const items_1 = require("./items");
|
|
8
|
+
const argon2_1 = __importDefault(require("argon2"));
|
|
9
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
10
|
+
const ms_1 = __importDefault(require("ms"));
|
|
11
|
+
const exceptions_1 = require("../exceptions");
|
|
12
|
+
const env_1 = __importDefault(require("../env"));
|
|
13
|
+
const nanoid_1 = require("nanoid");
|
|
14
|
+
const authorization_1 = require("./authorization");
|
|
15
|
+
const users_1 = require("./users");
|
|
16
|
+
const mail_1 = require("./mail");
|
|
17
|
+
const user_name_1 = require("../utils/user-name");
|
|
18
|
+
const md_1 = require("../utils/md");
|
|
19
|
+
class SharesService extends items_1.ItemsService {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super('directus_shares', options);
|
|
22
|
+
this.authorizationService = new authorization_1.AuthorizationService({
|
|
23
|
+
accountability: this.accountability,
|
|
24
|
+
knex: this.knex,
|
|
25
|
+
schema: this.schema,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async createOne(data, opts) {
|
|
29
|
+
await this.authorizationService.checkAccess('share', data.collection, data.item);
|
|
30
|
+
return super.createOne(data, opts);
|
|
31
|
+
}
|
|
32
|
+
async login(payload) {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const record = await this.knex
|
|
35
|
+
.select({
|
|
36
|
+
share_id: 'id',
|
|
37
|
+
share_role: 'role',
|
|
38
|
+
share_item: 'item',
|
|
39
|
+
share_collection: 'collection',
|
|
40
|
+
share_start: 'date_start',
|
|
41
|
+
share_end: 'date_end',
|
|
42
|
+
share_times_used: 'times_used',
|
|
43
|
+
share_max_uses: 'max_uses',
|
|
44
|
+
share_password: 'password',
|
|
45
|
+
})
|
|
46
|
+
.from('directus_shares')
|
|
47
|
+
.where('id', payload.share)
|
|
48
|
+
.andWhere((subQuery) => {
|
|
49
|
+
subQuery.whereNull('date_end').orWhere('date_end', '>=', new Date());
|
|
50
|
+
})
|
|
51
|
+
.andWhere((subQuery) => {
|
|
52
|
+
subQuery.whereNull('date_start').orWhere('date_start', '<=', new Date());
|
|
53
|
+
})
|
|
54
|
+
.andWhere((subQuery) => {
|
|
55
|
+
subQuery.whereNull('max_uses').orWhere('max_uses', '>=', this.knex.ref('times_used'));
|
|
56
|
+
})
|
|
57
|
+
.first();
|
|
58
|
+
if (!record) {
|
|
59
|
+
throw new exceptions_1.InvalidCredentialsException();
|
|
60
|
+
}
|
|
61
|
+
if (record.share_password && !(await argon2_1.default.verify(record.share_password, payload.password))) {
|
|
62
|
+
throw new exceptions_1.InvalidCredentialsException();
|
|
63
|
+
}
|
|
64
|
+
await this.knex('directus_shares')
|
|
65
|
+
.update({ times_used: record.share_times_used + 1 })
|
|
66
|
+
.where('id', record.share_id);
|
|
67
|
+
const tokenPayload = {
|
|
68
|
+
app_access: false,
|
|
69
|
+
admin_access: false,
|
|
70
|
+
role: record.share_role,
|
|
71
|
+
share: record.share_id,
|
|
72
|
+
share_scope: {
|
|
73
|
+
item: record.share_item,
|
|
74
|
+
collection: record.share_collection,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
const accessToken = jsonwebtoken_1.default.sign(tokenPayload, env_1.default.SECRET, {
|
|
78
|
+
expiresIn: env_1.default.ACCESS_TOKEN_TTL,
|
|
79
|
+
issuer: 'directus',
|
|
80
|
+
});
|
|
81
|
+
const refreshToken = (0, nanoid_1.nanoid)(64);
|
|
82
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL));
|
|
83
|
+
await this.knex('directus_sessions').insert({
|
|
84
|
+
token: refreshToken,
|
|
85
|
+
expires: refreshTokenExpiration,
|
|
86
|
+
ip: (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.ip,
|
|
87
|
+
user_agent: (_b = this.accountability) === null || _b === void 0 ? void 0 : _b.userAgent,
|
|
88
|
+
share: record.share_id,
|
|
89
|
+
});
|
|
90
|
+
await this.knex('directus_sessions').delete().where('expires', '<', new Date());
|
|
91
|
+
return {
|
|
92
|
+
accessToken,
|
|
93
|
+
refreshToken,
|
|
94
|
+
expires: (0, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Send a link to the given share ID to the given email(s). Note: you can only send a link to a share
|
|
99
|
+
* if you have read access to that particular share
|
|
100
|
+
*/
|
|
101
|
+
async invite(payload) {
|
|
102
|
+
var _a;
|
|
103
|
+
if (!((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.user))
|
|
104
|
+
throw new exceptions_1.ForbiddenException();
|
|
105
|
+
const share = await this.readOne(payload.share, { fields: ['collection'] });
|
|
106
|
+
const usersService = new users_1.UsersService({
|
|
107
|
+
knex: this.knex,
|
|
108
|
+
schema: this.schema,
|
|
109
|
+
});
|
|
110
|
+
const mailService = new mail_1.MailService({ schema: this.schema, accountability: this.accountability });
|
|
111
|
+
const userInfo = await usersService.readOne(this.accountability.user, {
|
|
112
|
+
fields: ['first_name', 'last_name', 'email', 'id'],
|
|
113
|
+
});
|
|
114
|
+
const message = `
|
|
115
|
+
Hello!
|
|
116
|
+
|
|
117
|
+
${(0, user_name_1.userName)(userInfo)} has invited you to view an item in ${share.collection}.
|
|
118
|
+
|
|
119
|
+
[Open](${env_1.default.PUBLIC_URL}/admin/shared/${payload.share})
|
|
120
|
+
`;
|
|
121
|
+
for (const email of payload.emails) {
|
|
122
|
+
await mailService.send({
|
|
123
|
+
template: {
|
|
124
|
+
name: 'base',
|
|
125
|
+
data: {
|
|
126
|
+
html: (0, md_1.md)(message),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
to: email,
|
|
130
|
+
subject: `${(0, user_name_1.userName)(userInfo)} has shared an item with you`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.SharesService = SharesService;
|
|
@@ -442,7 +442,7 @@ class OASSpecsService {
|
|
|
442
442
|
],
|
|
443
443
|
};
|
|
444
444
|
}
|
|
445
|
-
else if (relationType === '
|
|
445
|
+
else if (relationType === 'a2o') {
|
|
446
446
|
const relatedTags = tags.filter((tag) => relation.meta.one_allowed_collections.includes(tag['x-collection']));
|
|
447
447
|
propertyObject.type = 'array';
|
|
448
448
|
propertyObject.items = {
|
package/dist/services/users.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Knex } from 'knex';
|
|
2
|
-
import { AbstractServiceOptions, Item, PrimaryKey, SchemaOverview } from '../types';
|
|
2
|
+
import { AbstractServiceOptions, Item, PrimaryKey, SchemaOverview, MutationOptions } from '../types';
|
|
3
3
|
import { Query } from '@directus/shared/types';
|
|
4
4
|
import { Accountability } from '@directus/shared/types';
|
|
5
|
-
import { ItemsService
|
|
5
|
+
import { ItemsService } from './items';
|
|
6
6
|
export declare class UsersService extends ItemsService {
|
|
7
7
|
knex: Knex;
|
|
8
8
|
accountability: Accountability | null;
|