@directus/api 21.0.0 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/dist/app.js +4 -4
  2. package/dist/auth/drivers/ldap.js +4 -4
  3. package/dist/auth/drivers/local.js +4 -4
  4. package/dist/auth/drivers/oauth2.js +4 -4
  5. package/dist/auth/drivers/openid.js +2 -4
  6. package/dist/cache.js +3 -0
  7. package/dist/cli/commands/bootstrap/index.js +8 -2
  8. package/dist/cli/commands/init/index.js +9 -10
  9. package/dist/cli/commands/init/questions.d.ts +7 -6
  10. package/dist/cli/commands/init/questions.js +2 -2
  11. package/dist/cli/utils/create-env/index.d.ts +2 -2
  12. package/dist/cli/utils/create-env/index.js +3 -1
  13. package/dist/cli/utils/defaults.d.ts +4 -11
  14. package/dist/cli/utils/defaults.js +7 -1
  15. package/dist/cli/utils/drivers.js +1 -1
  16. package/dist/constants.d.ts +1 -1
  17. package/dist/controllers/access.d.ts +2 -0
  18. package/dist/controllers/access.js +148 -0
  19. package/dist/controllers/auth.js +5 -16
  20. package/dist/controllers/permissions.js +14 -2
  21. package/dist/controllers/policies.d.ts +2 -0
  22. package/dist/controllers/policies.js +169 -0
  23. package/dist/controllers/roles.js +22 -1
  24. package/dist/controllers/tus.js +14 -26
  25. package/dist/controllers/users.js +0 -55
  26. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
  27. package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
  28. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
  29. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
  30. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
  31. package/dist/database/get-ast-from-query/lib/parse-fields.js +200 -0
  32. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
  33. package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
  34. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
  35. package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
  36. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
  37. package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
  38. package/dist/database/helpers/fn/types.d.ts +2 -1
  39. package/dist/database/helpers/fn/types.js +1 -1
  40. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  41. package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
  42. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  43. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  44. package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
  45. package/dist/database/helpers/geometry/types.d.ts +1 -1
  46. package/dist/database/helpers/geometry/types.js +4 -2
  47. package/dist/database/helpers/index.d.ts +3 -3
  48. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
  49. package/dist/database/helpers/schema/dialects/cockroachdb.js +4 -0
  50. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  51. package/dist/database/helpers/schema/dialects/mssql.js +4 -0
  52. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
  53. package/dist/database/helpers/schema/dialects/oracle.js +4 -0
  54. package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
  55. package/dist/database/helpers/schema/dialects/postgres.js +4 -0
  56. package/dist/database/helpers/schema/types.d.ts +5 -0
  57. package/dist/database/helpers/schema/types.js +3 -0
  58. package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +8 -0
  59. package/dist/database/helpers/schema/utils/preprocess-bindings.js +30 -0
  60. package/dist/database/index.js +6 -1
  61. package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -2
  62. package/dist/database/migrations/20230721A-require-shares-fields.js +3 -5
  63. package/dist/database/migrations/20240716A-update-files-date-fields.js +3 -7
  64. package/dist/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
  65. package/dist/database/migrations/20240806A-permissions-policies.js +352 -0
  66. package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
  67. package/dist/database/run-ast/lib/get-db-query.js +218 -0
  68. package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
  69. package/dist/database/run-ast/lib/parse-current-level.js +41 -0
  70. package/dist/database/run-ast/run-ast.d.ts +7 -0
  71. package/dist/database/run-ast/run-ast.js +107 -0
  72. package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
  73. package/dist/database/run-ast/types.js +1 -0
  74. package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
  75. package/dist/database/run-ast/utils/apply-case-when.js +27 -0
  76. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
  77. package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
  78. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
  79. package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
  80. package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
  81. package/dist/database/run-ast/utils/get-field-alias.js +4 -0
  82. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
  83. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
  84. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
  85. package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
  86. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
  87. package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
  88. package/dist/database/run-ast/utils/with-preprocess-bindings.d.ts +2 -0
  89. package/dist/database/run-ast/utils/with-preprocess-bindings.js +14 -0
  90. package/dist/flows.js +3 -4
  91. package/dist/middleware/authenticate.js +2 -7
  92. package/dist/middleware/cache.js +1 -1
  93. package/dist/middleware/respond.js +1 -1
  94. package/dist/permissions/cache.d.ts +2 -0
  95. package/dist/permissions/cache.js +23 -0
  96. package/dist/permissions/lib/fetch-permissions.d.ts +11 -0
  97. package/dist/permissions/lib/fetch-permissions.js +56 -0
  98. package/dist/permissions/lib/fetch-policies.d.ts +14 -0
  99. package/dist/permissions/lib/fetch-policies.js +43 -0
  100. package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
  101. package/dist/permissions/lib/fetch-roles-tree.js +28 -0
  102. package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  103. package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
  104. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
  105. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
  106. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
  107. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
  108. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
  109. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
  110. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
  111. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
  112. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
  113. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
  114. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
  115. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
  116. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
  117. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
  118. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
  119. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
  120. package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
  121. package/dist/permissions/modules/fetch-global-access/types.js +1 -0
  122. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
  123. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
  124. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
  125. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
  126. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
  127. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
  128. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
  129. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
  130. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
  131. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
  132. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
  133. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
  134. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
  135. package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
  136. package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
  137. package/dist/permissions/modules/process-ast/process-ast.js +39 -0
  138. package/dist/permissions/modules/process-ast/types.d.ts +18 -0
  139. package/dist/permissions/modules/process-ast/types.js +1 -0
  140. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
  141. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
  142. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
  143. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
  144. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
  145. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +60 -0
  146. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
  147. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
  148. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
  149. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
  150. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
  151. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
  152. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
  153. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
  154. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
  155. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
  156. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
  157. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
  158. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
  159. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
  160. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
  161. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
  162. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
  163. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
  164. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
  165. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
  166. package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
  167. package/dist/permissions/modules/process-payload/process-payload.js +77 -0
  168. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
  169. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
  170. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
  171. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
  172. package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
  173. package/dist/permissions/modules/validate-access/validate-access.js +28 -0
  174. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
  175. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
  176. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
  177. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
  178. package/dist/permissions/types.d.ts +6 -0
  179. package/dist/permissions/types.js +1 -0
  180. package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
  181. package/dist/permissions/utils/create-default-accountability.js +11 -0
  182. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
  183. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
  184. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
  185. package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
  186. package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
  187. package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
  188. package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
  189. package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
  190. package/dist/permissions/utils/process-permissions.d.ts +7 -0
  191. package/dist/permissions/utils/process-permissions.js +9 -0
  192. package/dist/permissions/utils/with-cache.d.ts +10 -0
  193. package/dist/permissions/utils/with-cache.js +25 -0
  194. package/dist/server.js +17 -4
  195. package/dist/services/access.d.ts +10 -0
  196. package/dist/services/access.js +43 -0
  197. package/dist/services/activity.js +22 -10
  198. package/dist/services/assets.d.ts +2 -3
  199. package/dist/services/assets.js +10 -5
  200. package/dist/services/authentication.js +18 -18
  201. package/dist/services/collections.js +18 -17
  202. package/dist/services/fields.d.ts +0 -1
  203. package/dist/services/fields.js +54 -25
  204. package/dist/services/files.js +10 -3
  205. package/dist/services/graphql/index.d.ts +3 -3
  206. package/dist/services/graphql/index.js +126 -22
  207. package/dist/services/graphql/subscription.js +2 -4
  208. package/dist/services/import-export.d.ts +3 -1
  209. package/dist/services/import-export.js +67 -9
  210. package/dist/services/index.d.ts +3 -2
  211. package/dist/services/index.js +3 -2
  212. package/dist/services/items.js +115 -44
  213. package/dist/services/meta.js +60 -23
  214. package/dist/services/notifications.js +14 -6
  215. package/dist/services/payload.d.ts +9 -10
  216. package/dist/services/payload.js +18 -3
  217. package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
  218. package/dist/services/{permissions/index.js → permissions.js} +30 -54
  219. package/dist/services/policies.d.ts +12 -0
  220. package/dist/services/policies.js +87 -0
  221. package/dist/services/relations.d.ts +0 -6
  222. package/dist/services/relations.js +27 -30
  223. package/dist/services/roles.d.ts +4 -12
  224. package/dist/services/roles.js +57 -424
  225. package/dist/services/shares.d.ts +0 -2
  226. package/dist/services/shares.js +12 -8
  227. package/dist/services/specifications.d.ts +2 -2
  228. package/dist/services/specifications.js +39 -27
  229. package/dist/services/users.d.ts +1 -5
  230. package/dist/services/users.js +78 -161
  231. package/dist/services/utils.js +11 -7
  232. package/dist/services/versions.d.ts +0 -2
  233. package/dist/services/versions.js +34 -10
  234. package/dist/telemetry/lib/get-report.js +2 -2
  235. package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
  236. package/dist/telemetry/utils/check-user-limits.js +19 -0
  237. package/dist/types/ast.d.ts +43 -1
  238. package/dist/types/database.d.ts +1 -1
  239. package/dist/types/items.d.ts +11 -0
  240. package/dist/utils/apply-query.d.ts +11 -7
  241. package/dist/utils/apply-query.js +69 -11
  242. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +19 -0
  243. package/dist/utils/fetch-user-count/fetch-access-lookup.js +23 -0
  244. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
  245. package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
  246. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
  247. package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
  248. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
  249. package/dist/utils/fetch-user-count/fetch-user-count.js +64 -0
  250. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
  251. package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
  252. package/dist/utils/get-accountability-for-role.js +16 -25
  253. package/dist/utils/get-accountability-for-token.js +17 -16
  254. package/dist/utils/get-address.d.ts +5 -0
  255. package/dist/utils/get-address.js +13 -0
  256. package/dist/utils/get-cache-key.d.ts +1 -1
  257. package/dist/utils/get-cache-key.js +12 -1
  258. package/dist/utils/get-column.d.ts +2 -1
  259. package/dist/utils/get-column.js +1 -0
  260. package/dist/utils/get-service.js +5 -1
  261. package/dist/utils/reduce-schema.d.ts +4 -6
  262. package/dist/utils/reduce-schema.js +16 -32
  263. package/dist/utils/sanitize-schema.d.ts +1 -1
  264. package/dist/utils/transaction.js +28 -11
  265. package/dist/utils/validate-user-count-integrity.d.ts +13 -0
  266. package/dist/utils/validate-user-count-integrity.js +29 -0
  267. package/dist/websocket/authenticate.d.ts +0 -2
  268. package/dist/websocket/authenticate.js +0 -12
  269. package/dist/websocket/controllers/graphql.js +3 -7
  270. package/dist/websocket/controllers/hooks.js +4 -0
  271. package/dist/websocket/controllers/rest.js +2 -5
  272. package/dist/websocket/handlers/subscribe.js +0 -2
  273. package/dist/websocket/utils/items.d.ts +1 -1
  274. package/package.json +31 -30
  275. package/dist/database/run-ast.js +0 -458
  276. package/dist/middleware/check-ip.d.ts +0 -2
  277. package/dist/middleware/check-ip.js +0 -37
  278. package/dist/middleware/get-permissions.d.ts +0 -3
  279. package/dist/middleware/get-permissions.js +0 -10
  280. package/dist/services/authorization.d.ts +0 -17
  281. package/dist/services/authorization.js +0 -456
  282. package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
  283. package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
  284. package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
  285. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
  286. package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
  287. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
  288. package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
  289. package/dist/telemetry/utils/get-user-count.d.ts +0 -8
  290. package/dist/telemetry/utils/get-user-count.js +0 -33
  291. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
  292. package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
  293. package/dist/utils/get-ast-from-query.d.ts +0 -13
  294. package/dist/utils/get-ast-from-query.js +0 -297
  295. package/dist/utils/get-permissions.d.ts +0 -2
  296. package/dist/utils/get-permissions.js +0 -150
  297. package/dist/utils/merge-permissions-for-share.d.ts +0 -4
  298. package/dist/utils/merge-permissions-for-share.js +0 -109
  299. package/dist/utils/merge-permissions.js +0 -95
