directus 9.22.4 → 9.23.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 (112) hide show
  1. package/dist/app.js +5 -4
  2. package/dist/auth/drivers/ldap.d.ts +2 -2
  3. package/dist/auth/drivers/ldap.js +8 -8
  4. package/dist/auth/drivers/oauth2.js +2 -2
  5. package/dist/auth/drivers/openid.js +2 -2
  6. package/dist/cache.js +4 -4
  7. package/dist/cli/commands/schema/apply.js +19 -17
  8. package/dist/cli/utils/create-db-connection.d.ts +2 -1
  9. package/dist/cli/utils/create-env/env-stub.liquid +1 -1
  10. package/dist/cli/utils/drivers.d.ts +3 -9
  11. package/dist/constants.d.ts +2 -8
  12. package/dist/constants.js +3 -7
  13. package/dist/controllers/assets.js +5 -5
  14. package/dist/controllers/extensions.js +7 -7
  15. package/dist/controllers/files.js +1 -1
  16. package/dist/controllers/graphql.js +8 -0
  17. package/dist/controllers/schema.d.ts +2 -0
  18. package/dist/controllers/schema.js +98 -0
  19. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -1
  20. package/dist/database/helpers/schema/dialects/oracle.d.ts +4 -1
  21. package/dist/database/helpers/schema/dialects/oracle.js +25 -0
  22. package/dist/database/helpers/schema/types.d.ts +8 -6
  23. package/dist/database/helpers/schema/types.js +7 -1
  24. package/dist/database/index.d.ts +2 -1
  25. package/dist/database/run-ast.js +2 -2
  26. package/dist/env.js +9 -2
  27. package/dist/extensions.js +1 -1
  28. package/dist/flows.js +17 -8
  29. package/dist/middleware/cache.js +2 -2
  30. package/dist/middleware/respond.js +14 -9
  31. package/dist/operations/request/index.js +2 -1
  32. package/dist/operations/trigger/index.d.ts +2 -0
  33. package/dist/operations/trigger/index.js +26 -9
  34. package/dist/request/index.d.ts +5 -0
  35. package/dist/request/index.js +18 -0
  36. package/dist/request/index.test.d.ts +1 -0
  37. package/dist/request/request-interceptor.d.ts +2 -0
  38. package/dist/request/request-interceptor.js +33 -0
  39. package/dist/request/request-interceptor.test.d.ts +1 -0
  40. package/dist/request/response-interceptor.d.ts +2 -0
  41. package/dist/request/response-interceptor.js +9 -0
  42. package/dist/request/response-interceptor.test.d.ts +1 -0
  43. package/dist/request/validate-ip.d.ts +1 -0
  44. package/dist/request/validate-ip.js +27 -0
  45. package/dist/request/validate-ip.test.d.ts +1 -0
  46. package/dist/services/assets.d.ts +1 -1
  47. package/dist/services/assets.js +11 -2
  48. package/dist/services/authentication.js +5 -5
  49. package/dist/services/fields.js +1 -0
  50. package/dist/services/files.js +44 -88
  51. package/dist/services/graphql/index.js +14 -8
  52. package/dist/services/graphql/utils/process-error.js +22 -9
  53. package/dist/services/import-export.d.ts +4 -2
  54. package/dist/services/import-export.js +17 -3
  55. package/dist/services/import-export.test.d.ts +1 -0
  56. package/dist/services/index.d.ts +1 -0
  57. package/dist/services/index.js +1 -0
  58. package/dist/services/items.js +34 -15
  59. package/dist/services/relations.js +2 -0
  60. package/dist/services/roles.js +32 -11
  61. package/dist/services/schema.d.ts +15 -0
  62. package/dist/services/schema.js +58 -0
  63. package/dist/services/schema.test.d.ts +1 -0
  64. package/dist/services/shares.d.ts +2 -2
  65. package/dist/services/shares.js +9 -9
  66. package/dist/services/users.js +74 -47
  67. package/dist/types/assets.d.ts +1 -1
  68. package/dist/types/database.d.ts +3 -0
  69. package/dist/types/database.js +4 -0
  70. package/dist/types/index.d.ts +1 -0
  71. package/dist/types/index.js +1 -0
  72. package/dist/types/items.d.ts +5 -0
  73. package/dist/types/snapshot.d.ts +22 -0
  74. package/dist/types/snapshot.js +14 -0
  75. package/dist/utils/apply-diff.d.ts +9 -0
  76. package/dist/utils/apply-diff.js +259 -0
  77. package/dist/utils/apply-diff.test.d.ts +1 -0
  78. package/dist/utils/apply-query.js +8 -6
  79. package/dist/utils/apply-snapshot.d.ts +1 -3
  80. package/dist/utils/apply-snapshot.js +4 -234
  81. package/dist/utils/get-cache-headers.d.ts +3 -1
  82. package/dist/utils/get-cache-headers.js +20 -19
  83. package/dist/utils/get-cache-headers.test.d.ts +1 -0
  84. package/dist/utils/get-milliseconds.d.ts +4 -0
  85. package/dist/utils/get-milliseconds.js +15 -0
  86. package/dist/utils/get-milliseconds.test.d.ts +1 -0
  87. package/dist/utils/get-snapshot-diff.js +11 -7
  88. package/dist/utils/get-snapshot.js +29 -6
  89. package/dist/utils/get-versioned-hash.d.ts +1 -0
  90. package/dist/utils/get-versioned-hash.js +12 -0
  91. package/dist/utils/get-versioned-hash.test.d.ts +1 -0
  92. package/dist/utils/map-values-deep.d.ts +1 -0
  93. package/dist/utils/map-values-deep.js +29 -0
  94. package/dist/utils/map-values-deep.test.d.ts +1 -0
  95. package/dist/utils/sanitize-schema.d.ts +30 -0
  96. package/dist/utils/sanitize-schema.js +80 -0
  97. package/dist/utils/sanitize-schema.test.d.ts +1 -0
  98. package/dist/utils/track.js +3 -3
  99. package/dist/utils/url.js +2 -6
  100. package/dist/utils/url.test.d.ts +1 -0
  101. package/dist/utils/validate-diff.d.ts +7 -0
  102. package/dist/utils/validate-diff.js +114 -0
  103. package/dist/utils/validate-diff.test.d.ts +1 -0
  104. package/dist/utils/validate-query.js +1 -1
  105. package/dist/utils/validate-query.test.d.ts +1 -0
  106. package/dist/utils/validate-snapshot.d.ts +5 -0
  107. package/dist/utils/validate-snapshot.js +71 -0
  108. package/dist/utils/validate-snapshot.test.d.ts +1 -0
  109. package/dist/utils/with-timeout.d.ts +1 -0
  110. package/dist/utils/with-timeout.js +16 -0
  111. package/dist/webhooks.js +3 -2
  112. package/package.json +54 -53
