directus 9.2.1 → 9.4.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.
Files changed (93) hide show
  1. package/dist/app.js +5 -3
  2. package/dist/auth/auth.d.ts +4 -6
  3. package/dist/auth/auth.js +5 -9
  4. package/dist/auth/drivers/ldap.d.ts +3 -3
  5. package/dist/auth/drivers/ldap.js +6 -2
  6. package/dist/auth/drivers/local.d.ts +2 -2
  7. package/dist/auth/drivers/local.js +5 -12
  8. package/dist/auth/drivers/oauth2.d.ts +3 -3
  9. package/dist/auth/drivers/oauth2.js +2 -3
  10. package/dist/auth/drivers/openid.d.ts +3 -3
  11. package/dist/auth/drivers/openid.js +2 -3
  12. package/dist/cli/commands/bootstrap/index.js +3 -2
  13. package/dist/cli/commands/init/index.js +3 -7
  14. package/dist/cli/commands/schema/apply.js +1 -1
  15. package/dist/cli/utils/defaults.d.ts +11 -0
  16. package/dist/cli/utils/defaults.js +14 -0
  17. package/dist/constants.d.ts +8 -0
  18. package/dist/constants.js +16 -2
  19. package/dist/controllers/shares.d.ts +2 -0
  20. package/dist/controllers/shares.js +212 -0
  21. package/dist/controllers/users.js +21 -9
  22. package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
  23. package/dist/database/migrations/20211211A-add-shares.js +38 -0
  24. package/dist/database/run-ast.js +5 -5
  25. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
  26. package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
  27. package/dist/database/system-data/app-access-permissions/index.js +4 -2
  28. package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
  29. package/dist/database/system-data/collections/collections.yaml +3 -0
  30. package/dist/database/system-data/fields/_defaults.yaml +2 -0
  31. package/dist/database/system-data/fields/sessions.yaml +1 -1
  32. package/dist/database/system-data/fields/settings.yaml +9 -0
  33. package/dist/database/system-data/fields/shares.yaml +77 -0
  34. package/dist/database/system-data/fields/users.yaml +1 -1
  35. package/dist/database/system-data/relations/relations.yaml +15 -0
  36. package/dist/emitter.d.ts +3 -2
  37. package/dist/emitter.js +13 -6
  38. package/dist/env.js +1 -1
  39. package/dist/exceptions/index.d.ts +1 -0
  40. package/dist/exceptions/index.js +1 -0
  41. package/dist/exceptions/unexpected-response.d.ts +4 -0
  42. package/dist/exceptions/unexpected-response.js +10 -0
  43. package/dist/extensions.d.ts +1 -0
  44. package/dist/extensions.js +10 -4
  45. package/dist/middleware/authenticate.js +5 -15
  46. package/dist/middleware/check-ip.js +9 -6
  47. package/dist/middleware/respond.js +4 -1
  48. package/dist/services/activity.d.ts +2 -1
  49. package/dist/services/activity.js +2 -2
  50. package/dist/services/authentication.d.ts +2 -7
  51. package/dist/services/authentication.js +81 -41
  52. package/dist/services/authorization.js +3 -3
  53. package/dist/services/collections.d.ts +1 -2
  54. package/dist/services/collections.js +2 -2
  55. package/dist/services/files.d.ts +2 -2
  56. package/dist/services/files.js +14 -8
  57. package/dist/services/graphql.js +20 -5
  58. package/dist/services/index.d.ts +1 -0
  59. package/dist/services/index.js +1 -0
  60. package/dist/services/items.d.ts +1 -15
  61. package/dist/services/notifications.d.ts +2 -2
  62. package/dist/services/permissions.d.ts +2 -2
  63. package/dist/services/roles.d.ts +2 -2
  64. package/dist/services/shares.d.ts +17 -0
  65. package/dist/services/shares.js +135 -0
  66. package/dist/services/specifications.js +1 -1
  67. package/dist/services/users.d.ts +2 -2
  68. package/dist/services/users.js +8 -6
  69. package/dist/services/webhooks.d.ts +2 -2
  70. package/dist/tests/database/migrations/run.test.d.ts +1 -0
  71. package/dist/tests/database/migrations/run.test.js +29 -0
  72. package/dist/types/ast.d.ts +3 -3
  73. package/dist/types/auth.d.ts +31 -0
  74. package/dist/types/extensions.d.ts +2 -0
  75. package/dist/types/items.d.ts +14 -0
  76. package/dist/utils/apply-query.d.ts +0 -38
  77. package/dist/utils/apply-query.js +66 -67
  78. package/dist/utils/apply-snapshot.js +69 -14
  79. package/dist/utils/get-ast-from-query.js +3 -3
  80. package/dist/utils/get-default-value.js +3 -1
  81. package/dist/utils/get-permissions.d.ts +2 -2
  82. package/dist/utils/get-permissions.js +117 -72
  83. package/dist/utils/get-relation-type.d.ts +1 -1
  84. package/dist/utils/get-relation-type.js +1 -1
  85. package/dist/utils/merge-permissions-for-share.d.ts +5 -0
  86. package/dist/utils/merge-permissions-for-share.js +116 -0
  87. package/dist/utils/merge-permissions.d.ts +13 -1
  88. package/dist/utils/merge-permissions.js +27 -19
  89. package/dist/utils/reduce-schema.d.ts +2 -2
  90. package/dist/utils/reduce-schema.js +7 -7
  91. package/dist/utils/user-name.js +3 -0
  92. package/example.env +1 -1
  93. package/package.json +14 -13
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ActivityService = void 0;
7
7
  const types_1 = require("../types");