@@ -0,0 +1,28 @@
1
+ import { createCollectionForbiddenError, createFieldsForbiddenError } from './create-error.js';
2
+ export function validatePathPermissions(path, permissions, collection, fields) {
3
+ const permissionsForCollection = permissions.filter((permission) => permission.collection === collection);
4
+ if (permissionsForCollection.length === 0) {
5
+ throw createCollectionForbiddenError(path, collection);
6
+ }
7
+ // Set of all fields that are allowed to be queried combined
8
+ const allowedFields = new Set();
9
+ for (const { fields } of permissionsForCollection) {
10
+ if (!fields) {
11
+ continue;
12
+ }
13
+ for (const field of fields) {
14
+ if (field === '*') {
15
+ // Early exit in case all fields are allowed
16
+ return;
17
+ }
18
+ allowedFields.add(field);
19
+ }
20
+ }
21
+ const requestedFields = Array.from(fields);
22
+ const forbiddenFields = allowedFields.has('*')
23
+ ? []
24
+ : requestedFields.filter((field) => allowedFields.has(field) === false);
25
+ if (forbiddenFields.length > 0) {
26
+ throw createFieldsForbiddenError(path, collection, forbiddenFields);
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ import type { FieldOverview } from '@directus/types';
2
+ /**
3
+ * Checks if a given field is allowed to be set to `null`.
4
+ */
5
+ export declare function isFieldNullable(field: FieldOverview): boolean;
@@ -0,0 +1,12 @@
1
+ import { GENERATE_SPECIAL } from '../../../../constants.js';
2
+ /**
3
+ * Checks if a given field is allowed to be set to `null`.
4
+ */
5
+ export function isFieldNullable(field) {
6
+ if (field.nullable)
7
+ return true;
8
+ if (field.generated)
9
+ return true;
10
+ const hasGenerateSpecial = GENERATE_SPECIAL.some((name) => field.special.includes(name));
11
+ return hasGenerateSpecial;
12
+ }
@@ -0,0 +1,13 @@
1
+ import type { Accountability, Item, PermissionsAction } from '@directus/types';
2
+ import type { Context } from '../../types.js';
3
+ export interface ProcessPayloadOptions {
4
+ accountability: Accountability;
5
+ action: PermissionsAction;
6
+ collection: string;
7
+ payload: Item;
8
+ }
9
+ /**
10
+ * @note this only validates the top-level fields. The expectation is that this function is called
11
+ * for each level of nested insert separately
12
+ */
13
+ export declare function processPayload(options: ProcessPayloadOptions, context: Context): Promise<any>;
@@ -0,0 +1,77 @@
1
+ import { ForbiddenError } from '@directus/errors';
2
+ import { validatePayload } from '@directus/utils';
3
+ import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
4
+ import { assign, difference, uniq } from 'lodash-es';
5
+ import { fetchPermissions } from '../../lib/fetch-permissions.js';
6
+ import { fetchPolicies } from '../../lib/fetch-policies.js';
7
+ import { isFieldNullable } from './lib/is-field-nullable.js';
8
+ /**
9
+ * @note this only validates the top-level fields. The expectation is that this function is called
10
+ * for each level of nested insert separately
11
+ */
12
+ export async function processPayload(options, context) {
13
+ let permissions;
14
+ let permissionValidationRules = [];
15
+ if (!options.accountability.admin) {
16
+ const policies = await fetchPolicies(options.accountability, context);
17
+ permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
18
+ if (permissions.length === 0) {
19
+ throw new ForbiddenError({
20
+ reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
21
+ });
22
+ }
23
+ const fieldsAllowed = uniq(permissions.map(({ fields }) => fields ?? []).flat());
24
+ if (fieldsAllowed.includes('*') === false) {
25
+ const fieldsUsed = Object.keys(options.payload);
26
+ const notAllowed = difference(fieldsUsed, fieldsAllowed);
27
+ if (notAllowed.length > 0) {
28
+ const fieldStr = notAllowed.map((field) => `"${field}"`).join(', ');
29
+ throw new ForbiddenError({
30
+ reason: notAllowed.length === 1
31
+ ? `You don't have permission to access field ${fieldStr} in collection "${options.collection}" or it does not exist.`
32
+ : `You don't have permission to access fields ${fieldStr} in collection "${options.collection}" or they do not exist.`,
33
+ });
34
+ }
35
+ }
36
+ permissionValidationRules = permissions.map(({ validation }) => validation);
37
+ }
38
+ const fields = Object.values(context.schema.collections[options.collection]?.fields ?? {});
39
+ const fieldValidationRules = [];
40
+ for (const field of fields) {
41
+ if (!isFieldNullable(field)) {
42
+ const isSubmissionRequired = options.action === 'create' && field.defaultValue === null;
43
+ if (isSubmissionRequired) {
44
+ fieldValidationRules.push({
45
+ [field.field]: {
46
+ _submitted: true,
47
+ },
48
+ });
49
+ }
50
+ fieldValidationRules.push({
51
+ [field.field]: {
52
+ _nnull: true,
53
+ },
54
+ });
55
+ }
56
+ fieldValidationRules.push(field.validation);
57
+ }
58
+ const validationRules = [...fieldValidationRules, ...permissionValidationRules].filter((rule) => {
59
+ if (rule === null)
60
+ return false;
61
+ if (Object.keys(rule).length === 0)
62
+ return false;
63
+ return true;
64
+ });
65
+ if (validationRules.length > 0) {
66
+ const validationErrors = [];
67
+ validationErrors.push(...validatePayload({ _and: validationRules }, options.payload)
68
+ .map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))
69
+ .flat());
70
+ if (validationErrors.length > 0)
71
+ throw validationErrors;
72
+ }
73
+ if (!permissions)
74
+ return options.payload;
75
+ const presets = permissions.map((permission) => permission.presets);
76
+ return assign({}, ...presets, options.payload);
77
+ }
@@ -0,0 +1,12 @@
1
+ import type { Accountability, PermissionsAction } from '@directus/types';
2
+ import type { Context } from '../../../types.js';
3
+ export interface ValidateCollectionAccessOptions {
4
+ accountability: Accountability;
5
+ action: PermissionsAction;
6
+ collection: string;
7
+ }
8
+ /**
9
+ * Check if you have (limited) access to a given collection by making sure there's at least 1
10
+ * permission rule available for the collection and action combo
11
+ */
12
+ export declare function validateCollectionAccess(options: ValidateCollectionAccessOptions, context: Context): Promise<boolean>;
@@ -0,0 +1,11 @@
1
+ import { fetchPermissions } from '../../../lib/fetch-permissions.js';
2
+ import { fetchPolicies } from '../../../lib/fetch-policies.js';
3
+ /**
4
+ * Check if you have (limited) access to a given collection by making sure there's at least 1
5
+ * permission rule available for the collection and action combo
6
+ */
7
+ export async function validateCollectionAccess(options, context) {
8
+ const policies = await fetchPolicies(options.accountability, context);
9
+ const permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
10
+ return permissions.length > 0;
11
+ }
@@ -0,0 +1,9 @@
1
+ import type { Accountability, PermissionsAction, PrimaryKey } from '@directus/types';
2
+ import type { Context } from '../../../types.js';
3
+ export interface ValidateItemAccessOptions {
4
+ accountability: Accountability;
5
+ action: PermissionsAction;
6
+ collection: string;
7
+ primaryKeys: PrimaryKey[];
8
+ }
9
+ export declare function validateItemAccess(options: ValidateItemAccessOptions, context: Context): Promise<boolean>;
@@ -0,0 +1,33 @@
1
+ import { getAstFromQuery } from '../../../../database/get-ast-from-query/get-ast-from-query.js';
2
+ import { runAst } from '../../../../database/run-ast/run-ast.js';
3
+ import { processAst } from '../../process-ast/process-ast.js';
4
+ export async function validateItemAccess(options, context) {
5
+ const primaryKeyField = context.schema.collections[options.collection]?.primary;
6
+ if (!primaryKeyField) {
7
+ throw new Error(`Cannot find primary key for collection "${options.collection}"`);
8
+ }
9
+ // When we're looking up access to specific items, we have to read them from the database to
10
+ // make sure you are allowed to access them.
11
+ const query = {
12
+ // We don't actually need any of the field data, just want to know if we can read the item as
13
+ // whole or not
14
+ fields: [],
15
+ limit: options.primaryKeys.length,
16
+ filter: {
17
+ [primaryKeyField]: {
18
+ _in: options.primaryKeys,
19
+ },
20
+ },
21
+ };
22
+ const ast = await getAstFromQuery({
23
+ accountability: options.accountability,
24
+ query,
25
+ collection: options.collection,
26
+ }, context);
27
+ await processAst({ ast, ...options }, context);
28
+ const items = await runAst(ast, context.schema, { knex: context.knex });
29
+ if (items && items.length === options.primaryKeys.length) {
30
+ return true;
31
+ }
32
+ return false;
33
+ }
@@ -0,0 +1,14 @@
1
+ import type { Accountability, PermissionsAction, PrimaryKey } from '@directus/types';
2
+ import type { Context } from '../../types.js';
3
+ export interface ValidateAccessOptions {
4
+ accountability: Accountability;
5
+ action: PermissionsAction;
6
+ collection: string;
7
+ primaryKeys?: PrimaryKey[];
8
+ }
9
+ /**
10
+ * Validate if the current user has access to perform action against the given collection and
11
+ * optional primary keys. This is done by reading the item from the database using the access
12
+ * control rules and checking if we got the expected result back
13
+ */
14
+ export declare function validateAccess(options: ValidateAccessOptions, context: Context): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { ForbiddenError } from '@directus/errors';
2
+ import { validateCollectionAccess } from './lib/validate-collection-access.js';
3
+ import { validateItemAccess } from './lib/validate-item-access.js';
4
+ /**
5
+ * Validate if the current user has access to perform action against the given collection and
6
+ * optional primary keys. This is done by reading the item from the database using the access
7
+ * control rules and checking if we got the expected result back
8
+ */
9
+ export async function validateAccess(options, context) {
10
+ if (options.accountability.admin === true) {
11
+ return;
12
+ }
13
+ let access;
14
+ // If primary keys are passed, we have to confirm the access by actually trying to read the items
15
+ // from the database. If no keys are passed, we can simply check if the collection+action combo
16
+ // exists within permissions
17
+ if (options.primaryKeys) {
18
+ access = await validateItemAccess(options, context);
19
+ }
20
+ else {
21
+ access = await validateCollectionAccess(options, context);
22
+ }
23
+ if (!access) {
24
+ throw new ForbiddenError({
25
+ reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
26
+ });
27
+ }
28
+ }
@@ -0,0 +1 @@
1
+ export declare function validateRemainingAdminCount(count: number): void;
@@ -0,0 +1,8 @@
1
+ import { UnprocessableContentError } from '@directus/errors';
2
+ export function validateRemainingAdminCount(count) {
3
+ if (count <= 0) {
4
+ throw new UnprocessableContentError({
5
+ reason: `Cannot remove the last admin user from the system`,
6
+ });
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ import { type FetchUserCountOptions } from '../../../utils/fetch-user-count/fetch-user-count.js';
2
+ import type { Context } from '../../types.js';
3
+ export interface ValidateRemainingAdminUsersOptions extends Pick<FetchUserCountOptions, 'excludeAccessRows' | 'excludePolicies' | 'excludeUsers' | 'excludeRoles'> {
4
+ }
5
+ export declare function validateRemainingAdminUsers(options: ValidateRemainingAdminUsersOptions, context: Context): Promise<void>;
@@ -0,0 +1,10 @@
1
+ import { fetchUserCount } from '../../../utils/fetch-user-count/fetch-user-count.js';
2
+ import { validateRemainingAdminCount } from './validate-remaining-admin-count.js';
3
+ export async function validateRemainingAdminUsers(options, context) {
4
+ const { admin } = await fetchUserCount({
5
+ ...options,
6
+ adminOnly: true,
7
+ knex: context.knex,
8
+ });
9
+ validateRemainingAdminCount(admin);
10
+ }
@@ -0,0 +1,6 @@
1
+ import type { SchemaOverview } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ export interface Context {
4
+ schema: SchemaOverview;
5
+ knex: Knex;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Accountability } from '@directus/types';
2
+ export declare function createDefaultAccountability(overrides?: Partial<Accountability>): Accountability;
@@ -0,0 +1,11 @@
1
+ export function createDefaultAccountability(overrides) {
2
+ return {
3
+ role: null,
4
+ user: null,
5
+ roles: [],
6
+ admin: false,
7
+ app: false,
8
+ ip: null,
9
+ ...overrides,
10
+ };
11
+ }
@@ -0,0 +1,8 @@
1
+ import type { Permission } from '@directus/types';
2
+ export interface RequiredPermissionContext {
3
+ $CURRENT_USER: Set<string>;
4
+ $CURRENT_ROLE: Set<string>;
5
+ $CURRENT_ROLES: Set<string>;
6
+ $CURRENT_POLICIES: Set<string>;
7
+ }
8
+ export declare function extractRequiredDynamicVariableContext(permissions: Permission[]): RequiredPermissionContext;
@@ -0,0 +1,27 @@
1
+ import { deepMap } from '@directus/utils';
2
+ export function extractRequiredDynamicVariableContext(permissions) {
3
+ const permissionContext = {
4
+ $CURRENT_USER: new Set(),
5
+ $CURRENT_ROLE: new Set(),
6
+ $CURRENT_ROLES: new Set(),
7
+ $CURRENT_POLICIES: new Set(),
8
+ };
9
+ for (const permission of permissions) {
10
+ deepMap(permission.permissions, extractPermissionData);
11
+ deepMap(permission.validation, extractPermissionData);
12
+ deepMap(permission.presets, extractPermissionData);
13
+ }
14
+ return permissionContext;
15
+ function extractPermissionData(val) {
16
+ for (const placeholder of [
17
+ '$CURRENT_USER',
18
+ '$CURRENT_ROLE',
19
+ '$CURRENT_ROLES',
20
+ '$CURRENT_POLICIES',
21
+ ]) {
22
+ if (typeof val === 'string' && val.startsWith(`${placeholder}.`)) {
23
+ permissionContext[placeholder].add(val.replace(`${placeholder}.`, ''));
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,9 @@
1
+ import type { Accountability, Permission } from '@directus/types';
2
+ import type { Context } from '../types.js';
3
+ export declare const fetchDynamicVariableContext: typeof _fetchDynamicVariableContext;
4
+ export interface FetchDynamicVariableContext {
5
+ accountability: Pick<Accountability, 'user' | 'role' | 'roles'>;
6
+ policies: string[];
7
+ permissions: Permission[];
8
+ }
9
+ export declare function _fetchDynamicVariableContext(options: FetchDynamicVariableContext, context: Context): Promise<Record<string, any>>;
@@ -0,0 +1,43 @@
1
+ import { extractRequiredDynamicVariableContext } from './extract-required-dynamic-variable-context.js';
2
+ import { withCache } from './with-cache.js';
3
+ export const fetchDynamicVariableContext = withCache('permission-dynamic-variables', _fetchDynamicVariableContext, ({ policies, permissions, accountability: { user, role, roles } }) => ({
4
+ policies,
5
+ permissions,
6
+ accountability: {
7
+ user,
8
+ role,
9
+ roles,
10
+ },
11
+ }));
12
+ export async function _fetchDynamicVariableContext(options, context) {
13
+ const { UsersService } = await import('../../services/users.js');
14
+ const { RolesService } = await import('../../services/roles.js');
15
+ const { PoliciesService } = await import('../../services/policies.js');
16
+ const contextData = {};
17
+ const permissionContext = extractRequiredDynamicVariableContext(options.permissions);
18
+ if (options.accountability.user && (permissionContext.$CURRENT_USER?.size ?? 0) > 0) {
19
+ const usersService = new UsersService(context);
20
+ contextData['$CURRENT_USER'] = await usersService.readOne(options.accountability.user, {
21
+ fields: Array.from(permissionContext.$CURRENT_USER),
22
+ });
23
+ }
24
+ if (options.accountability.role && (permissionContext.$CURRENT_ROLE?.size ?? 0) > 0) {
25
+ const rolesService = new RolesService(context);
26
+ contextData['$CURRENT_ROLE'] = await rolesService.readOne(options.accountability.role, {
27
+ fields: Array.from(permissionContext.$CURRENT_ROLE),
28
+ });
29
+ }
30
+ if (options.accountability.roles.length > 0 && (permissionContext.$CURRENT_ROLES?.size ?? 0) > 0) {
31
+ const rolesService = new RolesService(context);
32
+ contextData['$CURRENT_ROLES'] = await rolesService.readMany(options.accountability.roles, {
33
+ fields: Array.from(permissionContext.$CURRENT_ROLES),
34
+ });
35
+ }
36
+ if (options.policies.length > 0 && (permissionContext.$CURRENT_POLICIES?.size ?? 0) > 0) {
37
+ const policiesService = new PoliciesService(context);
38
+ contextData['$CURRENT_POLICIES'] = await policiesService.readMany(options.policies, {
39
+ fields: Array.from(permissionContext.$CURRENT_POLICIES),
40
+ });
41
+ }
42
+ return contextData;
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { AccessRow } from '../lib/fetch-policies.js';
2
+ export declare function filterPoliciesByIp(policies: AccessRow[], ip: string | null | undefined): AccessRow[];
@@ -0,0 +1,15 @@
1
+ import { ipInNetworks } from '../../utils/ip-in-networks.js';
2
+ export function filterPoliciesByIp(policies, ip) {
3
+ return policies.filter(({ policy }) => {
4
+ // Keep policies that don't have an ip address allow list configured
5
+ if (!policy.ip_access || policy.ip_access.length === 0) {
6
+ return true;
7
+ }
8
+ // If the client's IP address is unknown, we can't validate it against the allow list and will
9
+ // have to default to the more secure option of preventing access
10
+ if (!ip) {
11
+ return false;
12
+ }
13
+ return ipInNetworks(ip, policy.ip_access);
14
+ });
15
+ }
@@ -0,0 +1,5 @@
1
+ import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../../types/index.js';
2
+ /**
3
+ * Derive the unaliased field key from the given AST node.
4
+ */
5
+ export declare function getUnaliasedFieldKey(node: NestedCollectionNode | FieldNode | FunctionFieldNode): string;
@@ -0,0 +1,17 @@
1
+ import { parseFilterKey } from '../../utils/parse-filter-key.js';
2
+ /**
3
+ * Derive the unaliased field key from the given AST node.
4
+ */
5
+ export function getUnaliasedFieldKey(node) {
6
+ switch (node.type) {
7
+ case 'o2m':
8
+ return node.relation.meta.one_field;
9
+ case 'a2o':
10
+ case 'm2o':
11
+ return node.relation.field;
12
+ case 'field':
13
+ case 'functionField':
14
+ // The field name might still include a function, so process that here as well
15
+ return parseFilterKey(node.name).fieldName;
16
+ }
17
+ }
@@ -0,0 +1,7 @@
1
+ import type { Accountability, Permission } from '@directus/types';
2
+ export interface ProcessPermissionsOptions {
3
+ permissions: Permission[];
4
+ accountability: Pick<Accountability, 'user' | 'role' | 'roles'>;
5
+ permissionsContext: Record<string, any>;
6
+ }
7
+ export declare function processPermissions({ permissions, accountability, permissionsContext }: ProcessPermissionsOptions): Permission[];
@@ -0,0 +1,9 @@
1
+ import { parseFilter, parsePreset } from '@directus/utils';
2
+ export function processPermissions({ permissions, accountability, permissionsContext }) {
3
+ return permissions.map((permission) => {
4
+ permission.permissions = parseFilter(permission.permissions, accountability, permissionsContext);
5
+ permission.validation = parseFilter(permission.validation, accountability, permissionsContext);
6
+ permission.presets = parsePreset(permission.presets, accountability, permissionsContext);
7
+ return permission;
8
+ });
9
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The `pick` parameter can be used to stabilize cache keys, by only using a subset of the available parameters and
3
+ * ensuring key order.
4
+ *
5
+ * If the `pick` function is provided, we pass the picked result to the handler, in order for TypeScript to ensure that
6
+ * the function only relies on the parameters that are used for generating the cache key.
7
+ *
8
+ * @NOTE only uses the first parameter for memoization
9
+ */
10
+ export declare function withCache<F extends (arg0: Arg0, ...args: any[]) => R, R, Arg0 = Parameters<F>[0]>(namespace: string, handler: F, prepareArg?: (arg0: Arg0) => Arg0): F;
@@ -0,0 +1,25 @@
1
+ import { getSimpleHash } from '@directus/utils';
2
+ import { useCache } from '../cache.js';
3
+ /**
4
+ * The `pick` parameter can be used to stabilize cache keys, by only using a subset of the available parameters and
5
+ * ensuring key order.
6
+ *
7
+ * If the `pick` function is provided, we pass the picked result to the handler, in order for TypeScript to ensure that
8
+ * the function only relies on the parameters that are used for generating the cache key.
9
+ *
10
+ * @NOTE only uses the first parameter for memoization
11
+ */
12
+ export function withCache(namespace, handler, prepareArg) {
13
+ const cache = useCache();
14
+ return (async (arg0, ...args) => {
15
+ arg0 = prepareArg ? prepareArg(arg0) : arg0;
16
+ const key = namespace + '-' + getSimpleHash(JSON.stringify(arg0));
17
+ const cached = await cache.get(key);
18
+ if (cached !== undefined) {
19
+ return cached;
20
+ }
21
+ const res = await handler(arg0, ...args);
22
+ cache.set(key, res);
23
+ return res;
24
+ });
25
+ }
package/dist/server.js CHANGED
@@ -13,6 +13,7 @@ import emitter from './emitter.js';
13
13
  import { useLogger } from './logger/index.js';
14
14
  import { getConfigFromEnv } from './utils/get-config-from-env.js';
15
15
  import { getIPFromReq } from './utils/get-ip-from-req.js';
16
+ import { getAddress } from './utils/get-address.js';
16
17
  import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
17
18
  import { startWebSocketHandlers } from './websocket/handlers/index.js';
18
19
  export let SERVER_ONLINE = true;
@@ -116,10 +117,22 @@ export async function createServer() {
116
117
  export async function startServer() {
117
118
  const server = await createServer();
118
119
  const host = env['HOST'];
119
- const port = parseInt(env['PORT']);
120
+ const path = env['UNIX_SOCKET_PATH'];
121
+ const port = env['PORT'];
122
+ let listenOptions;
123
+ if (path) {
124
+ listenOptions = { path };
125
+ }
126
+ else {
127
+ listenOptions = {
128
+ host,
129
+ port: parseInt(port || '8055'),
130
+ };
131
+ }
120
132
  server
121
- .listen(port, host, () => {
122
- logger.info(`Server started at http://${host}:${port}`);
133
+ .listen(listenOptions, () => {
134
+ const protocol = server instanceof https.Server ? 'https' : 'http';
135
+ logger.info(`Server started at ${listenOptions.port ? `${protocol}://${getAddress(server)}` : getAddress(server)}`);
123
136
  process.send?.('ready');
124
137
  emitter.emitAction('server.start', { server }, {
125
138
  database: getDatabase(),
@@ -129,7 +142,7 @@ export async function startServer() {
129
142
  })
130
143
  .once('error', (err) => {
131
144
  if (err?.code === 'EADDRINUSE') {
132
- logger.error(`Port ${port} is already in use`);
145
+ logger.error(`${listenOptions.port ? `Port ${listenOptions.port}` : getAddress(server)} is already in use`);
133
146
  process.exit(1);
134
147
  }
135
148
  else {
@@ -0,0 +1,10 @@
1
+ import type { Item, PrimaryKey } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
+ import { ItemsService } from './items.js';
4
+ export declare class AccessService extends ItemsService {
5
+ constructor(options: AbstractServiceOptions);
6
+ private clearCaches;
7
+ createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
8
+ updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
9
+ deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
10
+ }
@@ -0,0 +1,43 @@
1
+ import { clearSystemCache } from '../cache.js';
2
+ import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
3
+ import { ItemsService } from './items.js';
4
+ export class AccessService extends ItemsService {
5
+ constructor(options) {
6
+ super('directus_access', options);
7
+ }
8
+ async clearCaches(opts) {
9
+ await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
10
+ if (this.cache && opts?.autoPurgeCache !== false) {
11
+ await this.cache.clear();
12
+ }
13
+ }
14
+ async createOne(data, opts = {}) {
15
+ // Creating a new policy attachments affects the number of admin/app/api users.
16
+ // But it can only add app or admin users, so no need to check the remaining admin users.
17
+ opts.userIntegrityCheckFlags =
18
+ (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
19
+ opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
20
+ const result = await super.createOne(data, opts);
21
+ // A new policy has been attached to a user or a role, clear the caches
22
+ await this.clearCaches();
23
+ return result;
24
+ }
25
+ async updateMany(keys, data, opts = {}) {
26
+ // Updating policy attachments might affect the number of admin/app/api users
27
+ opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
28
+ opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
29
+ const result = await super.updateMany(keys, data, { ...opts, userIntegrityCheckFlags: UserIntegrityCheckFlag.All });
30
+ // Some policy attachments have been updated, clear the caches
31
+ await this.clearCaches();
32
+ return result;
33
+ }
34
+ async deleteMany(keys, opts = {}) {
35
+ // Changes here can affect the number of admin/app/api users
36
+ opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
37
+ opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
38
+ const result = await super.deleteMany(keys, opts);
39
+ // Some policy attachments have been deleted, clear the caches
40
+ await this.clearCaches();
41
+ return result;
42
+ }
43
+ }