@@ -56,8 +56,13 @@ class RolesService extends items_1.ItemsService {
56
56
  return;
57
57
  }
58
58
  async updateOne(key, data, opts) {
59
- if ('users' in data) {
60
- await this.checkForOtherAdminUsers(key, data.users);
59
+ try {
60
+ if ('users' in data) {
61
+ await this.checkForOtherAdminUsers(key, data.users);
62
+ }
63
+ }
64
+ catch (err) {
65
+ (opts || (opts = {})).preMutationException = err;
61
66
  }
62
67
  return super.updateOne(key, data, opts);
63
68
  }
@@ -65,14 +70,24 @@ class RolesService extends items_1.ItemsService {
65
70
  const primaryKeyField = this.schema.collections[this.collection].primary;
66
71
  const keys = data.map((item) => item[primaryKeyField]);
67
72
  const setsToNoAdmin = data.some((item) => item.admin_access === false);
68
- if (setsToNoAdmin) {
69
- await this.checkForOtherAdminRoles(keys);
73
+ try {
74
+ if (setsToNoAdmin) {
75
+ await this.checkForOtherAdminRoles(keys);
76
+ }
77
+ }
78
+ catch (err) {
79
+ (opts || (opts = {})).preMutationException = err;
70
80
  }
71
81
  return super.updateBatch(data, opts);
72
82
  }
73
83
  async updateMany(keys, data, opts) {
74
- if ('admin_access' in data && data.admin_access === false) {
75
- await this.checkForOtherAdminRoles(keys);
84
+ try {
85
+ if ('admin_access' in data && data.admin_access === false) {
86
+ await this.checkForOtherAdminRoles(keys);
87
+ }
88
+ }
89
+ catch (err) {
90
+ (opts || (opts = {})).preMutationException = err;
76
91
  }
77
92
  return super.updateMany(keys, data, opts);
78
93
  }
@@ -81,7 +96,13 @@ class RolesService extends items_1.ItemsService {
81
96
  return key;
82
97
  }
83
98
  async deleteMany(keys) {
84
- await this.checkForOtherAdminRoles(keys);
99
+ const opts = {};
100
+ try {
101
+ await this.checkForOtherAdminRoles(keys);
102
+ }
103
+ catch (err) {
104
+ opts.preMutationException = err;
105
+ }
85
106
  await this.knex.transaction(async (trx) => {
86
107
  const itemsService = new items_1.ItemsService('directus_roles', {
87
108
  knex: trx,
@@ -106,17 +127,17 @@ class RolesService extends items_1.ItemsService {
106
127
  // Delete permissions/presets for this role, suspend all remaining users in role
107
128
  await permissionsService.deleteByQuery({
108
129
  filter: { role: { _in: keys } },
109
- });
130
+ }, opts);
110
131
  await presetsService.deleteByQuery({
111
132
  filter: { role: { _in: keys } },
112
- });
133
+ }, opts);
113
134
  await usersService.updateByQuery({
114
135
  filter: { role: { _in: keys } },
115
136
  }, {
116
137
  status: 'suspended',
117
138
  role: null,
118
- });
119
- await itemsService.deleteMany(keys);
139
+ }, opts);
140
+ await itemsService.deleteMany(keys, opts);
120
141
  });
121
142
  return keys;
122
143
  }