8
- const index_1 = require("./index");
8
+ const items_1 = require("./items");
9
9
  const notifications_1 = require("./notifications");
10
10
  const users_1 = require("./users");
11
11
  const authorization_1 = require("./authorization");
@@ -16,7 +16,7 @@ const user_name_1 = require("../utils/user-name");
16
16
  const lodash_1 = require("lodash");
17
17
  const env_1 = __importDefault(require("../env"));
18
18
  const uuid_validate_1 = __importDefault(require("uuid-validate"));
19
- class ActivityService extends index_1.ItemsService {
19
+ class ActivityService extends items_1.ItemsService {
20
20
  constructor(options) {
21
21
  super('directus_activity', options);
22
22
  this.notificationsService = new notifications_1.NotificationsService({ schema: this.schema });
@@ -1,6 +1,6 @@
1
1
  import { Knex } from 'knex';
2
2
  import { ActivityService } from './activity';
3
- import { AbstractServiceOptions, SchemaOverview } from '../types';
3
+ import { AbstractServiceOptions, SchemaOverview, LoginResult } from '../types';
4
4
  import { Accountability } from '@directus/shared/types';
5
5
  export declare class AuthenticationService {
6
6
  knex: Knex;
@@ -14,12 +14,7 @@ export declare class AuthenticationService {
14
14
  * Password is optional to allow usage of this function within the SSO flow and extensions. Make sure
15
15
  * to handle password existence checks elsewhere
16
16
  */
17
- login(providerName: string | undefined, payload: Record<string, any>, otp?: string): Promise<{
18
- accessToken: any;
19
- refreshToken: any;
20
- expires: any;
21
- id?: any;
22
- }>;
17
+ login(providerName: string | undefined, payload: Record<string, any>, otp?: string): Promise<LoginResult>;
23
18
  refresh(refreshToken: string): Promise<Record<string, any>>;
24
19
  logout(refreshToken: string): Promise<void>;
25
20
  verifyPassword(userID: string, password: string): Promise<void>;
@@ -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
- .where('id', await provider.getUserID((0, lodash_1.cloneDeep)(payload)))
48
- .andWhere('provider', providerName)
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
+ .innerJoin('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
- sessionData = await provider.login((0, lodash_1.clone)(user), (0, lodash_1.cloneDeep)(updatedPayload));
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,80 @@ class AuthenticationService {
177
178
  throw new exceptions_1.InvalidCredentialsException();
178
179
  }
179
180
  const record = await this.knex
180
- .select('s.expires', 's.data', 'u.id', 'u.first_name', 'u.last_name', 'u.email', 'u.password', 'u.status', 'u.role', 'u.provider', 'u.external_identifier', 'u.auth_data')
181
- .from('directus_sessions as s')
182
- .innerJoin('directus_users as u', 's.user', 'u.id')
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.expires < new Date()) {
217
+ if (!record || (!record.share_id && !record.user_id)) {
186
218
  throw new exceptions_1.InvalidCredentialsException();
187
219
  }
188
- let { data: sessionData } = record;
189
- const user = (0, lodash_1.omit)(record, 'data');
190
- if (typeof sessionData === 'string') {
191
- try {
192
- sessionData = JSON.parse(sessionData);
193
- }
194
- catch {
195
- logger_1.default.warn(`Session data isn't valid JSON: ${sessionData}`);
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: user.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
+ }
203
251
  const customClaims = await emitter_1.default.emitFilter('auth.jwt', tokenPayload, {
204
252
  status: 'pending',
205
- user: user === null || user === void 0 ? void 0 : user.id,
206
- provider: user.provider,
253
+ user: record.user_id,
254
+ provider: record.user_provider,
207
255
  type: 'refresh',
208
256
  }, {
209
257
  database: this.knex,
@@ -220,37 +268,29 @@ class AuthenticationService {
220
268
  .update({
221
269
  token: newRefreshToken,
222
270
  expires: refreshTokenExpiration,
223
- data: newSessionData && JSON.stringify(newSessionData),
224
271
  })
225
272
  .where({ token: refreshToken });
226
- await this.knex('directus_users').update({ last_access: new Date() }).where({ id: user.id });
273
+ if (record.user_id) {
274
+ await this.knex('directus_users').update({ last_access: new Date() }).where({ id: record.user_id });
275
+ }
227
276
  return {
228
277
  accessToken,
229
278
  refreshToken: newRefreshToken,
230
279
  expires: (0, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
231
- id: user.id,
280
+ id: record.user_id,
232
281
  };
233
282
  }
234
283
  async logout(refreshToken) {
235
284
  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', 's.data')
285
+ .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
286
  .from('directus_sessions as s')
238
287
  .innerJoin('directus_users as u', 's.user', 'u.id')
239
288
  .where('s.token', refreshToken)
240
289
  .first();
241
290
  if (record) {
242
- let { data: sessionData } = record;
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
- }
291
+ const user = record;
252
292
  const provider = (0, auth_1.getAuthProvider)(user.provider);
253
- await provider.logout((0, lodash_1.clone)(user), sessionData);
293
+ await provider.logout((0, lodash_1.clone)(user));
254
294
  await this.knex.delete().from('directus_sessions').where('token', refreshToken);
255
295
  }
256
296
  }
@@ -41,7 +41,7 @@ class AuthorizationService {
41
41
  */
42
42
  function getCollectionsFromAST(ast) {
43
43
  const collections = [];
44
- if (ast.type === 'm2a') {
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 === 'm2a') {
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 === 'm2a') {
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 m2aRelationsThatIncludeThisCollection = this.schema.relations.filter((relation) => {
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 m2aRelationsThatIncludeThisCollection) {
366
+ for (const relation of a2oRelationsThatIncludeThisCollection) {
367
367
  const newAllowedCollections = relation
368
368
  .meta.one_allowed_collections.filter((collection) => collectionKey !== collection)
369
369
  .join(',');
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
- import { AbstractServiceOptions, File, PrimaryKey } from '../types';
3
- import { ItemsService, MutationOptions } from './items';
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
  /**
@@ -64,14 +64,20 @@ class FilesService extends items_1.ItemsService {
64
64
  payload.filesize = size;
65
65
  if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
66
66
  const buffer = await storage_1.default.disk(data.storage).getBuffer(payload.filename_disk);
67
- const meta = await (0, sharp_1.default)(buffer.content, {}).metadata();
68
- if (meta.orientation && meta.orientation >= 5) {
69
- payload.height = meta.width;
70
- payload.width = meta.height;
67
+ try {
68
+ const meta = await (0, sharp_1.default)(buffer.content, {}).metadata();
69
+ if (meta.orientation && meta.orientation >= 5) {
70
+ payload.height = meta.width;
71
+ payload.width = meta.height;
72
+ }
73
+ else {
74
+ payload.width = meta.width;
75
+ payload.height = meta.height;
76
+ }
71
77
  }
72
- else {
73
- payload.width = meta.width;
74
- payload.height = meta.height;
78
+ catch (err) {
79
+ logger_1.default.warn(`Couldn't extract sharp metadata from file`);
80
+ logger_1.default.warn(err);
75
81
  }
76
82
  payload.metadata = {};
77
83
  try {
@@ -95,7 +101,7 @@ class FilesService extends items_1.ItemsService {
95
101
  }
96
102
  }
97
103
  catch (err) {
98
- logger_1.default.warn(`Couldn't extract metadata from file`);
104
+ logger_1.default.warn(`Couldn't extract EXIF metadata from file`);
99
105
  logger_1.default.warn(err);
100
106
  }
101
107
  }
@@ -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 ? this.schema : (0, reduce_schema_1.reduceSchema)(this.schema, this.accountability, ['read']),
121
- create: ((_b = this.accountability) === null || _b === void 0 ? void 0 : _b.admin) === true ? this.schema : (0, reduce_schema_1.reduceSchema)(this.schema, this.accountability, ['create']),
122
- update: ((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.admin) === true ? this.schema : (0, reduce_schema_1.reduceSchema)(this.schema, this.accountability, ['update']),
123
- delete: ((_d = this.accountability) === null || _d === void 0 ? void 0 : _d.admin) === true ? this.schema : (0, reduce_schema_1.reduceSchema)(this.schema, this.accountability, ['delete']),
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
  }
@@ -26,3 +26,4 @@ export * from './tfa';
26
26
  export * from './users';
27
27
  export * from './utils';
28
28
  export * from './webhooks';
29
+ export * from './shares';
@@ -39,3 +39,4 @@ __exportStar(require("./tfa"), exports);
39
39
  __exportStar(require("./users"), exports);
40
40
  __exportStar(require("./utils"), exports);
41
41
  __exportStar(require("./webhooks"), exports);
42
+ __exportStar(require("./shares"), exports);
@@ -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, MutationOptions } from './items';
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, MutationOptions } from '../services/items';
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 {
@@ -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, MutationOptions } from './items';
3
+ import { ItemsService } from './items';
4
4
  export declare class RolesService extends ItemsService {
5
5
  constructor(options: AbstractServiceOptions);
6
6
  private checkForOtherAdminRoles;
@@ -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 === 'm2a') {
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 = {
@@ -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, MutationOptions } from './items';
5
+ import { ItemsService } from './items';
6
6
  export declare class UsersService extends ItemsService {
7
7
  knex: Knex;
8
8
  accountability: Accountability | null;