@directus/api 19.3.0 → 19.3.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.
@@ -1,16 +1,17 @@
1
1
  import { InvalidPayloadError, UnprocessableContentError } from '@directus/errors';
2
2
  import { getMatch } from 'ip-matching';
3
+ import { omit } from 'lodash-es';
3
4
  import { checkIncreasedUserLimits } from '../telemetry/utils/check-increased-user-limits.js';
4
5
  import { getRoleCountsByUsers } from '../telemetry/utils/get-role-counts-by-users.js';
5
6
  import {} from '../telemetry/utils/get-user-count.js';
6
7
  import { getUserCountsByRoles } from '../telemetry/utils/get-user-counts-by-roles.js';
8
+ import { shouldCheckUserLimits } from '../telemetry/utils/should-check-user-limits.js';
9
+ import { shouldClearCache } from '../utils/should-clear-cache.js';
7
10
  import { transaction } from '../utils/transaction.js';
8
11
  import { ItemsService } from './items.js';
9
12
  import { PermissionsService } from './permissions/index.js';
10
13
  import { PresetsService } from './presets.js';
11
14
  import { UsersService } from './users.js';
12
- import { shouldClearCache } from '../utils/should-clear-cache.js';
13
- import { omit } from 'lodash-es';
14
15
  export class RolesService extends ItemsService {
15
16
  constructor(options) {
16
17
  super('directus_roles', options);
@@ -171,28 +172,31 @@ export class RolesService extends ItemsService {
171
172
  }
172
173
  async createOne(data, opts) {
173
174
  this.assertValidIpAccess(data);
174
- const increasedCounts = {
175
- admin: 0,
176
- app: 0,
177
- api: 0,
178
- };
179
- const existingIds = [];
180
- if ('users' in data) {
181
- const type = this.getRoleAccessType(data);
182
- increasedCounts[type] += data['users'].length;
183
- for (const user of data['users']) {
184
- if (typeof user === 'string') {
185
- existingIds.push(user);
186
- }
187
- else if (typeof user === 'object' && 'id' in user) {
188
- existingIds.push(user['id']);
175
+ if (shouldCheckUserLimits()) {
176
+ const increasedCounts = {
177
+ admin: 0,
178
+ app: 0,
179
+ api: 0,
180
+ };
181
+ const existingIds = [];
182
+ if ('users' in data) {
183
+ const type = this.getRoleAccessType(data);
184
+ increasedCounts[type] += data['users'].length;
185
+ for (const user of data['users']) {
186
+ if (typeof user === 'string') {
187
+ existingIds.push(user);
188
+ }
189
+ else if (typeof user === 'object' && 'id' in user) {
190
+ existingIds.push(user['id']);
191
+ }
189
192
  }
190
193
  }
194
+ await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
191
195
  }
192
- await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
193
196
  return super.createOne(data, opts);
194
197
  }
195
198
  async createMany(data, opts) {
199
+ const needsUserLimitCheck = shouldCheckUserLimits();
196
200
  const increasedCounts = {
197
201
  admin: 0,
198
202
  app: 0,
@@ -201,7 +205,7 @@ export class RolesService extends ItemsService {
201
205
  const existingIds = [];
202
206
  for (const partialItem of data) {
203
207
  this.assertValidIpAccess(partialItem);
204
- if ('users' in partialItem) {
208
+ if (needsUserLimitCheck && 'users' in partialItem) {
205
209
  const type = this.getRoleAccessType(partialItem);
206
210
  increasedCounts[type] += partialItem['users'].length;
207
211
  for (const user of partialItem['users']) {
@@ -214,121 +218,127 @@ export class RolesService extends ItemsService {
214
218
  }
215
219
  }
216
220
  }
217
- await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
221
+ if (needsUserLimitCheck) {
222
+ await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
223
+ }
218
224
  return super.createMany(data, opts);
219
225
  }
220
226
  async updateOne(key, data, opts) {
221
227
  this.assertValidIpAccess(data);
222
228
  try {
223
- const increasedCounts = {
224
- admin: 0,
225
- app: 0,
226
- api: 0,
227
- };
228
- let increasedUsers = 0;
229
- const existingIds = [];
230
- let existingRole = await this.knex
231
- .count('directus_users.id', { as: 'count' })
232
- .select('directus_roles.admin_access', 'directus_roles.app_access')
233
- .from('directus_users')
234
- .where('directus_roles.id', '=', key)
235
- .andWhere('directus_users.status', '=', 'active')
236
- .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
237
- .groupBy('directus_roles.admin_access', 'directus_roles.app_access')
238
- .first();
239
- if (!existingRole) {
240
- try {
241
- const role = (await this.knex
242
- .select('admin_access', 'app_access')
243
- .from('directus_roles')
244
- .where('id', '=', key)
245
- .first()) ?? { admin_access: null, app_access: null };
246
- existingRole = { count: 0, ...role };
247
- }
248
- catch {
249
- existingRole = { count: 0, admin_access: null, app_access: null };
250
- }
251
- }
252
229
  if ('users' in data) {
253
230
  await this.checkForOtherAdminUsers(key, data['users']);
254
- const users = data['users'];
255
- if (Array.isArray(users)) {
256
- increasedUsers = users.length - Number(existingRole.count);
257
- for (const user of users) {
258
- if (typeof user === 'string') {
259
- existingIds.push(user);
260
- }
261
- else if (typeof user === 'object' && 'id' in user) {
262
- existingIds.push(user['id']);
263
- }
231
+ }
232
+ if (shouldCheckUserLimits()) {
233
+ const increasedCounts = {
234
+ admin: 0,
235
+ app: 0,
236
+ api: 0,
237
+ };
238
+ let increasedUsers = 0;
239
+ const existingIds = [];
240
+ let existingRole = await this.knex
241
+ .count('directus_users.id', { as: 'count' })
242
+ .select('directus_roles.admin_access', 'directus_roles.app_access')
243
+ .from('directus_users')
244
+ .where('directus_roles.id', '=', key)
245
+ .andWhere('directus_users.status', '=', 'active')
246
+ .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
247
+ .groupBy('directus_roles.admin_access', 'directus_roles.app_access')
248
+ .first();
249
+ if (!existingRole) {
250
+ try {
251
+ const role = (await this.knex
252
+ .select('admin_access', 'app_access')
253
+ .from('directus_roles')
254
+ .where('id', '=', key)
255
+ .first()) ?? { admin_access: null, app_access: null };
256
+ existingRole = { count: 0, ...role };
257
+ }
258
+ catch {
259
+ existingRole = { count: 0, admin_access: null, app_access: null };
264
260
  }
265
261
  }
266
- else {
267
- increasedUsers += users.create.length;
268
- increasedUsers -= users.delete.length;
269
- const userIds = [];
270
- for (const user of users.update) {
271
- if ('status' in user) {
272
- // account for users being activated and deactivated
273
- if (user['status'] === 'active') {
274
- increasedUsers++;
262
+ if ('users' in data) {
263
+ const users = data['users'];
264
+ if (Array.isArray(users)) {
265
+ increasedUsers = users.length - Number(existingRole.count);
266
+ for (const user of users) {
267
+ if (typeof user === 'string') {
268
+ existingIds.push(user);
275
269
  }
276
- else {
277
- increasedUsers--;
270
+ else if (typeof user === 'object' && 'id' in user) {
271
+ existingIds.push(user['id']);
278
272
  }
279
273
  }
280
- userIds.push(user.id);
281
274
  }
282
- try {
283
- const existingCounts = await getRoleCountsByUsers(this.knex, userIds);
284
- if (existingRole.admin_access) {
285
- increasedUsers += existingCounts.app + existingCounts.api;
275
+ else {
276
+ increasedUsers += users.create.length;
277
+ increasedUsers -= users.delete.length;
278
+ const userIds = [];
279
+ for (const user of users.update) {
280
+ if ('status' in user) {
281
+ // account for users being activated and deactivated
282
+ if (user['status'] === 'active') {
283
+ increasedUsers++;
284
+ }
285
+ else {
286
+ increasedUsers--;
287
+ }
288
+ }
289
+ userIds.push(user.id);
286
290
  }
287
- else if (existingRole.app_access) {
288
- increasedUsers += existingCounts.admin + existingCounts.api;
291
+ try {
292
+ const existingCounts = await getRoleCountsByUsers(this.knex, userIds);
293
+ if (existingRole.admin_access) {
294
+ increasedUsers += existingCounts.app + existingCounts.api;
295
+ }
296
+ else if (existingRole.app_access) {
297
+ increasedUsers += existingCounts.admin + existingCounts.api;
298
+ }
299
+ else {
300
+ increasedUsers += existingCounts.admin + existingCounts.app;
301
+ }
289
302
  }
290
- else {
291
- increasedUsers += existingCounts.admin + existingCounts.app;
303
+ catch {
304
+ // ignore failed user call
292
305
  }
293
306
  }
294
- catch {
295
- // ignore failed user call
296
- }
297
307
  }
298
- }
299
- let isAccessChanged = false;
300
- let accessType = 'api';
301
- if ('app_access' in data) {
302
- if (data['app_access'] === true) {
303
- accessType = 'app';
304
- if (!existingRole.app_access)
308
+ let isAccessChanged = false;
309
+ let accessType = 'api';
310
+ if ('app_access' in data) {
311
+ if (data['app_access'] === true) {
312
+ accessType = 'app';
313
+ if (!existingRole.app_access)
314
+ isAccessChanged = true;
315
+ }
316
+ else if (existingRole.app_access) {
305
317
  isAccessChanged = true;
318
+ }
306
319
  }
307
320
  else if (existingRole.app_access) {
308
- isAccessChanged = true;
321
+ accessType = 'app';
309
322
  }
310
- }
311
- else if (existingRole.app_access) {
312
- accessType = 'app';
313
- }
314
- if ('admin_access' in data) {
315
- if (data['admin_access'] === true) {
316
- accessType = 'admin';
317
- if (!existingRole.admin_access)
323
+ if ('admin_access' in data) {
324
+ if (data['admin_access'] === true) {
325
+ accessType = 'admin';
326
+ if (!existingRole.admin_access)
327
+ isAccessChanged = true;
328
+ }
329
+ else if (existingRole.admin_access) {
318
330
  isAccessChanged = true;
331
+ }
319
332
  }
320
333
  else if (existingRole.admin_access) {
321
- isAccessChanged = true;
334
+ accessType = 'admin';
322
335
  }
336
+ if (isAccessChanged) {
337
+ increasedCounts[accessType] += Number(existingRole.count);
338
+ }
339
+ increasedCounts[accessType] += increasedUsers;
340
+ await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
323
341
  }
324
- else if (existingRole.admin_access) {
325
- accessType = 'admin';
326
- }
327
- if (isAccessChanged) {
328
- increasedCounts[accessType] += Number(existingRole.count);
329
- }
330
- increasedCounts[accessType] += increasedUsers;
331
- await checkIncreasedUserLimits(this.knex, increasedCounts, existingIds);
332
342
  }
333
343
  catch (err) {
334
344
  (opts || (opts = {})).preMutationError = err;
@@ -370,7 +380,7 @@ export class RolesService extends ItemsService {
370
380
  if ('admin_access' in data && data['admin_access'] === false) {
371
381
  await this.checkForOtherAdminRoles(keys);
372
382
  }
373
- if ('admin_access' in data || 'app_access' in data) {
383
+ if (shouldCheckUserLimits() && ('admin_access' in data || 'app_access' in data)) {
374
384
  const existingCounts = await getUserCountsByRoles(this.knex, keys);
375
385
  const increasedCounts = {
376
386
  admin: 0,
@@ -12,6 +12,7 @@ import { checkIncreasedUserLimits } from '../telemetry/utils/check-increased-use
12
12
  import { getRoleCountsByRoles } from '../telemetry/utils/get-role-counts-by-roles.js';
13
13
  import { getRoleCountsByUsers } from '../telemetry/utils/get-role-counts-by-users.js';
14
14
  import {} from '../telemetry/utils/get-user-count.js';
15
+ import { shouldCheckUserLimits } from '../telemetry/utils/should-check-user-limits.js';
15
16
  import { getSecret } from '../utils/get-secret.js';
16
17
  import isUrlAllowed from '../utils/is-url-allowed.js';
17
18
  import { verifyJWT } from '../utils/jwt.js';
@@ -177,7 +178,7 @@ export class UsersService extends ItemsService {
177
178
  if (passwords.length) {
178
179
  await this.checkPasswordPolicy(passwords);
179
180
  }
180
- if (roles.length) {
181
+ if (shouldCheckUserLimits() && roles.length) {
181
182
  const increasedCounts = {
182
183
  admin: 0,
183
184
  app: 0,
@@ -248,6 +249,7 @@ export class UsersService extends ItemsService {
248
249
  */
249
250
  async updateMany(keys, data, opts) {
250
251
  try {
252
+ const needsUserLimitCheck = shouldCheckUserLimits();
251
253
  if (data['role']) {
252
254
  /*
253
255
  * data['role'] has the following cases:
@@ -270,7 +272,7 @@ export class UsersService extends ItemsService {
270
272
  if (!newRole?.admin_access) {
271
273
  await this.checkRemainingAdminExistence(keys);
272
274
  }
273
- if (newRole) {
275
+ if (needsUserLimitCheck && newRole) {
274
276
  const existingCounts = await getRoleCountsByUsers(this.knex, keys);
275
277
  const increasedCounts = {
276
278
  admin: 0,
@@ -289,13 +291,13 @@ export class UsersService extends ItemsService {
289
291
  await checkIncreasedUserLimits(this.knex, increasedCounts);
290
292
  }
291
293
  }
292
- if (data['role'] === null) {
294
+ if (needsUserLimitCheck && data['role'] === null) {
293
295
  await checkIncreasedUserLimits(this.knex, { admin: 0, app: 0, api: 1 });
294
296
  }
295
297
  if (data['status'] !== undefined && data['status'] !== 'active') {
296
298
  await this.checkRemainingActiveAdmin(keys);
297
299
  }
298
- if (data['status'] === 'active') {
300
+ if (needsUserLimitCheck && data['status'] === 'active') {
299
301
  const increasedCounts = await getRoleCountsByUsers(this.knex, keys, { inactiveUsers: true });
300
302
  await checkIncreasedUserLimits(this.knex, increasedCounts);
301
303
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Confirm whether user limits needs to be checked
3
+ */
4
+ export declare function shouldCheckUserLimits(): boolean;
@@ -0,0 +1,13 @@
1
+ import { useEnv } from '@directus/env';
2
+ /**
3
+ * Confirm whether user limits needs to be checked
4
+ */
5
+ export function shouldCheckUserLimits() {
6
+ const env = useEnv();
7
+ if (Number(env['USERS_ADMIN_ACCESS_LIMIT']) !== Infinity ||
8
+ Number(env['USERS_APP_ACCESS_LIMIT']) !== Infinity ||
9
+ Number(env['USERS_API_ACCESS_LIMIT']) !== Infinity) {
10
+ return true;
11
+ }
12
+ return false;
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "19.3.0",
3
+ "version": "19.3.1",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -65,7 +65,7 @@
65
65
  "@rollup/plugin-node-resolve": "15.2.3",
66
66
  "@rollup/plugin-virtual": "3.0.2",
67
67
  "@types/cookie": "0.6.0",
68
- "argon2": "0.40.1",
68
+ "argon2": "0.40.3",
69
69
  "async": "3.2.5",
70
70
  "axios": "1.7.2",
71
71
  "busboy": "1.6.0",
@@ -96,7 +96,7 @@
96
96
  "graphql-ws": "5.16.0",
97
97
  "helmet": "7.1.0",
98
98
  "icc": "3.0.0",
99
- "inquirer": "9.2.22",
99
+ "inquirer": "9.2.23",
100
100
  "ioredis": "5.4.1",
101
101
  "ip-matching": "2.1.2",
102
102
  "isolated-vm": "4.7.2",
@@ -140,35 +140,35 @@
140
140
  "sharp": "0.33.3",
141
141
  "snappy": "7.2.2",
142
142
  "stream-json": "1.8.0",
143
- "tar": "7.1.0",
144
- "tsx": "4.9.3",
143
+ "tar": "7.2.0",
144
+ "tsx": "4.12.0",
145
145
  "wellknown": "0.5.0",
146
146
  "ws": "8.17.0",
147
147
  "zod": "3.23.8",
148
148
  "zod-validation-error": "3.2.0",
149
- "@directus/app": "12.1.3",
150
- "@directus/env": "1.1.6",
151
149
  "@directus/constants": "11.0.4",
150
+ "@directus/app": "12.1.4",
151
+ "@directus/env": "1.1.6",
152
+ "@directus/extensions": "1.0.8",
152
153
  "@directus/errors": "0.3.2",
153
- "@directus/extensions": "1.0.7",
154
- "@directus/extensions-registry": "1.0.7",
154
+ "@directus/extensions-registry": "1.0.8",
155
+ "@directus/extensions-sdk": "11.0.8",
155
156
  "@directus/format-title": "10.1.2",
156
- "@directus/extensions-sdk": "11.0.7",
157
157
  "@directus/pressure": "1.0.20",
158
158
  "@directus/memory": "1.0.9",
159
- "@directus/schema": "11.0.2",
159
+ "@directus/schema": "11.0.3",
160
160
  "@directus/specs": "10.2.10",
161
161
  "@directus/storage": "10.0.13",
162
162
  "@directus/storage-driver-azure": "10.0.22",
163
- "@directus/storage-driver-cloudinary": "10.0.22",
164
163
  "@directus/storage-driver-gcs": "10.0.23",
165
164
  "@directus/storage-driver-local": "10.0.20",
166
- "@directus/storage-driver-supabase": "1.0.14",
165
+ "@directus/storage-driver-cloudinary": "10.0.22",
167
166
  "@directus/storage-driver-s3": "10.0.23",
167
+ "@directus/storage-driver-supabase": "1.0.14",
168
168
  "@directus/system-data": "1.0.4",
169
169
  "@directus/utils": "11.0.9",
170
- "directus": "10.12.0",
171
- "@directus/validation": "0.0.17"
170
+ "@directus/validation": "0.0.17",
171
+ "directus": "10.12.1"
172
172
  },
173
173
  "devDependencies": {
174
174
  "@ngneat/falso": "7.2.0",
@@ -182,7 +182,7 @@
182
182
  "@types/destroy": "1.0.3",
183
183
  "@types/encodeurl": "1.0.2",
184
184
  "@types/express": "4.17.21",
185
- "@types/express-serve-static-core": "4.19.0",
185
+ "@types/express-serve-static-core": "4.19.3",
186
186
  "@types/fs-extra": "11.0.4",
187
187
  "@types/glob-to-regexp": "0.4.4",
188
188
  "@types/inquirer": "9.0.7",
@@ -211,7 +211,7 @@
211
211
  "vitest": "1.5.3",
212
212
  "@directus/random": "0.2.8",
213
213
  "@directus/tsconfig": "1.0.1",
214
- "@directus/types": "11.1.2"
214
+ "@directus/types": "11.1.3"
215
215
  },
216
216
  "optionalDependencies": {
217
217
  "@keyv/redis": "2.8.4",