@@ -0,0 +1,15 @@
1
+ import { Accountability } from '@directus/shared/types';
2
+ import { Knex } from 'knex';
3
+ import { AbstractServiceOptions, Snapshot, SnapshotDiff, SnapshotDiffWithHash, SnapshotWithHash } from '../types';
4
+ export declare class SchemaService {
5
+ knex: Knex;
6
+ accountability: Accountability | null;
7
+ constructor(options: Omit<AbstractServiceOptions, 'schema'>);
8
+ snapshot(): Promise<Snapshot>;
9
+ apply(payload: SnapshotDiffWithHash): Promise<void>;
10
+ diff(snapshot: Snapshot, options?: {
11
+ currentSnapshot?: Snapshot;
12
+ force?: boolean;
13
+ }): Promise<SnapshotDiff | null>;
14
+ getHashedSnapshot(snapshot: Snapshot): SnapshotWithHash;
15
+ }
@@ -0,0 +1,58 @@
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.SchemaService = void 0;
7
+ const database_1 = __importDefault(require("../database"));
8
+ const exceptions_1 = require("../exceptions");
9
+ const apply_diff_1 = require("../utils/apply-diff");
10
+ const get_snapshot_1 = require("../utils/get-snapshot");
11
+ const get_snapshot_diff_1 = require("../utils/get-snapshot-diff");
12
+ const get_versioned_hash_1 = require("../utils/get-versioned-hash");
13
+ const validate_diff_1 = require("../utils/validate-diff");
14
+ const validate_snapshot_1 = require("../utils/validate-snapshot");
15
+ class SchemaService {
16
+ constructor(options) {
17
+ var _a, _b;
18
+ this.knex = (_a = options.knex) !== null && _a !== void 0 ? _a : (0, database_1.default)();
19
+ this.accountability = (_b = options.accountability) !== null && _b !== void 0 ? _b : null;
20
+ }
21
+ async snapshot() {
22
+ var _a;
23
+ if (((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true)
24
+ throw new exceptions_1.ForbiddenException();
25
+ const currentSnapshot = await (0, get_snapshot_1.getSnapshot)({ database: this.knex });
26
+ return currentSnapshot;
27
+ }
28
+ async apply(payload) {
29
+ var _a;
30
+ if (((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true)
31
+ throw new exceptions_1.ForbiddenException();
32
+ const currentSnapshot = await this.snapshot();
33
+ const snapshotWithHash = this.getHashedSnapshot(currentSnapshot);
34
+ if (!(0, validate_diff_1.validateApplyDiff)(payload, snapshotWithHash))
35
+ return;
36
+ await (0, apply_diff_1.applyDiff)(currentSnapshot, payload.diff, { database: this.knex });
37
+ }
38
+ async diff(snapshot, options) {
39
+ var _a, _b;
40
+ if (((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true)
41
+ throw new exceptions_1.ForbiddenException();
42
+ (0, validate_snapshot_1.validateSnapshot)(snapshot, options === null || options === void 0 ? void 0 : options.force);
43
+ const currentSnapshot = (_b = options === null || options === void 0 ? void 0 : options.currentSnapshot) !== null && _b !== void 0 ? _b : (await (0, get_snapshot_1.getSnapshot)({ database: this.knex }));
44
+ const diff = (0, get_snapshot_diff_1.getSnapshotDiff)(currentSnapshot, snapshot);
45
+ if (diff.collections.length === 0 && diff.fields.length === 0 && diff.relations.length === 0) {
46
+ return null;
47
+ }
48
+ return diff;
49
+ }
50
+ getHashedSnapshot(snapshot) {
51
+ const snapshotHash = (0, get_versioned_hash_1.getVersionedHash)(snapshot);
52
+ return {
53
+ ...snapshot,
54
+ hash: snapshotHash,
55
+ };
56
+ }
57
+ }
58
+ exports.SchemaService = SchemaService;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
- import { AbstractServiceOptions, LoginResult, Item, PrimaryKey, MutationOptions } from '../types';
2
- import { ItemsService } from './items';
1
+ import { AbstractServiceOptions, Item, LoginResult, MutationOptions, PrimaryKey } from '../types';
3
2
  import { AuthorizationService } from './authorization';
3
+ import { ItemsService } from './items';
4
4
  export declare class SharesService extends ItemsService {
5
5
  authorizationService: AuthorizationService;
6
6
  constructor(options: AbstractServiceOptions);
@@ -4,18 +4,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SharesService = void 0;
7
- const items_1 = require("./items");
8
7
  const argon2_1 = __importDefault(require("argon2"));
9
8
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
- const ms_1 = __importDefault(require("ms"));
11
- const exceptions_1 = require("../exceptions");
12
9
  const env_1 = __importDefault(require("../env"));
13
- const authorization_1 = require("./authorization");
14
- const users_1 = require("./users");
15
- const mail_1 = require("./mail");
16
- const user_name_1 = require("../utils/user-name");
10
+ const exceptions_1 = require("../exceptions");
11
+ const get_milliseconds_1 = require("../utils/get-milliseconds");
17
12
  const md_1 = require("../utils/md");
18
13
  const url_1 = require("../utils/url");
14
+ const user_name_1 = require("../utils/user-name");
15
+ const authorization_1 = require("./authorization");
16
+ const items_1 = require("./items");
17
+ const mail_1 = require("./mail");
18
+ const users_1 = require("./users");
19
19
  class SharesService extends items_1.ItemsService {
20
20
  constructor(options) {
21
21
  super('directus_shares', options);
@@ -80,7 +80,7 @@ class SharesService extends items_1.ItemsService {
80
80
  issuer: 'directus',
81
81
  });
82
82
  const refreshToken = nanoid(64);
83
- const refreshTokenExpiration = new Date(Date.now() + (0, ms_1.default)(env_1.default.REFRESH_TOKEN_TTL));
83
+ const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default.REFRESH_TOKEN_TTL, 0));
84
84
  await this.knex('directus_sessions').insert({
85
85
  token: refreshToken,
86
86
  expires: refreshTokenExpiration,
@@ -93,7 +93,7 @@ class SharesService extends items_1.ItemsService {
93
93
  return {
94
94
  accessToken,
95
95
  refreshToken,
96
- expires: (0, ms_1.default)(env_1.default.ACCESS_TOKEN_TTL),
96
+ expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default.ACCESS_TOKEN_TTL),
97
97
  };
98
98
  }
99
99
  /**
@@ -131,11 +131,16 @@ class UsersService extends items_1.ItemsService {
131
131
  async createMany(data, opts) {
132
132
  const emails = data.map((payload) => payload.email).filter((email) => email);
133
133
  const passwords = data.map((payload) => payload.password).filter((password) => password);
134
- if (emails.length) {
135
- await this.checkUniqueEmails(emails);
134
+ try {
135
+ if (emails.length) {
136
+ await this.checkUniqueEmails(emails);
137
+ }
138
+ if (passwords.length) {
139
+ await this.checkPasswordPolicy(passwords);
140
+ }
136
141
  }
137
- if (passwords.length) {
138
- await this.checkPasswordPolicy(passwords);
142
+ catch (err) {
143
+ (opts || (opts = {})).preMutationException = err;
139
144
  }
140
145
  return await super.createMany(data, opts);
141
146
  }
@@ -175,44 +180,49 @@ class UsersService extends items_1.ItemsService {
175
180
  */
176
181
  async updateMany(keys, data, opts) {
177
182
  var _a, _b;
178
- if (data.role) {
179
- // data.role will be an object with id with GraphQL mutations
180
- const roleId = (_b = (_a = data.role) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : data.role;
181
- const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
182
- if (!(newRole === null || newRole === void 0 ? void 0 : newRole.admin_access)) {
183
- await this.checkRemainingAdminExistence(keys);
183
+ try {
184
+ if (data.role) {
185
+ // data.role will be an object with id with GraphQL mutations
186
+ const roleId = (_b = (_a = data.role) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : data.role;
187
+ const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
188
+ if (!(newRole === null || newRole === void 0 ? void 0 : newRole.admin_access)) {
189
+ await this.checkRemainingAdminExistence(keys);
190
+ }
184
191
  }
185
- }
186
- if (data.status !== undefined && data.status !== 'active') {
187
- await this.checkRemainingActiveAdmin(keys);
188
- }
189
- if (data.email) {
190
- if (keys.length > 1) {
191
- throw new record_not_unique_1.RecordNotUniqueException('email', {
192
- collection: 'directus_users',
193
- field: 'email',
194
- invalid: data.email,
195
- });
192
+ if (data.status !== undefined && data.status !== 'active') {
193
+ await this.checkRemainingActiveAdmin(keys);
196
194
  }
197
- await this.checkUniqueEmails([data.email], keys[0]);
198
- }
199
- if (data.password) {
200
- await this.checkPasswordPolicy([data.password]);
201
- }
202
- if (data.tfa_secret !== undefined) {
203
- throw new exceptions_2.InvalidPayloadException(`You can't change the "tfa_secret" value manually.`);
204
- }
205
- if (data.provider !== undefined) {
206
- if (this.accountability && this.accountability.admin !== true) {
207
- throw new exceptions_2.InvalidPayloadException(`You can't change the "provider" value manually.`);
195
+ if (data.email) {
196
+ if (keys.length > 1) {
197
+ throw new record_not_unique_1.RecordNotUniqueException('email', {
198
+ collection: 'directus_users',
199
+ field: 'email',
200
+ invalid: data.email,
201
+ });
202
+ }
203
+ await this.checkUniqueEmails([data.email], keys[0]);
208
204
  }
209
- data.auth_data = null;
210
- }
211
- if (data.external_identifier !== undefined) {
212
- if (this.accountability && this.accountability.admin !== true) {
213
- throw new exceptions_2.InvalidPayloadException(`You can't change the "external_identifier" value manually.`);
205
+ if (data.password) {
206
+ await this.checkPasswordPolicy([data.password]);
207
+ }
208
+ if (data.tfa_secret !== undefined) {
209
+ throw new exceptions_2.InvalidPayloadException(`You can't change the "tfa_secret" value manually.`);
214
210
  }
215
- data.auth_data = null;
211
+ if (data.provider !== undefined) {
212
+ if (this.accountability && this.accountability.admin !== true) {
213
+ throw new exceptions_2.InvalidPayloadException(`You can't change the "provider" value manually.`);
214
+ }
215
+ data.auth_data = null;
216
+ }
217
+ if (data.external_identifier !== undefined) {
218
+ if (this.accountability && this.accountability.admin !== true) {
219
+ throw new exceptions_2.InvalidPayloadException(`You can't change the "external_identifier" value manually.`);
220
+ }
221
+ data.auth_data = null;
222
+ }
223
+ }
224
+ catch (err) {
225
+ (opts || (opts = {})).preMutationException = err;
216
226
  }
217
227
  return await super.updateMany(keys, data, opts);
218
228
  }
@@ -227,7 +237,12 @@ class UsersService extends items_1.ItemsService {
227
237
  * Delete multiple users by primary key
228
238
  */
229
239
  async deleteMany(keys, opts) {
230
- await this.checkRemainingAdminExistence(keys);
240
+ try {
241
+ await this.checkRemainingAdminExistence(keys);
242
+ }
243
+ catch (err) {
244
+ (opts || (opts = {})).preMutationException = err;
245
+ }
231
246
  await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
232
247
  await super.deleteMany(keys, opts);
233
248
  return keys;
@@ -248,8 +263,14 @@ class UsersService extends items_1.ItemsService {
248
263
  return await this.deleteMany(keys, opts);
249
264
  }
250
265
  async inviteUser(email, role, url, subject) {
251
- if (url && (0, is_url_allowed_1.default)(url, env_1.default.USER_INVITE_URL_ALLOW_LIST) === false) {
252
- throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
266
+ const opts = {};
267
+ try {
268
+ if (url && (0, is_url_allowed_1.default)(url, env_1.default.USER_INVITE_URL_ALLOW_LIST) === false) {
269
+ throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to invite users.`);
270
+ }
271
+ }
272
+ catch (err) {
273
+ opts.preMutationException = err;
253
274
  }
254
275
  const emails = (0, utils_1.toArray)(email);
255
276
  const mailService = new mail_1.MailService({
@@ -263,7 +284,7 @@ class UsersService extends items_1.ItemsService {
263
284
  const inviteURL = url ? new url_1.Url(url) : new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin', 'accept-invite');
264
285
  inviteURL.setQuery('token', token);
265
286
  // Create user first to verify uniqueness
266
- await this.createOne({ email, role, status: 'invited' });
287
+ await this.createOne({ email, role, status: 'invited' }, opts);
267
288
  await mailService.send({
268
289
  to: email,
269
290
  subject: subjectLine,
@@ -293,9 +314,6 @@ class UsersService extends items_1.ItemsService {
293
314
  await service.updateOne(user.id, { password, status: 'active' });
294
315
  }
295
316
  async requestPasswordReset(email, url, subject) {
296
- if (url && (0, is_url_allowed_1.default)(url, env_1.default.PASSWORD_RESET_URL_ALLOW_LIST) === false) {
297
- throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
298
- }
299
317
  const STALL_TIME = 500;
300
318
  const timeStart = perf_hooks_1.performance.now();
301
319
  const user = await this.knex
@@ -307,6 +325,9 @@ class UsersService extends items_1.ItemsService {
307
325
  await (0, stall_1.stall)(STALL_TIME, timeStart);
308
326
  throw new exceptions_2.ForbiddenException();
309
327
  }
328
+ if (url && (0, is_url_allowed_1.default)(url, env_1.default.PASSWORD_RESET_URL_ALLOW_LIST) === false) {
329
+ throw new exceptions_2.InvalidPayloadException(`Url "${url}" can't be used to reset passwords.`);
330
+ }
310
331
  const mailService = new mail_1.MailService({
311
332
  schema: this.schema,
312
333
  knex: this.knex,
@@ -336,7 +357,13 @@ class UsersService extends items_1.ItemsService {
336
357
  const { email, scope, hash } = jsonwebtoken_1.default.verify(token, env_1.default.SECRET, { issuer: 'directus' });
337
358
  if (scope !== 'password-reset' || !hash)
338
359
  throw new exceptions_2.ForbiddenException();
339
- await this.checkPasswordPolicy([password]);
360
+ const opts = {};
361
+ try {
362
+ await this.checkPasswordPolicy([password]);
363
+ }
364
+ catch (err) {
365
+ opts.preMutationException = err;
366
+ }
340
367
  const user = await this.knex.select('id', 'status', 'password').from('directus_users').where({ email }).first();
341
368
  if ((user === null || user === void 0 ? void 0 : user.status) !== 'active' || hash !== (0, utils_2.getSimpleHash)('' + user.password)) {
342
369
  throw new exceptions_2.ForbiddenException();
@@ -350,7 +377,7 @@ class UsersService extends items_1.ItemsService {
350
377
  admin: true, // We need to skip permissions checks for the update call below
351
378
  },
352
379
  });
353
- await service.updateOne(user.id, { password, status: 'active' });
380
+ await service.updateOne(user.id, { password, status: 'active' }, opts);
354
381
  }
355
382
  }
356
383
  exports.UsersService = UsersService;
@@ -1,6 +1,6 @@
1
1
  import { ResizeOptions, Sharp } from 'sharp';
2
2
  export declare const TransformationMethods: readonly ["toFormat", "jpeg", "png", "tiff", "webp", "resize", "extend", "extract", "trim", "rotate", "flip", "flop", "sharpen", "median", "blur", "flatten", "gamma", "negate", "normalise", "normalize", "clahe", "convolve", "threshold", "linear", "recomb", "modulate", "tint", "greyscale", "grayscale", "toColorspace", "toColourspace", "removeAlpha", "ensureAlpha", "extractChannel", "bandbool"];
3
- type AllowedSharpMethods = Pick<Sharp, typeof TransformationMethods[number]>;
3
+ type AllowedSharpMethods = Pick<Sharp, (typeof TransformationMethods)[number]>;
4
4
  export type TransformationMap = {
5
5
  [M in keyof AllowedSharpMethods]: readonly [M, ...Parameters<AllowedSharpMethods[M]>];
6
6
  };
@@ -0,0 +1,3 @@
1
+ export type Driver = 'mysql' | 'pg' | 'cockroachdb' | 'sqlite3' | 'oracledb' | 'mssql';
2
+ export declare const DatabaseClients: readonly ["mysql", "postgres", "cockroachdb", "sqlite", "oracle", "mssql", "redshift"];
3
+ export type DatabaseClient = (typeof DatabaseClients)[number];
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseClients = void 0;
4
+ exports.DatabaseClients = ['mysql', 'postgres', 'cockroachdb', 'sqlite', 'oracle', 'mssql', 'redshift'];
@@ -2,6 +2,7 @@ export * from './assets';
2
2
  export * from './ast';
3
3
  export * from './auth';
4
4
  export * from './collection';
5
+ export * from './database';
5
6
  export * from './events';
6
7
  export * from './files';
7
8
  export * from './graphql';
@@ -18,6 +18,7 @@ __exportStar(require("./assets"), exports);
18
18
  __exportStar(require("./ast"), exports);
19
19
  __exportStar(require("./auth"), exports);
20
20
  __exportStar(require("./collection"), exports);
21
+ __exportStar(require("./database"), exports);
21
22
  __exportStar(require("./events"), exports);
22
23
  __exportStar(require("./files"), exports);
23
24
  __exportStar(require("./graphql"), exports);
@@ -2,6 +2,7 @@
2
2
  * I know this looks a little silly, but it allows us to explicitly differentiate between when we're
3
3
  * expecting an item vs any other generic object.
4
4
  */
5
+ import { BaseException } from '@directus/shared/exceptions';
5
6
  import { EventContext } from '@directus/shared/types';
6
7
  export type Item = Record<string, any>;
7
8
  export type PrimaryKey = string | number;
@@ -36,6 +37,10 @@ export type MutationOptions = {
36
37
  * Can be used to queue up the nested events from item service's create, update and delete
37
38
  */
38
39
  bypassEmitAction?: (params: ActionEventParams) => void;
40
+ /**
41
+ * The validation error to throw right before the mutation takes place
42
+ */
43
+ preMutationException?: BaseException;
39
44
  };
40
45
  export type ActionEventParams = {
41
46
  event: string | string[];
@@ -1,9 +1,11 @@
1
1
  import { Collection } from './collection';
2
2
  import { Relation, RelationMeta, Field, FieldMeta } from '@directus/shared/types';
3
3
  import { Diff } from 'deep-diff';
4
+ import { DatabaseClient } from './database';
4
5
  export type Snapshot = {
5
6
  version: number;
6
7
  directus: string;
8
+ vendor?: DatabaseClient;
7
9
  collections: Collection[];
8
10
  fields: SnapshotField[];
9
11
  relations: SnapshotRelation[];
@@ -14,6 +16,9 @@ export type SnapshotField = Field & {
14
16
  export type SnapshotRelation = Relation & {
15
17
  meta: Omit<RelationMeta, 'id'>;
16
18
  };
19
+ export type SnapshotWithHash = Snapshot & {
20
+ hash: string;
21
+ };
17
22
  export type SnapshotDiff = {
18
23
  collections: {
19
24
  collection: string;
@@ -31,3 +36,20 @@ export type SnapshotDiff = {
31
36
  diff: Diff<SnapshotRelation | undefined>[];
32
37
  }[];
33
38
  };
39
+ export type SnapshotDiffWithHash = {
40
+ hash: string;
41
+ diff: SnapshotDiff;
42
+ };
43
+ /**
44
+ * Indicates the kind of change based on comparisons by deep-diff package
45
+ */
46
+ export declare const DiffKind: {
47
+ /** indicates a newly added property/element */
48
+ readonly NEW: "N";
49
+ /** indicates a property/element was deleted */
50
+ readonly DELETE: "D";
51
+ /** indicates a property/element was edited */
52
+ readonly EDIT: "E";
53
+ /** indicates a change occurred within an array */
54
+ readonly ARRAY: "A";
55
+ };
@@ -1,2 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiffKind = void 0;
4
+ /**
5
+ * Indicates the kind of change based on comparisons by deep-diff package
6
+ */
7
+ exports.DiffKind = {
8
+ /** indicates a newly added property/element */
9
+ NEW: 'N',
10
+ /** indicates a property/element was deleted */
11
+ DELETE: 'D',
12
+ /** indicates a property/element was edited */
13
+ EDIT: 'E',
14
+ /** indicates a change occurred within an array */
15
+ ARRAY: 'A',
16
+ };
@@ -0,0 +1,9 @@
1
+ import { SchemaOverview } from '@directus/shared/types';
2
+ import { Knex } from 'knex';
3
+ import { Snapshot, SnapshotDiff, SnapshotField } from '../types';
4
+ import { Diff } from 'deep-diff';
5
+ export declare function applyDiff(currentSnapshot: Snapshot, snapshotDiff: SnapshotDiff, options?: {
6
+ database?: Knex;
7
+ schema?: SchemaOverview;
8
+ }): Promise<void>;
9
+ export declare function isNestedMetaUpdate(diff: Diff<SnapshotField | undefined>): boolean;