ldap-rest 1.2.1 → 2.0.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 (358) hide show
  1. package/bin/sync-james.mjs +13 -2
  2. package/dist/abstract/ldapFlat.js +1 -1
  3. package/dist/abstract/plugin.js +1 -1
  4. package/dist/abstract/twakePlugin.js +1 -1
  5. package/dist/bin/index.js +1 -1
  6. package/dist/config/args.js +1 -1
  7. package/dist/config/options.js +1 -0
  8. package/dist/config/schema.js +1 -1
  9. package/dist/lib/auth/base.js +1 -1
  10. package/dist/lib/authz/base.js +1 -1
  11. package/dist/lib/entitySchema/validator.js +1 -1
  12. package/dist/lib/errors.js +1 -1
  13. package/dist/lib/expressFormatedResponses.js +1 -1
  14. package/dist/lib/health.js +1 -1
  15. package/dist/lib/hookBus.js +1 -0
  16. package/dist/lib/instanceProviders.js +1 -1
  17. package/dist/lib/ldap/callerView.js +1 -0
  18. package/dist/lib/ldap/pool.js +1 -0
  19. package/dist/lib/ldap/schema.js +1 -0
  20. package/dist/lib/ldap/searchCache.js +1 -0
  21. package/dist/lib/ldapActions.js +1 -1
  22. package/dist/lib/ldapSchema.js +1 -1
  23. package/dist/lib/parseConfig.js +1 -1
  24. package/dist/lib/route.js +1 -0
  25. package/dist/lib/schemaFile.js +1 -1
  26. package/dist/lib/services/index.js +1 -0
  27. package/dist/lib/storage/store.js +1 -1
  28. package/dist/lib/utils.js +1 -1
  29. package/dist/lib/version.js +1 -0
  30. package/dist/logger/winston.js +1 -1
  31. package/dist/plugins/auth/auth.options.js +1 -0
  32. package/dist/plugins/auth/authzDynamic.js +1 -1
  33. package/dist/plugins/auth/authzLinid1.js +1 -1
  34. package/dist/plugins/auth/authzPerBranch.js +1 -1
  35. package/dist/plugins/auth/authzPerRoute.js +1 -1
  36. package/dist/plugins/auth/authzScope.js +1 -1
  37. package/dist/plugins/auth/crowdsec.js +1 -1
  38. package/dist/plugins/auth/fake.js +1 -1
  39. package/dist/plugins/auth/hmac.js +1 -1
  40. package/dist/plugins/auth/llng.js +1 -1
  41. package/dist/plugins/auth/openidconnect.js +1 -1
  42. package/dist/plugins/auth/rateLimit.js +1 -1
  43. package/dist/plugins/auth/token.js +1 -1
  44. package/dist/plugins/auth/totp.js +1 -1
  45. package/dist/plugins/auth/trustedProxy.js +1 -1
  46. package/dist/plugins/bcl.js +1 -1
  47. package/dist/plugins/bcl.options.js +1 -0
  48. package/dist/plugins/configApi.js +1 -1
  49. package/dist/plugins/demo/helloworld.js +1 -1
  50. package/dist/plugins/ldap/accountLifecycle.js +1 -1
  51. package/dist/plugins/ldap/bulkImport.js +1 -1
  52. package/dist/plugins/ldap/departmentSync.js +1 -1
  53. package/dist/plugins/ldap/enterpriseRules.js +1 -1
  54. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  55. package/dist/plugins/ldap/flatGeneric.js +1 -1
  56. package/dist/plugins/ldap/groups.js +1 -1
  57. package/dist/plugins/ldap/ldap.options.js +1 -0
  58. package/dist/plugins/ldap/onChange.js +1 -1
  59. package/dist/plugins/ldap/organizations.js +1 -1
  60. package/dist/plugins/ldap/passwordPolicy.js +1 -1
  61. package/dist/plugins/ldap/raw.js +1 -1
  62. package/dist/plugins/ldap/trash.js +1 -1
  63. package/dist/plugins/metrics.js +1 -1
  64. package/dist/plugins/metrics.options.js +1 -0
  65. package/dist/plugins/rabbitmq.js +1 -1
  66. package/dist/plugins/rabbitmq.options.js +1 -0
  67. package/dist/plugins/scim/baseResolver.js +1 -1
  68. package/dist/plugins/scim/bulk.js +1 -1
  69. package/dist/plugins/scim/discovery.js +1 -1
  70. package/dist/plugins/scim/errors.js +1 -1
  71. package/dist/plugins/scim/filter.js +1 -1
  72. package/dist/plugins/scim/groups.js +1 -1
  73. package/dist/plugins/scim/mapping.js +1 -1
  74. package/dist/plugins/scim/patch.js +1 -1
  75. package/dist/plugins/scim/scim.js +1 -1
  76. package/dist/plugins/scim/scim.options.js +1 -0
  77. package/dist/plugins/scim/users.js +1 -1
  78. package/dist/plugins/static.js +1 -1
  79. package/dist/plugins/static.options.js +1 -0
  80. package/dist/plugins/storage/file.js +1 -1
  81. package/dist/plugins/storage/ldap.js +1 -1
  82. package/dist/plugins/storage/postgres.js +1 -1
  83. package/dist/plugins/storage/valkey.js +1 -1
  84. package/dist/plugins/storage.js +1 -1
  85. package/dist/plugins/storage.options.js +1 -0
  86. package/dist/plugins/twake/appAccounts.options.js +1 -0
  87. package/dist/plugins/twake/appAccountsApi.js +1 -1
  88. package/dist/plugins/twake/appAccountsConsistency.js +1 -1
  89. package/dist/plugins/twake/calendar.js +1 -1
  90. package/dist/plugins/twake/calendar.options.js +1 -0
  91. package/dist/plugins/twake/calendarResources.js +1 -1
  92. package/dist/plugins/twake/cloudery.options.js +1 -0
  93. package/dist/plugins/twake/clouderyProvision.js +1 -1
  94. package/dist/plugins/twake/cozy.options.js +1 -0
  95. package/dist/plugins/twake/cozyProvision.js +1 -1
  96. package/dist/plugins/twake/drive.js +1 -1
  97. package/dist/plugins/twake/drive.options.js +1 -0
  98. package/dist/plugins/twake/groups.js +1 -1
  99. package/dist/plugins/twake/instances.js +1 -1
  100. package/dist/plugins/twake/james.js +1 -1
  101. package/dist/plugins/twake/james.options.js +1 -0
  102. package/dist/plugins/twake/lifecycleAttributes.js +1 -1
  103. package/dist/plugins/twake/lifecycleEvents.js +1 -1
  104. package/dist/plugins/twake/migrationProxy.js +1 -1
  105. package/dist/plugins/twake/migrationProxy.options.js +1 -0
  106. package/dist/plugins/twake/spaces.js +1 -1
  107. package/dist/plugins/twake/tombstone.js +1 -1
  108. package/dist/plugins/twake/twakeGroups.options.js +1 -0
  109. package/dist/plugins/twake/twakeInstances.options.js +1 -0
  110. package/dist/plugins/twake/twakeLifecycle.options.js +1 -0
  111. package/dist/plugins/twake/twakeSpaces.options.js +1 -0
  112. package/dist/plugins/twake/twakeTombstone.options.js +1 -0
  113. package/dist/plugins/weblogs.js +1 -1
  114. package/dist/src/abstract/ldapFlat.d.ts +41 -10
  115. package/dist/src/abstract/plugin.d.ts +31 -5
  116. package/dist/src/abstract/twakePlugin.d.ts +14 -4
  117. package/dist/src/bin/index.d.ts +35 -4
  118. package/dist/src/config/args.d.ts +2 -4
  119. package/dist/src/config/options.d.ts +104 -0
  120. package/dist/src/config/schema.d.ts +50 -1
  121. package/dist/src/hooks.d.ts +60 -22
  122. package/dist/src/lib/authz/base.d.ts +4 -51
  123. package/dist/src/lib/authz/composition.d.ts +2 -2
  124. package/dist/src/lib/entitySchema/validator.d.ts +63 -6
  125. package/dist/src/lib/errors.d.ts +57 -1
  126. package/dist/src/lib/expressFormatedResponses.d.ts +49 -7
  127. package/dist/src/lib/hookBus.d.ts +226 -0
  128. package/dist/src/lib/instanceProviders.d.ts +0 -8
  129. package/dist/src/lib/ldap/callerView.d.ts +45 -0
  130. package/dist/src/lib/ldap/pool.d.ts +108 -0
  131. package/dist/src/lib/ldap/schema.d.ts +157 -0
  132. package/dist/src/lib/ldap/searchCache.d.ts +112 -0
  133. package/dist/src/lib/ldapActions.d.ts +26 -256
  134. package/dist/src/lib/parseConfig.d.ts +31 -2
  135. package/dist/src/lib/route.d.ts +99 -0
  136. package/dist/src/lib/services/index.d.ts +56 -0
  137. package/dist/src/lib/utils.d.ts +100 -36
  138. package/dist/src/lib/version.d.ts +12 -0
  139. package/dist/src/plugins/auth/auth.options.d.ts +88 -0
  140. package/dist/src/plugins/auth/authzDynamic.d.ts +10 -1
  141. package/dist/src/plugins/auth/authzLinid1.d.ts +6 -0
  142. package/dist/src/plugins/auth/authzPerBranch.d.ts +9 -0
  143. package/dist/src/plugins/auth/authzPerRoute.d.ts +3 -0
  144. package/dist/src/plugins/auth/authzScope.d.ts +23 -14
  145. package/dist/src/plugins/auth/crowdsec.d.ts +5 -0
  146. package/dist/src/plugins/auth/fake.d.ts +3 -0
  147. package/dist/src/plugins/auth/hmac.d.ts +4 -0
  148. package/dist/src/plugins/auth/llng.d.ts +4 -0
  149. package/dist/src/plugins/auth/openidconnect.d.ts +7 -0
  150. package/dist/src/plugins/auth/rateLimit.d.ts +4 -0
  151. package/dist/src/plugins/auth/token.d.ts +3 -0
  152. package/dist/src/plugins/auth/totp.d.ts +5 -0
  153. package/dist/src/plugins/auth/trustedProxy.d.ts +4 -0
  154. package/dist/src/plugins/bcl.d.ts +3 -0
  155. package/dist/src/plugins/bcl.options.d.ts +8 -0
  156. package/dist/src/plugins/configApi.d.ts +24 -5
  157. package/dist/src/plugins/demo/helloworld.d.ts +8 -19
  158. package/dist/src/plugins/ldap/accountLifecycle.d.ts +17 -0
  159. package/dist/src/plugins/ldap/bulkImport.d.ts +6 -76
  160. package/dist/src/plugins/ldap/departmentSync.d.ts +1 -0
  161. package/dist/src/plugins/ldap/enterpriseRules.d.ts +8 -15
  162. package/dist/src/plugins/ldap/externalUsersInGroups.d.ts +4 -0
  163. package/dist/src/plugins/ldap/flatGeneric.d.ts +4 -0
  164. package/dist/src/plugins/ldap/groups.d.ts +27 -11
  165. package/dist/src/plugins/ldap/ldap.options.d.ts +75 -0
  166. package/dist/src/plugins/ldap/onChange.d.ts +10 -2
  167. package/dist/src/plugins/ldap/organizations.d.ts +16 -0
  168. package/dist/src/plugins/ldap/passwordPolicy.d.ts +35 -3
  169. package/dist/src/plugins/ldap/raw.d.ts +50 -19
  170. package/dist/src/plugins/ldap/trash.d.ts +6 -0
  171. package/dist/src/plugins/metrics.d.ts +3 -0
  172. package/dist/src/plugins/metrics.options.d.ts +8 -0
  173. package/dist/src/plugins/rabbitmq.d.ts +3 -0
  174. package/dist/src/plugins/rabbitmq.options.d.ts +8 -0
  175. package/dist/src/plugins/scim/baseResolver.d.ts +2 -1
  176. package/dist/src/plugins/scim/bulk.d.ts +6 -7
  177. package/dist/src/plugins/scim/discovery.d.ts +3 -0
  178. package/dist/src/plugins/scim/errors.d.ts +2 -1
  179. package/dist/src/plugins/scim/groups.d.ts +6 -4
  180. package/dist/src/plugins/scim/mapping.d.ts +16 -3
  181. package/dist/src/plugins/scim/scim.d.ts +37 -3
  182. package/dist/src/plugins/scim/scim.options.d.ts +35 -0
  183. package/dist/src/plugins/scim/types.d.ts +2 -0
  184. package/dist/src/plugins/scim/users.d.ts +6 -7
  185. package/dist/src/plugins/static.d.ts +9 -0
  186. package/dist/src/plugins/static.options.d.ts +13 -0
  187. package/dist/src/plugins/storage.d.ts +12 -1
  188. package/dist/src/plugins/storage.options.d.ts +16 -0
  189. package/dist/src/plugins/twake/appAccounts.options.d.ts +13 -0
  190. package/dist/src/plugins/twake/appAccountsApi.d.ts +18 -0
  191. package/dist/src/plugins/twake/appAccountsConsistency.d.ts +8 -0
  192. package/dist/src/plugins/twake/calendar.d.ts +25 -2
  193. package/dist/src/plugins/twake/calendar.options.d.ts +19 -0
  194. package/dist/src/plugins/twake/cloudery.options.d.ts +26 -0
  195. package/dist/src/plugins/twake/clouderyProvision.d.ts +18 -0
  196. package/dist/src/plugins/twake/cozy.options.d.ts +23 -0
  197. package/dist/src/plugins/twake/cozyProvision.d.ts +14 -0
  198. package/dist/src/plugins/twake/drive.d.ts +7 -0
  199. package/dist/src/plugins/twake/drive.options.d.ts +15 -0
  200. package/dist/src/plugins/twake/groups.d.ts +29 -1
  201. package/dist/src/plugins/twake/instances.d.ts +74 -10
  202. package/dist/src/plugins/twake/james.d.ts +17 -0
  203. package/dist/src/plugins/twake/james.options.d.ts +21 -0
  204. package/dist/src/plugins/twake/lifecycleAttributes.d.ts +13 -2
  205. package/dist/src/plugins/twake/lifecycleEvents.d.ts +12 -0
  206. package/dist/src/plugins/twake/migrationProxy.d.ts +11 -11
  207. package/dist/src/plugins/twake/migrationProxy.options.d.ts +16 -0
  208. package/dist/src/plugins/twake/spaces.d.ts +30 -0
  209. package/dist/src/plugins/twake/tombstone.d.ts +25 -16
  210. package/dist/src/plugins/twake/twakeGroups.options.d.ts +20 -0
  211. package/dist/src/plugins/twake/twakeInstances.options.d.ts +43 -0
  212. package/dist/src/plugins/twake/twakeLifecycle.options.d.ts +21 -0
  213. package/dist/src/plugins/twake/twakeSpaces.options.d.ts +19 -0
  214. package/dist/src/plugins/twake/twakeTombstone.options.d.ts +17 -0
  215. package/docs/README.md +17 -2
  216. package/docs/client-development/api/rest-api.md +13 -3
  217. package/docs/client-development/schemas/README.md +96 -32
  218. package/docs/development/contracts.md +109 -0
  219. package/docs/plugin-development/README.md +53 -95
  220. package/docs/plugin-development/dependencies.md +24 -8
  221. package/docs/plugin-development/hooks.md +143 -23
  222. package/docs/plugin-development/openapi.md +14 -1
  223. package/docs/plugin-development/options.md +136 -0
  224. package/docs/plugin-development/routes.md +152 -0
  225. package/docs/plugin-development/services.md +95 -0
  226. package/docs/plugin-development/testing.md +3 -3
  227. package/docs/usage/configuration.md +18 -11
  228. package/docs/usage/operations.md +28 -4
  229. package/docs/usage/plugins/integrations/instances.md +11 -3
  230. package/docs/usage/plugins/integrations/lifecycle-events.md +4 -1
  231. package/docs/usage/plugins/integrations/scim.md +20 -18
  232. package/docs/usage/plugins/integrations/tombstone.md +10 -6
  233. package/docs/usage/plugins/ldap/enterprise-rules.md +10 -1
  234. package/docs/usage/plugins/ldap/flat-generic.md +11 -0
  235. package/docs/usage/plugins/ldap/groups.md +4 -1
  236. package/docs/usage/plugins/ldap/password-policy.md +7 -0
  237. package/docs/usage/plugins/ldap/raw.md +17 -1
  238. package/docs/usage/upgrading.md +253 -0
  239. package/package.json +372 -286
  240. package/static/browser/ldap-browser/LdapBrowser.js +8 -6
  241. package/static/browser/ldap-browser/LdapBrowser.js.map +1 -1
  242. package/static/browser/ldap-browser/api/RawApiClient.js +2 -0
  243. package/static/browser/ldap-browser/api/RawApiClient.js.map +1 -1
  244. package/static/browser/ldap-browser/components/EntryTree.js +9 -5
  245. package/static/browser/ldap-browser/components/EntryTree.js.map +1 -1
  246. package/static/browser/ldap-browser/components/EntryView.js +2 -0
  247. package/static/browser/ldap-browser/components/EntryView.js.map +1 -1
  248. package/static/browser/ldap-browser/schema.js +3 -2
  249. package/static/browser/ldap-browser/schema.js.map +1 -1
  250. package/static/browser/ldap-browser.esm.js +27 -18
  251. package/static/browser/ldap-browser.esm.js.map +1 -1
  252. package/static/browser/ldap-browser.js +27 -18
  253. package/static/browser/ldap-browser.js.map +1 -1
  254. package/static/browser/ldap-browser.min.js +1 -1
  255. package/static/browser/ldap-browser.min.js.map +1 -1
  256. package/static/browser/ldap-group-editor/LdapGroupEditor.js +8 -6
  257. package/static/browser/ldap-group-editor/LdapGroupEditor.js.map +1 -1
  258. package/static/browser/ldap-group-editor/api/GroupApiClient.js +1 -0
  259. package/static/browser/ldap-group-editor/api/GroupApiClient.js.map +1 -1
  260. package/static/browser/ldap-group-editor/components/CreateGroupModal.js +5 -1
  261. package/static/browser/ldap-group-editor/components/CreateGroupModal.js.map +1 -1
  262. package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js +6 -3
  263. package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js.map +1 -1
  264. package/static/browser/ldap-group-editor/components/GroupTree.js +6 -3
  265. package/static/browser/ldap-group-editor/components/GroupTree.js.map +1 -1
  266. package/static/browser/ldap-group-editor/components/MoveGroupModal.js +6 -3
  267. package/static/browser/ldap-group-editor/components/MoveGroupModal.js.map +1 -1
  268. package/static/browser/ldap-group-editor.esm.js +32 -16
  269. package/static/browser/ldap-group-editor.esm.js.map +1 -1
  270. package/static/browser/ldap-group-editor.js +32 -16
  271. package/static/browser/ldap-group-editor.js.map +1 -1
  272. package/static/browser/ldap-group-editor.min.js +1 -1
  273. package/static/browser/ldap-group-editor.min.js.map +1 -1
  274. package/static/browser/ldap-resource-editor/api/ResourceApiClient.js +3 -0
  275. package/static/browser/ldap-resource-editor/api/ResourceApiClient.js.map +1 -1
  276. package/static/browser/ldap-resource-editor/cache/CacheManager.js +3 -1
  277. package/static/browser/ldap-resource-editor/cache/CacheManager.js.map +1 -1
  278. package/static/browser/ldap-tree-viewer/LdapTreeViewer.js +6 -1
  279. package/static/browser/ldap-tree-viewer/LdapTreeViewer.js.map +1 -1
  280. package/static/browser/ldap-tree-viewer/api/LdapApiClient.js +2 -0
  281. package/static/browser/ldap-tree-viewer/api/LdapApiClient.js.map +1 -1
  282. package/static/browser/ldap-tree-viewer/components/TreeNode.js +4 -0
  283. package/static/browser/ldap-tree-viewer/components/TreeNode.js.map +1 -1
  284. package/static/browser/ldap-tree-viewer/components/TreeRoot.js +3 -0
  285. package/static/browser/ldap-tree-viewer/components/TreeRoot.js.map +1 -1
  286. package/static/browser/ldap-tree-viewer/store/Store.js +3 -1
  287. package/static/browser/ldap-tree-viewer/store/Store.js.map +1 -1
  288. package/static/browser/ldap-tree-viewer.esm.js +18 -2
  289. package/static/browser/ldap-tree-viewer.esm.js.map +1 -1
  290. package/static/browser/ldap-tree-viewer.js +18 -2
  291. package/static/browser/ldap-tree-viewer.js.map +1 -1
  292. package/static/browser/ldap-tree-viewer.min.js +1 -1
  293. package/static/browser/ldap-tree-viewer.min.js.map +1 -1
  294. package/static/browser/ldap-unit-editor/LdapUnitEditor.js +7 -5
  295. package/static/browser/ldap-unit-editor/LdapUnitEditor.js.map +1 -1
  296. package/static/browser/ldap-unit-editor/api/UnitApiClient.js +1 -0
  297. package/static/browser/ldap-unit-editor/api/UnitApiClient.js.map +1 -1
  298. package/static/browser/ldap-unit-editor/components/MoveUnitModal.js +7 -4
  299. package/static/browser/ldap-unit-editor/components/MoveUnitModal.js.map +1 -1
  300. package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js +7 -4
  301. package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js.map +1 -1
  302. package/static/browser/ldap-unit-editor/components/UnitTree.js +6 -3
  303. package/static/browser/ldap-unit-editor/components/UnitTree.js.map +1 -1
  304. package/static/browser/ldap-unit-editor.esm.js +28 -16
  305. package/static/browser/ldap-unit-editor.esm.js.map +1 -1
  306. package/static/browser/ldap-unit-editor.js +28 -16
  307. package/static/browser/ldap-unit-editor.js.map +1 -1
  308. package/static/browser/ldap-unit-editor.min.js +1 -1
  309. package/static/browser/ldap-unit-editor.min.js.map +1 -1
  310. package/static/browser/ldap-user-editor/LdapUserEditor.js +9 -7
  311. package/static/browser/ldap-user-editor/LdapUserEditor.js.map +1 -1
  312. package/static/browser/ldap-user-editor/api/UserApiClient.js +2 -0
  313. package/static/browser/ldap-user-editor/api/UserApiClient.js.map +1 -1
  314. package/static/browser/ldap-user-editor/cache/CacheManager.js +3 -1
  315. package/static/browser/ldap-user-editor/cache/CacheManager.js.map +1 -1
  316. package/static/browser/ldap-user-editor/components/MoveUserModal.js +7 -4
  317. package/static/browser/ldap-user-editor/components/MoveUserModal.js.map +1 -1
  318. package/static/browser/ldap-user-editor/components/PointerField.js +6 -1
  319. package/static/browser/ldap-user-editor/components/PointerField.js.map +1 -1
  320. package/static/browser/ldap-user-editor/components/UserEditor.js +10 -5
  321. package/static/browser/ldap-user-editor/components/UserEditor.js.map +1 -1
  322. package/static/browser/ldap-user-editor/components/UserList.js +8 -4
  323. package/static/browser/ldap-user-editor/components/UserList.js.map +1 -1
  324. package/static/browser/ldap-user-editor/components/UserTree.js +7 -3
  325. package/static/browser/ldap-user-editor/components/UserTree.js.map +1 -1
  326. package/static/browser/ldap-user-editor.esm.js +55 -30
  327. package/static/browser/ldap-user-editor.esm.js.map +1 -1
  328. package/static/browser/ldap-user-editor.js +55 -30
  329. package/static/browser/ldap-user-editor.js.map +1 -1
  330. package/static/browser/ldap-user-editor.min.js +1 -1
  331. package/static/browser/ldap-user-editor.min.js.map +1 -1
  332. package/static/browser/shared/components/DisposableComponent.js +3 -5
  333. package/static/browser/shared/components/DisposableComponent.js.map +1 -1
  334. package/static/browser/shared/components/Modal.js +2 -1
  335. package/static/browser/shared/components/Modal.js.map +1 -1
  336. package/static/browser/shared/components/StatusMessage.js +3 -2
  337. package/static/browser/shared/components/StatusMessage.js.map +1 -1
  338. package/static/browser/shared/utils/form.js.map +1 -1
  339. package/static/browser/shared/utils/hmac.js +1 -0
  340. package/static/browser/shared/utils/hmac.js.map +1 -1
  341. package/static/browser/shared/utils/totp.js +1 -0
  342. package/static/browser/shared/utils/totp.js.map +1 -1
  343. package/static/browser/shared.esm.js +9 -8
  344. package/static/browser/shared.esm.js.map +1 -1
  345. package/static/browser/shared.js +9 -8
  346. package/static/browser/shared.js.map +1 -1
  347. package/static/browser/shared.min.js +1 -1
  348. package/static/browser/shared.min.js.map +1 -1
  349. package/static/schemas/ad/groups.json +1 -1
  350. package/static/schemas/ad/users.json +3 -3
  351. package/static/schemas/example/organizations.json +2 -2
  352. package/static/schemas/example/users.json +2 -2
  353. package/static/schemas/obm/users.json +2 -2
  354. package/static/schemas/standard/groups.json +1 -1
  355. package/static/schemas/standard/users.json +2 -2
  356. package/static/schemas/twake/groups.json +4 -4
  357. package/static/schemas/twake/teamMailboxes.json +3 -3
  358. package/static/schemas/twake/users.json +7 -7
@@ -1 +1 @@
1
- import e from"../../abstract/plugin.js";import{BadRequestError as t,NotFoundError as s,ConflictError as r}from"../../lib/errors.js";import{jsonBody as a}from"../../lib/expressFormatedResponses.js";import{changeContext as i}from"../../lib/changeContext.js";import{extractLdapCode as o}from"../../lib/ldapCodes.js";import{escapeLdapFilter as n,asyncHandler as h,isDnInBranch as l,launchHooks as d}from"../../lib/utils.js";import{isTombstone as c,lifecycleAttributes as f,first as m,valueOf as p,parseDeletedAt as u,formatDeletedAt as w}from"./lifecycleAttributes.js";const g=/^[A-Za-z0-9_.-]{1,64}$/;class TwakeTombstone extends e{constructor(e){super(e),this.name="twakeTombstone",this.roles=["consistency","api"],this.reasonFor=new Map,this.erasing=new Set,this.hooks={ldapdeleterequest:async([e,t])=>{const s=[];for(const r of Array.isArray(e)?e:[e]){if(this.keeps(r)){const e=await this.read(r);if(e){await this.writeTombstone(r,e,this.reasonOf(r,t),t);continue}}s.push(r)}return[s,t]},ldapaddrequest:async([e,t,s])=>{if(this.isScim(s)&&this.matches(e)){const t=await this.read(e);t&&c(t,this.attrs)&&await this.replace(e,t,s)}return[e,t,s]},ldapsearchrequest:([e,t,s])=>{if(!this.isScim(s))return[e,t,s];const r=t.filter||"(objectClass=*)",a=`(!(${this.attrs.deleted}=${n(this.attrs.deletedValue)}))`;return[e,{...t,filter:`(&${String(r).startsWith("(")?String(r):`(${String(r)})`}${a})`},s]}};const t=this.config;if(this.attrs=f(t),!this.attrs.deleted)throw new Error(`${this.name}: --twake-lifecycle-deleted-attribute is required`);this.patterns=(t.twake_tombstone_dn||[]).map(e=>new RegExp(e,"i")),this.reasons=t.twake_tombstone_reasons||[],this.defaultReason=this.checkReason(t.twake_tombstone_default_reason||"deleted"),this.reasonHeader=(t.twake_tombstone_reason_header||"x-deletion-reason").toLowerCase(),this.clearAttributes=t.twake_tombstone_clear_attributes||[],this.eraseMinAge=t.twake_tombstone_erase_min_age??2592e3,this.groupBases=t.twake_tombstone_group_bases?.length?t.twake_tombstone_group_bases:[t.ldap_group_base||t.ldap_base||""],this.scimPrefix=t.scim_prefix||"/scim/v2",0===this.patterns.length&&this.logger.warn(`${this.name}: --twake-tombstone-dn is empty, every delete stays a delete`)}afterLoad(){for(const e of["ldapdeleterequest","ldapaddrequest"]){const t=this.server.hooks[e],s=this.hooks[e],r=t&&s?t.indexOf(s):-1;r<0||(t.splice(r,1),t.push(s))}}api(e){e.post(`${this.config.api_prefix}/v1/twake/tombstones/erase`,h(async(e,s)=>{const r=a(e,s,"dn");if(r){if("string"!=typeof r.dn)throw new t("Field dn must be a string");await this.erase(r.dn,{force:!0===r.force,req:e}),s.json({success:!0})}}))}async tombstone(e,s,r){if(!this.matches(e))throw new t(`${e} is not an entry that becomes a tombstone`);this.reasonFor.set(e,this.checkReason(s));try{await this.server.ldap.delete(e,r)}finally{this.reasonFor.delete(e)}}async erase(e,{force:t=!1,req:a}={}){await this.server.ldap.authorize("ldapdeleterequest",[e,a]);const i=await this.read(e);if(!i||!c(i,this.attrs))throw new s(`No tombstone at ${e}`);if(!t){const t=m(p(i,this.attrs.deletedAt)),s=t&&u(t,this.attrs.deletedAtFormat);if(!s)throw new r(`${e} holds no readable deletion date (--twake-lifecycle-deleted-at-attribute); force it to erase now`);if(Date.now()-s.getTime()<1e3*this.eraseMinAge)throw new r(`${e} was deleted less than ${this.eraseMinAge} seconds ago; force it to erase now`)}this.erasing.add(e);try{await this.server.ldap.delete(e,a)}finally{this.erasing.delete(e)}await this.leaveGroups(e),this.logger.info({plugin:this.name,event:"erase",dn:e,force:t})}keeps(e){return!this.erasing.has(e)&&this.matches(e)}assertComposition(){if(!this.server.loadedPlugins.trash)return;const e=String(this.config.trash_watched_bases||"").split(";").map(e=>e.trim()).filter(Boolean),t=this.patterns.filter(t=>{const s=t.source.replace(/\$$/,"");return 0===e.length||e.some(e=>l(s,e)||l(e,s))});if(t.length)throw new Error(`${this.name}: core/ldap/trash watches the branch of ${t.map(e=>e.source).join(", ")}, and would move those entries instead of leaving a tombstone. Leave that branch out of --trash-watched-bases, or load one of the two plugins only`)}async replace(e,t,s){const r=await this.groupsOf(e),a=this.config.group_dummy_user,i=new Set(a?await this.groupsOf(a):[]);await this.erase(e,{force:!0,req:s});const o=s.res;if(!o)return;const n=o.end.bind(o);o.end=(...s)=>{o.end=n;const a=n(...s);return this.restore(e,t,r,i).catch(t=>this.logger.error({plugin:this.name,event:"restore",dn:e,error:String(t)})),a}}async restore(e,t,s,r){if(await this.read(e))return;const a=Object.fromEntries(Object.entries(t).filter(([e,t])=>"dn"!==e&&!(Array.isArray(t)&&0===t.length)));await this.server.ldap.system.add(e,a);const i=this.config.group_dummy_user;for(const t of s)await this.server.ldap.system.modify(t,{add:{member:e}}),i&&!r.has(t)&&await this.server.ldap.system.modify(t,{delete:{member:i}}).catch(e=>{if(16!==o(e))throw e});this.logger.warn({plugin:this.name,event:"restore",dn:e})}matches(e){return this.patterns.some(t=>t.test(e))}isScim(e){const t=e?.originalUrl??e?.url;return"string"==typeof t&&t.startsWith(this.scimPrefix)}checkReason(e){if(!(this.reasons.length?this.reasons.includes(e):g.test(e)))throw new t(`Unknown deletion reason "${e}"${this.reasons.length?`; known: ${this.reasons.join(", ")}`:""}`);return e}reasonOf(e,t){const s=this.reasonFor.get(e);if(s)return s;const r=t?.headers?.[this.reasonHeader],a=Array.isArray(r)?r[0]:r;return a?this.checkReason(a):this.defaultReason}async read(e){try{return(await this.server.ldap.system.search({paged:!1,scope:"base",attributes:["*",this.attrs.lock]},e)).searchEntries[0]}catch(e){if(32===o(e))return;throw e}}async writeTombstone(e,t,s,r){const{attrs:a}=this;if(c(t,a))return d(this.server.hooks.twakedeletionreplay,e,t,i(r)),void this.logger.info({plugin:this.name,event:"replay",dn:e});const o={[a.deleted]:a.deletedValue,[a.lock]:a.lockValue};a.deletedAt&&(o[a.deletedAt]=w(new Date,a.deletedAtFormat)),a.reason&&(o[a.reason]=s);const n=this.clearAttributes.filter(e=>void 0!==p(t,e));await this.server.ldap.modify(e,n.length?{replace:o,delete:n}:{replace:o},void 0,{context:i(r)}),this.logger.info({plugin:this.name,event:"tombstone",dn:e,reason:s})}async groupsOf(e){const t=[];for(const s of this.groupBases.filter(Boolean))try{const r=await this.server.ldap.system.search({paged:!1,scope:"sub",filter:`(member=${n(e)})`,attributes:["dn"]},s);t.push(...r.searchEntries.map(e=>e.dn))}catch(e){if(32!==o(e))throw e}return t}async leaveGroups(e){for(const t of await this.groupsOf(e))await this.leaveGroup(t,e)}async leaveGroup(e,t){const s=this.config.group_dummy_user;let r=!1;for(let a=0;;a++)try{return void await this.server.ldap.system.modify(e,r?{add:{member:s},delete:{member:t}}:{delete:{member:t}})}catch(e){const t=o(e);if(16===t)return;if(a>=2)throw e;if(65===t&&s)r=!0;else{if(20!==t)throw e;r=!1}}}}export{TwakeTombstone as default};
1
+ import{Type as e}from"@sinclair/typebox";import t from"../../abstract/plugin.js";import{BadRequestError as s,NotFoundError as r,ConflictError as a}from"../../lib/errors.js";import{changeContext as o}from"../../lib/changeContext.js";import{extractLdapCode as i}from"../../lib/ldapCodes.js";import{escapeLdapFilter as n,isDnInBranch as h,normalizeDn as c}from"../../lib/utils.js";import{scimOptionsOf as l}from"../scim/scim.options.js";import{lifecycleAttributes as d,isTombstone as f,first as m,valueOf as p,parseDeletedAt as u,formatDeletedAt as w}from"./lifecycleAttributes.js";import{twakeTombstoneOptions as g,twakeTombstoneOptionsOf as b}from"./twakeTombstone.options.js";import{defineRoute as y}from"../../lib/route.js";const _=/^[A-Za-z0-9_.-]{1,64}$/,k=e.Object({dn:e.String(),force:e.Optional(e.Boolean())}),v=e.Object({success:e.Boolean()});class TwakeTombstone extends t{static optionDefinitions=g;name="twakeTombstone";roles=["consistency","api"];attrs;patterns;defaultReason;reasonHeader;reasons;clearAttributes;eraseMinAge;groupBases;scimPrefix;reasonFor=new Map;erasing=new Set;constructor(e){super(e);const t=b(e.config);if(this.attrs=d(e.config),!this.attrs.deleted)throw new Error(`${this.name}: --twake-lifecycle-deleted-attribute is required`);this.patterns=t.twake_tombstone_dn.map(e=>new RegExp(e,"i")),this.reasons=t.twake_tombstone_reasons,this.defaultReason=this.checkReason(t.twake_tombstone_default_reason),this.reasonHeader=t.twake_tombstone_reason_header.toLowerCase(),this.clearAttributes=t.twake_tombstone_clear_attributes,this.eraseMinAge=t.twake_tombstone_erase_min_age,this.groupBases=t.twake_tombstone_group_bases.length?t.twake_tombstone_group_bases:[e.config.ldap_group_base||e.config.ldap_base||""],this.scimPrefix=l(e.config).scim_prefix,0===this.patterns.length&&this.logger.warn(`${this.name}: --twake-tombstone-dn is empty, every delete stays a delete`)}afterLoad(){for(const e of["ldapdeleterequest","ldapaddrequest"]){const t=this.hooks?.[e];t&&this.hookBus.toLast(e,t)}}hooks={ldapdeleterequest:async([e,t])=>{const s=[];for(const r of Array.isArray(e)?e:[e]){if(!this.erasing.has(r)){const e=await this.read(r);if(e&&(this.matches(r)||this.matches(e.dn))){await this.writeTombstone(r,e,this.reasonOf(r,t),t);continue}}s.push(r)}return[s,t]},ldapaddrequest:async([e,t,s])=>{if(this.isScim(s)&&this.matches(e)){const t=await this.read(e);t&&f(t,this.attrs)&&await this.replace(e,t,s)}return[e,t,s]},ldapsearchrequest:([e,t,s])=>{if(!this.isScim(s))return[e,t,s];const r=t.filter||"(objectClass=*)",a=`(!(${this.attrs.deleted}=${n(this.attrs.deletedValue)}))`;return[e,{...t,filter:`(&${String(r).startsWith("(")?String(r):`(${String(r)})`}${a})`},s]}};api(e){y(e,{method:"post",path:`${this.config.api_prefix}/v1/twake/tombstones/erase`,body:k,responses:{200:v},handler:async({req:e,res:t,body:s})=>{await this.erase(s.dn,{force:!0===s.force,req:e}),t.json({success:!0})}})}async tombstone(e,t,r){if(!this.matches(e))throw new s(`${e} is not an entry that becomes a tombstone`);this.reasonFor.set(e,this.checkReason(t));try{await this.server.ldap.delete(e,r)}finally{this.reasonFor.delete(e)}}async erase(e,{force:t=!1,req:s}={}){await this.server.ldap.authorize("ldapdeleterequest",[e,s]);const o=await this.read(e);if(!o||!f(o,this.attrs))throw new r(`No tombstone at ${e}`);if(!t){const t=m(p(o,this.attrs.deletedAt)),s=t&&u(t);if(!s)throw new a(`${e} holds no readable deletion date (--twake-lifecycle-deleted-at-attribute); force it to erase now`);if(Date.now()-s.getTime()<1e3*this.eraseMinAge)throw new a(`${e} was deleted less than ${this.eraseMinAge} seconds ago; force it to erase now`)}this.erasing.add(e);try{await this.server.ldap.delete(e,s)}finally{this.erasing.delete(e)}await this.leaveGroups(e),this.logger.info({plugin:this.name,event:"erase",dn:e,force:t})}keeps(e){return!this.erasing.has(e)&&this.matches(e)}assertComposition(){if(!this.server.loadedPlugins.trash)return;const e=String(this.config.trash_watched_bases||"").split(";").map(e=>e.trim()).filter(Boolean),t=this.patterns.filter(t=>{const s=t.source.replace(/\$$/,"");return 0===e.length||e.some(e=>h(s,e)||h(e,s))});if(t.length)throw new Error(`${this.name}: core/ldap/trash watches the branch of ${t.map(e=>e.source).join(", ")}, and would move those entries instead of leaving a tombstone. Leave that branch out of --trash-watched-bases, or load one of the two plugins only`)}async replace(e,t,s){const r=await this.groupsOf(e),a=this.config.group_dummy_user,o=new Set(a?await this.groupsOf(a):[]);await this.erase(e,{force:!0,req:s});const i=s.res;if(!i)return;const n=i.end.bind(i);i.end=(...s)=>{i.end=n;const a=n(...s);return this.restore(e,t,r,o).catch(t=>this.logger.error({plugin:this.name,event:"restore",dn:e,error:String(t)})),a}}async restore(e,t,s,r){if(await this.read(e))return;const a=Object.fromEntries(Object.entries(t).filter(([e,t])=>"dn"!==e&&!(Array.isArray(t)&&0===t.length)));await this.server.ldap.system.add(e,a);const o=this.config.group_dummy_user;for(const t of s)await this.server.ldap.system.modify(t,{add:{member:e}}),o&&!r.has(t)&&await this.server.ldap.system.modify(t,{delete:{member:o}}).catch(e=>{if(16!==i(e))throw e});this.logger.warn({plugin:this.name,event:"restore",dn:e})}matches(e){if(this.patterns.some(t=>t.test(e)))return!0;let t;try{t=c(e)}catch{return!1}return this.patterns.some(e=>e.test(t))}isScim(e){const t=e?.originalUrl??e?.url;return"string"==typeof t&&t.startsWith(this.scimPrefix)}checkReason(e){if(!(this.reasons.length?this.reasons.includes(e):_.test(e)))throw new s(`Unknown deletion reason "${e}"${this.reasons.length?`; known: ${this.reasons.join(", ")}`:""}`);return e}reasonOf(e,t){const s=this.reasonFor.get(e);if(s)return s;const r=t?.headers?.[this.reasonHeader],a=Array.isArray(r)?r[0]:r;return a?this.checkReason(a):this.defaultReason}async read(e){try{return(await this.server.ldap.system.search({paged:!1,scope:"base",attributes:["*",this.attrs.lock]},e)).searchEntries[0]}catch(t){const r=i(t);if(32===r)return;if(34===r)throw new s(`Invalid DN: ${e}`);throw t}}async writeTombstone(e,t,s,r){const{attrs:a}=this;if(f(t,a))return this.hookBus.notify("twakedeletionreplay",e,t,o(r)),void this.logger.info({plugin:this.name,event:"replay",dn:e});const i={[a.deleted]:a.deletedValue,[a.lock]:a.lockValue};a.deletedAt&&(i[a.deletedAt]=w(new Date,a.deletedAtFormat)),a.reason&&(i[a.reason]=s);const n=this.clearAttributes.filter(e=>void 0!==p(t,e));await this.server.ldap.modify(e,n.length?{replace:i,delete:n}:{replace:i},void 0,{context:o(r)}),this.logger.info({plugin:this.name,event:"tombstone",dn:e,reason:s})}async groupsOf(e){const t=[];for(const s of this.groupBases.filter(Boolean))try{const r=await this.server.ldap.system.search({paged:!1,scope:"sub",filter:`(member=${n(e)})`,attributes:["dn"]},s);t.push(...r.searchEntries.map(e=>e.dn))}catch(e){if(32!==i(e))throw e}return t}async leaveGroups(e){for(const t of await this.groupsOf(e))await this.leaveGroup(t,e)}async leaveGroup(e,t){const s=this.config.group_dummy_user;let r=!1;for(let a=0;;a++)try{return void await this.server.ldap.system.modify(e,r?{add:{member:s},delete:{member:t}}:{delete:{member:t}})}catch(e){const t=i(e);if(16===t)return;if(a>=2)throw e;if(65===t&&s)r=!0;else{if(20!==t)throw e;r=!1}}}}export{k as EraseTombstoneBody,v as EraseTombstoneResponse,TwakeTombstone as default};
@@ -0,0 +1 @@
1
+ import{defineOptions as _,opt as t,readOptions as e}from"../../config/options.js";const a=_({twake_group_base:t.string({env:"DM_TWAKE_GROUP_BASE",default:""}),twake_group_user_base:t.string({env:"DM_TWAKE_GROUP_USER_BASE",default:""}),twake_group_organization_dn:t.string({env:"DM_TWAKE_GROUP_ORGANIZATION_DN",default:""}),twake_group_organization_status_attribute:t.string({env:"DM_TWAKE_GROUP_ORGANIZATION_STATUS_ATTRIBUTE",default:"twakeOrgStatus"}),twake_group_organization_deleted_value:t.string({env:"DM_TWAKE_GROUP_ORGANIZATION_DELETED_VALUE",default:"deleted"}),twake_group_display_name_attribute:t.string({env:"DM_TWAKE_GROUP_DISPLAY_NAME_ATTRIBUTE",default:"twakeDisplayName"}),twake_group_color_attribute:t.string({env:"DM_TWAKE_GROUP_COLOR_ATTRIBUTE",default:"twakeGroupColor"}),twake_group_created_at_attribute:t.string({env:"DM_TWAKE_GROUP_CREATED_AT_ATTRIBUTE",default:"twakeCreatedAt"}),twake_group_max_page_limit:t.number({env:"DM_TWAKE_GROUP_MAX_PAGE_LIMIT",default:1e3,min:1}),twake_group_member_fields:t.json({env:"DM_TWAKE_GROUP_MEMBER_FIELDS",default:{}})});function twakeGroupsOptionsOf(_){return e(a,_)}export{a as twakeGroupsOptions,twakeGroupsOptionsOf};
@@ -0,0 +1 @@
1
+ import{defineOptions as _,opt as e,readOptions as t}from"../../config/options.js";const n=_({twake_instance_dn:e.array({env:"DM_TWAKE_INSTANCE_DN",default:[],kind:"dns"}),twake_instance_skip_attribute:e.string({env:"DM_TWAKE_INSTANCE_SKIP_ATTRIBUTE",default:""}),twake_instance_skip_value:e.string({env:"DM_TWAKE_INSTANCE_SKIP_VALUE",default:""}),twake_instance_provider:e.string({env:"DM_TWAKE_INSTANCE_PROVIDER",default:"cloudery"}),twake_instance_id:e.string({env:"DM_TWAKE_INSTANCE_ID",default:"{uid}"}),twake_instance_fqdn_attribute:e.string({env:"DM_TWAKE_INSTANCE_FQDN_ATTRIBUTE",default:"twakeWorkspaceUrl"}),twake_instance_sent_attribute:e.string({env:"DM_TWAKE_INSTANCE_SENT_ATTRIBUTE",default:"twakeCreatedEventAt"}),twake_instance_locale:e.string({env:"DM_TWAKE_INSTANCE_LOCALE",default:""}),twake_instance_timeout:e.number({env:"DM_TWAKE_INSTANCE_TIMEOUT",default:3e4,min:1}),twake_instance_cloudery_url:e.string({env:"DM_TWAKE_INSTANCE_CLOUDERY_URL",default:""}),twake_instance_cloudery_token:e.string({env:"DM_TWAKE_INSTANCE_CLOUDERY_TOKEN",default:""}),twake_instance_cloudery_offer:e.string({env:"DM_TWAKE_INSTANCE_CLOUDERY_OFFER",default:"b2b_twake_default",emptyIsValue:!0}),twake_instance_cloudery_domain:e.string({env:"DM_TWAKE_INSTANCE_CLOUDERY_DOMAIN",default:""}),twake_instance_cloudery_organization_offer:e.string({env:"DM_TWAKE_INSTANCE_CLOUDERY_ORGANIZATION_OFFER",default:""}),twake_instance_cozy_url:e.string({env:"DM_TWAKE_INSTANCE_COZY_URL",default:""}),twake_instance_cozy_user:e.string({env:"DM_TWAKE_INSTANCE_COZY_USER",default:"admin"}),twake_instance_cozy_passphrase:e.string({env:"DM_TWAKE_INSTANCE_COZY_PASSPHRASE",default:""}),twake_instance_cozy_domain:e.string({env:"DM_TWAKE_INSTANCE_COZY_DOMAIN",default:""}),twake_instance_cozy_context:e.string({env:"DM_TWAKE_INSTANCE_COZY_CONTEXT",default:"default",emptyIsValue:!0}),twake_instance_cozy_apps:e.string({env:"DM_TWAKE_INSTANCE_COZY_APPS",default:"home,drive,settings,notes,dataproxy",emptyIsValue:!0}),twake_instance_cozy_org_id:e.string({env:"DM_TWAKE_INSTANCE_COZY_ORG_ID",default:""}),twake_instance_cozy_org_domain:e.string({env:"DM_TWAKE_INSTANCE_COZY_ORG_DOMAIN",default:""}),twake_instance_auth_exchange:e.string({env:"DM_TWAKE_INSTANCE_AUTH_EXCHANGE",default:"auth"}),twake_instance_b2b_exchange:e.string({env:"DM_TWAKE_INSTANCE_B2B_EXCHANGE",default:"b2b"}),twake_instance_user_created_key:e.string({env:"DM_TWAKE_INSTANCE_USER_CREATED_KEY",default:"user.created"}),twake_instance_organization_created_key:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_CREATED_KEY",default:"organization.created"}),twake_instance_workplace_created_key:e.string({env:"DM_TWAKE_INSTANCE_WORKPLACE_CREATED_KEY",default:"workplace.created"}),twake_instance_queue:e.string({env:"DM_TWAKE_INSTANCE_QUEUE",default:"workplace.created.ldap-rest"}),twake_instance_organization_base:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_BASE",default:""}),twake_instance_organization_id_attribute:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_ID_ATTRIBUTE",default:"ou"}),twake_instance_organization_domain_attribute:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_DOMAIN_ATTRIBUTE",default:"twakeDomain"}),twake_instance_organization_name_attribute:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_NAME_ATTRIBUTE",default:"description"}),twake_instance_organization_fqdn_attribute:e.string({env:"DM_TWAKE_INSTANCE_ORGANIZATION_FQDN_ATTRIBUTE",default:""})});function twakeInstancesOptionsOf(_){return t(n,_)}export{n as twakeInstancesOptions,twakeInstancesOptionsOf};
@@ -0,0 +1 @@
1
+ import{defineOptions as e,opt as _,readOptions as t}from"../../config/options.js";const E=e({twake_lifecycle_role_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_ROLE_ATTRIBUTE",default:""}),twake_lifecycle_lock_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_LOCK_ATTRIBUTE",default:""}),twake_lifecycle_lock_value:_.string({env:"DM_TWAKE_LIFECYCLE_LOCK_VALUE",default:""}),twake_lifecycle_deleted_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_DELETED_ATTRIBUTE",default:""}),twake_lifecycle_deleted_value:_.string({env:"DM_TWAKE_LIFECYCLE_DELETED_VALUE",default:"TRUE"}),twake_lifecycle_deleted_at_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_DELETED_AT_ATTRIBUTE",default:""}),twake_lifecycle_deleted_at_format:_.string({env:"DM_TWAKE_LIFECYCLE_DELETED_AT_FORMAT",default:"iso8601"}),twake_lifecycle_reason_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_REASON_ATTRIBUTE",default:""}),twake_lifecycle_rules:_.string({env:"DM_TWAKE_LIFECYCLE_RULES",default:""}),twake_lifecycle_member_attribute:_.string({env:"DM_TWAKE_LIFECYCLE_MEMBER_ATTRIBUTE",default:"member"})});function twakeLifecycleOptionsOf(e){return t(E,e)}export{E as twakeLifecycleOptions,twakeLifecycleOptionsOf};
@@ -0,0 +1 @@
1
+ import{defineOptions as e,opt as t,readOptions as _}from"../../config/options.js";const a=e({twake_space_base:t.string({env:"DM_TWAKE_SPACE_BASE",default:""}),twake_space_class:t.array({env:"DM_TWAKE_SPACE_CLASS",default:["top","twakeSpace"],kind:"identifiers"}),twake_space_display_name_attribute:t.string({env:"DM_TWAKE_SPACE_DISPLAY_NAME_ATTRIBUTE",default:"twakeDisplayName"}),twake_space_admin_attribute:t.string({env:"DM_TWAKE_SPACE_ADMIN_ATTRIBUTE",default:"twakeSpaceAdmin"}),twake_space_editor_attribute:t.string({env:"DM_TWAKE_SPACE_EDITOR_ATTRIBUTE",default:"twakeSpaceEditor"}),twake_space_viewer_attribute:t.string({env:"DM_TWAKE_SPACE_VIEWER_ATTRIBUTE",default:"twakeSpaceViewer"}),twake_space_user_role_attribute:t.string({env:"DM_TWAKE_SPACE_USER_ROLE_ATTRIBUTE",default:""}),twake_space_exchange:t.string({env:"DM_TWAKE_SPACE_EXCHANGE",default:"space"}),twake_space_sync_queue:t.string({env:"DM_TWAKE_SPACE_SYNC_QUEUE",default:"twake.space.sync.requested.ldap-rest"})});function twakeSpacesOptionsOf(e){return _(a,e)}export{a as twakeSpacesOptions,twakeSpacesOptionsOf};
@@ -0,0 +1 @@
1
+ import{defineOptions as e,opt as t,readOptions as _}from"../../config/options.js";const n=e({twake_tombstone_dn:t.array({env:"DM_TWAKE_TOMBSTONE_DN",default:[],kind:"dns"}),twake_tombstone_default_reason:t.string({env:"DM_TWAKE_TOMBSTONE_DEFAULT_REASON",default:"deleted"}),twake_tombstone_reason_header:t.string({env:"DM_TWAKE_TOMBSTONE_REASON_HEADER",default:"x-deletion-reason"}),twake_tombstone_reasons:t.array({env:"DM_TWAKE_TOMBSTONE_REASONS",default:[],kind:"identifiers"}),twake_tombstone_clear_attributes:t.array({env:"DM_TWAKE_TOMBSTONE_CLEAR_ATTRIBUTES",default:[],kind:"identifiers"}),twake_tombstone_erase_min_age:t.number({env:"DM_TWAKE_TOMBSTONE_ERASE_MIN_AGE",default:2592e3}),twake_tombstone_group_bases:t.array({env:"DM_TWAKE_TOMBSTONE_GROUP_BASES",default:[],kind:"dns"})});function twakeTombstoneOptionsOf(e){return _(n,e)}export{n as twakeTombstoneOptions,twakeTombstoneOptionsOf};
@@ -1 +1 @@
1
- import e from"../abstract/plugin.js";import{requestId as r}from"../lib/changeContext.js";import{runWithLogContext as o}from"../lib/requestContext.js";class WebLogs extends e{constructor(){super(...arguments),this.name="weblogs",this.roles=["logging"]}api(e){e.use((e,r,o)=>{let t=!0;this.logger.debug(`Incoming request: ${e.method} ${e.originalUrl}`);const s=Date.now();r.on("finish",()=>{const o={};e.proxyAuthUser?o.user=e.proxyAuthUser:e.user&&(o.user=e.user),t&&this.log(e,r,s,o),t=!1}),r.on("error",o=>{t&&this.log(e,r,s,{error:o.message}),t=!1}),r.on("close",()=>{r.writableEnded||(t&&this.log(e,r,s,{error:`Connection closed before response was sent for ${e.method} ${e.originalUrl}`}),t=!1)}),o()})}log(e,t,s,n){const i=Date.now()-s,l={...e.logDetails||{},requestId:r(e),method:e.method,url:e.originalUrl,status:t.statusCode,duration:i,ip:this.getClientIp(e),...n};o({requestId:l.requestId},()=>this.logger.notice(l))}getClientIp(e){const r=e.headers["x-forwarded-for"];if(r){return(Array.isArray(r)?r[0]:r).split(",")[0].trim()}return e.socket.remoteAddress||"unknown"}}export{WebLogs as default};
1
+ import e from"../abstract/plugin.js";import{requestId as r}from"../lib/changeContext.js";import{runWithLogContext as o}from"../lib/requestContext.js";class WebLogs extends e{name="weblogs";roles=["logging"];api(e){e.use((e,r,o)=>{let t=!0;this.logger.debug(`Incoming request: ${e.method} ${e.originalUrl}`);const s=Date.now();r.on("finish",()=>{const o={};e.proxyAuthUser?o.user=e.proxyAuthUser:e.user&&(o.user=e.user),t&&this.log(e,r,s,o),t=!1}),r.on("error",o=>{t&&this.log(e,r,s,{error:o.message}),t=!1}),r.on("close",()=>{r.writableEnded||(t&&this.log(e,r,s,{error:`Connection closed before response was sent for ${e.method} ${e.originalUrl}`}),t=!1)}),o()})}log(e,t,s,n){const i=Date.now()-s,l={...e.logDetails||{},requestId:r(e),method:e.method,url:e.originalUrl,status:t.statusCode,duration:i,ip:this.getClientIp(e),...n};o({requestId:l.requestId},()=>this.logger.notice(l))}getClientIp(e){const r=e.headers["x-forwarded-for"];if(r){return(Array.isArray(r)?r[0]:r).split(",")[0].trim()}return e.socket.remoteAddress||"unknown"}}export{WebLogs as default};
@@ -1,12 +1,3 @@
1
- /**
2
- * @module abstract/ldapFlat
3
- * @author Xavier Guimard <xguimard@linagora.com>
4
- *
5
- * Abstract class to manage LDAP entries in a flat branch
6
- * - add/delete entries
7
- * - modify entries
8
- * - validate with schema
9
- */
10
1
  import type { Express, Request, Response } from 'express';
11
2
  import type { DM } from '../bin';
12
3
  import type ldapActions from '../lib/ldapActions';
@@ -210,6 +201,26 @@ export interface LdapFlatConfig {
210
201
  * dn: uid=bob,ou=users,dc=example,dc=com
211
202
  * referencesUpdated: 3
212
203
  */
204
+ export declare const FlatListQuery: import("@sinclair/typebox").TObject<{
205
+ match: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
206
+ attribute: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
207
+ attributes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
208
+ limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
209
+ }>;
210
+ export declare const FlatEntryParams: import("@sinclair/typebox").TObject<{
211
+ id: import("@sinclair/typebox").TString;
212
+ }>;
213
+ export declare const FlatMoveBody: import("@sinclair/typebox").TObject<{
214
+ targetOrgDn: import("@sinclair/typebox").TString;
215
+ }>;
216
+ export declare const FlatMoveResponse: import("@sinclair/typebox").TObject<{
217
+ success: import("@sinclair/typebox").TBoolean;
218
+ departmentPath: import("@sinclair/typebox").TString;
219
+ departmentLink: import("@sinclair/typebox").TString;
220
+ }>;
221
+ export declare const FlatRenameBody: import("@sinclair/typebox").TObject<{
222
+ newId: import("@sinclair/typebox").TString;
223
+ }>;
213
224
  export default abstract class LdapFlat extends DmPlugin {
214
225
  base: string;
215
226
  ldap: ldapActions;
@@ -220,6 +231,12 @@ export default abstract class LdapFlat extends DmPlugin {
220
231
  singularName: string;
221
232
  pluralName: string;
222
233
  hookPrefix: string;
234
+ /** `hookBus`, raising this entity's own hooks */
235
+ private readonly ownHooks;
236
+ /**
237
+ * @param server the server
238
+ * @param config the entity, its hook prefix included
239
+ */
223
240
  constructor(server: DM, config: LdapFlatConfig);
224
241
  /**
225
242
  * Attributes never sent back over the API. A manager may reset a password
@@ -365,6 +382,19 @@ export default abstract class LdapFlat extends DmPlugin {
365
382
  * @throws BadRequestError if the provided DN is not a direct child of this.base
366
383
  */
367
384
  protected resolveDn(id: string): string;
385
+ /**
386
+ * The name and the DN a creation of `id` gives: `id` is the naming value,
387
+ * or a DN whose first RDN names the entry, folded as the directory folds
388
+ * it and written back in this branch.
389
+ *
390
+ * Both the write and the answer it gets after it read the DN here, so a
391
+ * spelling the directory folds differently cannot leave the entry created
392
+ * under one DN and read back at another.
393
+ */
394
+ protected creationOf(id: string): {
395
+ name: string;
396
+ dn: string;
397
+ };
368
398
  addEntry(id: string, additional?: AttributesList, req?: Request): Promise<boolean>;
369
399
  modifyEntry(id: string, changes: ModifyRequest, req?: Request): Promise<boolean>;
370
400
  /**
@@ -421,6 +451,7 @@ export default abstract class LdapFlat extends DmPlugin {
421
451
  truncated: boolean;
422
452
  }>;
423
453
  searchEntriesByName(name: string, partial?: boolean, attrs?: string[]): Promise<LdapList>;
424
- validateNewEntry(dn: string, entry: AttributesList): Promise<boolean>;
454
+ /** @returns the entry to write, under the schema's spellings */
455
+ validateNewEntry(dn: string, entry: AttributesList): Promise<AttributesList>;
425
456
  validateChanges(dn: string, changes: ModifyRequest): Promise<boolean>;
426
457
  }
@@ -3,30 +3,42 @@
3
3
  * @author Xavier Guimard <xguimard@linagora.com>
4
4
  */
5
5
  import type { Express } from 'express';
6
- import fetch, { type RequestInit, type Response } from 'node-fetch';
7
6
  import type winston from 'winston';
8
7
  import type { Config, DM } from '../bin';
9
8
  import type { Hooks, MaybePromise } from '../hooks';
9
+ import type HookBus from '../lib/hookBus';
10
10
  import type { HealthStatus } from '../lib/health';
11
+ import { type ServiceProvider, type ServiceToken } from '../lib/services';
12
+ import { type OptionsOf, type OptionSet } from '../config/options';
11
13
  export { asyncHandler, escapeDnValue, unescapeDnValue, escapeLdapFilter, validateDnValue, } from '../lib/utils';
14
+ export { defineRoute, registeredRoutes } from '../lib/route';
15
+ export { serviceToken } from '../lib/services';
16
+ export type { ServiceProvider, ServiceToken } from '../lib/services';
12
17
  export { HttpError, BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, UriTooLongError, TooManyRequestsError, BadGatewayError, ServiceUnavailableError, GatewayTimeoutError, } from '../lib/errors';
13
18
  export type { HealthStatus };
14
19
  export type Role = 'auth' | 'authz' | 'protect' | 'api' | 'logging' | 'demo' | 'consistency' | 'configurable';
15
20
  export default abstract class DmPlugin {
21
+ /** Options this plugin declares, see `docs/plugin-development/options.md` */
22
+ static optionDefinitions: OptionSet;
16
23
  /**
17
24
  * Properties inherited from parent (DM)
18
25
  */
19
26
  server: DM;
20
27
  config: Config;
28
+ hookBus: HookBus;
21
29
  logger: winston.Logger;
22
- registeredHooks: {
23
- [K in keyof Hooks]?: Function[];
24
- };
30
+ registeredHooks: Hooks;
25
31
  /**
26
32
  * Interfaces
27
33
  */
28
34
  hooks?: Hooks;
29
35
  dependencies?: Record<string, string>;
36
+ /**
37
+ * Services this plugin provides, each bound to its token. A consumer
38
+ * reaches them with `service(token)`, see
39
+ * `docs/plugin-development/services.md`.
40
+ */
41
+ provides?: ServiceProvider<unknown>[];
30
42
  roles?: Role[] | undefined;
31
43
  /**
32
44
  * Operational attributes this plugin follows through `onLdapEntryChange`,
@@ -76,12 +88,14 @@ export default abstract class DmPlugin {
76
88
  * Call another service: `fetch`, timed into the outbound metrics under
77
89
  * this plugin's name, see `docs/usage/operations.md#metrics`.
78
90
  */
79
- protected fetch(url: Parameters<typeof fetch>[0], init?: RequestInit): Promise<Response>;
91
+ protected fetch(url: Parameters<typeof globalThis.fetch>[0], init?: RequestInit): Promise<Response>;
80
92
  /**
81
93
  * Constructor
82
94
  * @param server DM object
83
95
  */
84
96
  constructor(server: DM);
97
+ /** Values of the options this plugin declares, read and checked */
98
+ options<T extends OptionSet>(definitions: T): OptionsOf<T>;
85
99
  /**
86
100
  * Uniq ID, to be used when calling hooks
87
101
  *
@@ -106,6 +120,18 @@ export default abstract class DmPlugin {
106
120
  protected hideNeverReturn<T>(data: T): T;
107
121
  /** Names already warned about by requirePlugin, so each warns at most once. */
108
122
  private missingPluginsWarned?;
123
+ /** Services already warned about by service(), so each warns at most once. */
124
+ private missingServicesWarned?;
125
+ /**
126
+ * Reach a service another plugin provides. Declare the providing plugin in
127
+ * `dependencies` so it registers first. Returns null and logs a single
128
+ * warning when nothing provides it, so the consumer skips its feature
129
+ * rather than throwing.
130
+ *
131
+ * @param token the service wanted
132
+ * @returns the provided instance, or null
133
+ */
134
+ protected service<T>(token: ServiceToken<T>): T | null;
109
135
  /**
110
136
  * Resolve a sibling plugin by its registry name (the value of its `name`
111
137
  * property). Declare it in `dependencies` so it loads first. Returns null and
@@ -9,6 +9,18 @@ import type { DM } from '../bin';
9
9
  import { type HealthStatus } from '../lib/health';
10
10
  import type { AttributesList } from '../lib/ldapActions';
11
11
  import DmPlugin, { type Role } from './plugin';
12
+ /** What a Twake plugin needs to reach its WebAdmin API */
13
+ export interface TwakeConnection {
14
+ url: string;
15
+ token: string;
16
+ concurrency: number;
17
+ /** Option holding the URL, named in the startup error when it is empty */
18
+ urlOption: string;
19
+ /** Option holding the token, named in the warning when it is empty */
20
+ tokenOption: string;
21
+ /** Option holding the concurrency, named when it is not a positive integer */
22
+ concurrencyOption: string;
23
+ }
12
24
  /**
13
25
  * Abstract base class for all Twake-related plugins (James, Calendar, Matrix, etc.)
14
26
  * Consolidates common patterns for HTTP communication with Twake WebAdmin APIs
@@ -26,11 +38,9 @@ export declare abstract class TwakePlugin extends DmPlugin {
26
38
  /**
27
39
  * Constructor - initializes common Twake plugin configuration
28
40
  * @param server DM server instance
29
- * @param urlConfig Configuration key for WebAdmin URL
30
- * @param tokenConfig Configuration key for WebAdmin token
31
- * @param concurrencyConfig Configuration key for concurrency limit
41
+ * @param connection WebAdmin URL, token and concurrency
32
42
  */
33
- constructor(server: DM, urlConfig: string, tokenConfig: string, concurrencyConfig: string);
43
+ constructor(server: DM, connection: TwakeConnection);
34
44
  /**
35
45
  * Create HTTP headers with optional Authorization token
36
46
  * @param contentType Optional Content-Type header value
@@ -19,10 +19,12 @@ import type { Hooks } from '../hooks';
19
19
  import ldapActions from '../lib/ldapActions';
20
20
  import type DmPlugin from '../abstract/plugin';
21
21
  import AuthBase from '../lib/auth/base';
22
+ import { type ServiceMap } from '../lib/services';
23
+ import HookBus from '../lib/hookBus';
22
24
  export type { Config };
23
25
  export * from '../lib/utils';
24
26
  export { asyncHandler } from '../lib/utils';
25
- export { HttpError, BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, UriTooLongError, TooManyRequestsError, BadGatewayError, ServiceUnavailableError, GatewayTimeoutError, } from '../lib/errors';
27
+ export { HttpError, BadRequestError, UnauthorizedError, ForbiddenError, AuthzRefusedError, NotFoundError, ConflictError, UriTooLongError, TooManyRequestsError, BadGatewayError, ServiceUnavailableError, GatewayTimeoutError, } from '../lib/errors';
26
28
  /**
27
29
  * @class DM
28
30
  */
@@ -33,12 +35,23 @@ export declare class DM {
33
35
  /** The listener, once `run()` opened it; shared by the `withConfig` views */
34
36
  get server(): import('http').Server | undefined;
35
37
  set server(server: import('http').Server | undefined);
36
- hooks: {
37
- [K in keyof Hooks]?: Function[];
38
- };
38
+ /**
39
+ * What each hook's subscribers do, as a plugin declares them. The type
40
+ * names every hook: raising or subscribing to one goes through `hookBus`,
41
+ * which checks the arguments.
42
+ */
43
+ hooks: Hooks;
44
+ readonly hookBus: HookBus;
39
45
  loadedPlugins: {
40
46
  [key: string]: DmPlugin;
41
47
  };
48
+ /**
49
+ * The services plugins provide, keyed by their token. Shared by `withConfig`
50
+ * views as the plugin registry is; `registerPlugin` fills it.
51
+ */
52
+ readonly services: ServiceMap;
53
+ /** The plugin that provided each service, for the duplicate warning. */
54
+ private readonly serviceOwners;
42
55
  ldap: ldapActions;
43
56
  operationSequence: number;
44
57
  logger: winston.Logger;
@@ -87,6 +100,10 @@ export declare class DM {
87
100
  * whose name is taken. A Map, so that `withConfig` views share it.
88
101
  */
89
102
  private readonly pluginLoads;
103
+ /** CLI arguments no declared option matched, reported after the logger exists */
104
+ private readonly unknownCliOptions;
105
+ /** Configuration read through a deprecated name, reported once the logger exists */
106
+ private readonly deprecationWarnings;
90
107
  /**
91
108
  * The request hooks of the authorization plugins, which `registerPlugin`
92
109
  * keeps ahead of every other plugin's. See {@link AUTHZ_FIRST_HOOKS}.
@@ -123,6 +140,12 @@ export declare class DM {
123
140
  * @returns a server view backed by this instance
124
141
  */
125
142
  withConfig(config: Config): DM;
143
+ /**
144
+ * Name the CLI arguments and `DM_*` variables nothing declares, among the
145
+ * plugins `--plugins` loaded and their `optionDefinitions`. A host that
146
+ * registers its own plugins after `ready` is not covered.
147
+ */
148
+ private reportUnknownOptions;
126
149
  /**
127
150
  * Mount the authentication dispatcher, once, before the first plugin that
128
151
  * can register a route.
@@ -283,4 +306,12 @@ export declare class DM {
283
306
  */
284
307
  private admitToComposition;
285
308
  registerPlugin(pluginName: string, obj: DmPlugin, name?: string): Promise<boolean>;
309
+ /**
310
+ * Record the services a plugin provides. The first provider of a token
311
+ * wins: a second one would answer for the same interface and leave the
312
+ * choice to the load order, so it is ignored and named in a warning.
313
+ *
314
+ * @param obj the plugin just registered
315
+ */
316
+ private registerServices;
286
317
  }
@@ -1,8 +1,6 @@
1
1
  import type { AttributesList } from '../lib/ldapActions';
2
2
  import type { ConfigTemplate } from '../lib/parseConfig';
3
- /** The headers `core/static` sends unless told otherwise: no framing by another site */
4
- export declare const STATIC_CSP = "frame-ancestors 'self'";
5
- export declare const STATIC_FRAME_OPTIONS = "SAMEORIGIN";
3
+ export { STATIC_CSP, STATIC_FRAME_OPTIONS } from '../plugins/static.options';
6
4
  export interface BranchPermissions {
7
5
  read?: boolean;
8
6
  write?: boolean;
@@ -34,6 +32,7 @@ export interface Config {
34
32
  log_level: 'error' | 'warn' | 'notice' | 'info' | 'debug';
35
33
  shutdown_timeout?: number;
36
34
  debug: boolean;
35
+ strict_options?: boolean;
37
36
  logger: 'console' | 'file';
38
37
  log_file?: string;
39
38
  api_prefix: string;
@@ -364,7 +363,6 @@ export interface Config {
364
363
  scim_bulk_max_payload_size?: number;
365
364
  scim_etag?: boolean;
366
365
  scim_base_url?: string;
367
- [key: string]: string | string[] | boolean | number | AttributesList | AuthConfig | undefined;
368
366
  }
369
367
  /**
370
368
  * Config arguments
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Configuration options declared by the code that reads them.
3
+ *
4
+ * A plugin describes its options next to its implementation with
5
+ * {@link defineOptions}; `src/config/args.ts` concatenates the declarations so
6
+ * the parser sees the same entries it always did. {@link readOptions} gives a
7
+ * plugin typed values, with the defaults applied and the value checked once.
8
+ *
9
+ * See `docs/plugin-development/options.md`.
10
+ */
11
+ import { type ArrayKind, type ConfigEntry, type ConfigTemplate } from '../lib/parseConfig';
12
+ import type { Config } from './args';
13
+ export type OptionType = 'string' | 'number' | 'boolean' | 'array' | 'json';
14
+ export interface OptionSpec<T> {
15
+ readonly type: OptionType;
16
+ /** Environment variable the option reads when the CLI does not set it */
17
+ readonly env?: string;
18
+ /** Value kept when neither the CLI nor the environment sets the option */
19
+ readonly default?: T;
20
+ /** Refuse an empty value rather than read it as unset */
21
+ readonly required?: boolean;
22
+ /**
23
+ * A string option whose empty value means something: keep it instead of
24
+ * reading the declared default, as an empty environment or CLI value does.
25
+ */
26
+ readonly emptyIsValue?: boolean;
27
+ /**
28
+ * Former CLI names still accepted, without the leading `--`: the parser
29
+ * reads them as the canonical name and warns at startup. An alias takes one
30
+ * value per occurrence, as the singular form does.
31
+ */
32
+ readonly aliases?: readonly string[];
33
+ /**
34
+ * Former environment variables still accepted, read when `env` is unset or
35
+ * holds an empty value the type reads as nothing
36
+ */
37
+ readonly envAliases?: readonly string[];
38
+ }
39
+ export interface StringOption extends OptionSpec<string> {
40
+ readonly type: 'string';
41
+ /** Values the option accepts, when it is a closed set */
42
+ readonly choices?: readonly string[];
43
+ }
44
+ export interface StringOrFalseOption extends OptionSpec<string | false> {
45
+ readonly type: 'string';
46
+ readonly allowFalse: true;
47
+ }
48
+ export interface NumberOption extends OptionSpec<number> {
49
+ readonly type: 'number';
50
+ readonly min?: number;
51
+ readonly max?: number;
52
+ }
53
+ export interface BooleanOption extends OptionSpec<boolean> {
54
+ readonly type: 'boolean';
55
+ }
56
+ export interface ArrayOption extends OptionSpec<string[]> {
57
+ readonly type: 'array';
58
+ /** CLI name of the plural form, without the leading `--` */
59
+ readonly plural?: string;
60
+ /** How a comma, space or semicolon separated value splits, see ArrayKind */
61
+ readonly kind?: ArrayKind;
62
+ }
63
+ export interface JsonOption<T> extends OptionSpec<T> {
64
+ readonly type: 'json';
65
+ }
66
+ export type OptionSet = Record<string, OptionSpec<unknown>>;
67
+ /** The typed values {@link readOptions} returns for a set of declarations */
68
+ export type OptionsOf<T extends OptionSet> = {
69
+ [K in keyof T]: T[K] extends OptionSpec<infer V> ? V : never;
70
+ };
71
+ /**
72
+ * A set of options, keyed by their `Config` name (`scim_max_results` for
73
+ * `--scim-max-results`). Identity function: its only job is to keep the
74
+ * literal types of the declarations.
75
+ */
76
+ export declare function defineOptions<T extends OptionSet>(options: T): T;
77
+ export declare const opt: {
78
+ string: (options?: Omit<StringOption, "type">) => StringOption;
79
+ number: (options?: Omit<NumberOption, "type">) => NumberOption;
80
+ boolean: (options?: Omit<BooleanOption, "type">) => BooleanOption;
81
+ array: (options?: Omit<ArrayOption, "type">) => ArrayOption;
82
+ stringOrFalse: (options?: Omit<StringOrFalseOption, "type" | "allowFalse">) => StringOrFalseOption;
83
+ json: <T = Record<string, unknown>>(options?: Omit<JsonOption<T>, "type">) => JsonOption<T>;
84
+ };
85
+ /**
86
+ * The parser entries a set of declarations amounts to. `args.ts` concatenates
87
+ * them with the global options and the other plugins' declarations.
88
+ */
89
+ export declare function toConfigEntries(options: OptionSet): ConfigEntry[];
90
+ /**
91
+ * `DM_*` variables no declared option reads. Test-only variables are left
92
+ * out: a suite sets its own infrastructure through the same prefix.
93
+ */
94
+ export declare function unknownEnvironmentOptions(entries: ConfigTemplate, env?: Record<string, string | undefined>, extra?: OptionSet[]): string[];
95
+ /**
96
+ * Read a plugin's options from a parsed configuration, applying the declared
97
+ * defaults and checking the values. Called once per instance by
98
+ * `DmPlugin.options()`.
99
+ *
100
+ * `parsedKeys` names the options the parser knew. An option declared outside
101
+ * `src/config/args.ts` reaches the parser as an unknown `--name value` pair,
102
+ * so its aliases are read here, under the key the unknown name produces.
103
+ */
104
+ export declare function readOptions<T extends OptionSet>(options: T, config: Config | Record<string, unknown>, parsedKeys?: ReadonlySet<string>, warn?: (message: string) => void): OptionsOf<T>;
@@ -75,6 +75,16 @@ export declare function localizedText(text: LocalizedText | undefined, language?
75
75
  * @returns the message
76
76
  */
77
77
  export declare function invalidValueMessage(name: string, attr: SchemaAttribute | undefined): string;
78
+ /**
79
+ * Refusal of a value longer than its attribute accepts, for a
80
+ * `BadRequestError`. Said apart from {@link invalidValueMessage}: the value
81
+ * is not malformed, it is longer than the attribute may hold.
82
+ *
83
+ * @param name attribute name
84
+ * @param maxLength the length it may not exceed
85
+ * @returns the message
86
+ */
87
+ export declare function tooLongMessage(name: string, maxLength: number): string;
78
88
  /** Roles the core itself looks for. See {@link SchemaRole}. */
79
89
  export declare const CORE_ROLES: readonly ["identifier", "displayName", "primaryEmail", "emailAliases", "emailQuota", "organizationLink", "organizationPath", "members", "owners", "accountStatus", "password", "passwordReset", "accountExpiry", "domainLink", "domainName"];
80
90
  /**
@@ -142,10 +152,19 @@ export interface SchemaAttribute {
142
152
  test?: string | RegExp;
143
153
  hint?: LocalizedText;
144
154
  branch?: string[];
155
+ /** Longest value, in UTF-16 code units, before `test` runs on it */
156
+ maxLength?: number;
145
157
  };
146
158
  default?: AttributeValue;
147
159
  required?: boolean;
148
160
  test?: string | RegExp;
161
+ /**
162
+ * Longest value, in UTF-16 code units, `test` is asked to judge: a longer one is
163
+ * refused before the pattern reads it. Without it, the length is
164
+ * {@link DEFAULT_MAX_LENGTH}; an attribute holding longer values declares
165
+ * it here, the reason being in `docs/client-development/schemas/README.md`.
166
+ */
167
+ maxLength?: number;
149
168
  /**
150
169
  * Plain-language description of `test`, shown to the user *before* they get
151
170
  * a value wrong — "Expected pattern 999 9999". It travels with the
@@ -334,6 +353,10 @@ export declare function modifiedAttributeNames(body: {
334
353
  * written as text where the schema declares one, a boolean as LDAP's
335
354
  * `TRUE` / `FALSE`, and anything else is refused.
336
355
  *
356
+ * The attribute is read as a request names it, under any case and with or
357
+ * without a `;binary` option: the value is converted by its type, as the
358
+ * validator accepted it.
359
+ *
337
360
  * @param schema schema of the entry, if any
338
361
  * @param field attribute name
339
362
  * @param value value as the client sent it
@@ -391,15 +414,41 @@ export declare function withoutAttributes<T extends object>(entry: T, hidden: Se
391
414
  * @throws BadRequestError naming the first DN that breaks a rule
392
415
  */
393
416
  export declare function checkDnValues(field: string, attr: SchemaAttribute, value: AttributeValue, entryExists: (dn: string) => Promise<boolean>): Promise<void>;
417
+ /**
418
+ * Longest value a pattern is asked to judge when the schema declares no
419
+ * `maxLength`. See `docs/client-development/schemas/README.md`.
420
+ */
421
+ export declare const DEFAULT_MAX_LENGTH = 4096;
422
+ /**
423
+ * The length a value is judged against: `maxLength` on the attribute, else
424
+ * the one on its `items`, else {@link DEFAULT_MAX_LENGTH}.
425
+ */
426
+ export declare function maxLengthOf(attr: SchemaAttribute | undefined): number;
394
427
  /**
395
428
  * Tell whether every value of an attribute matches its pattern: `test`, or
396
429
  * `items.test` for the elements of an array.
397
430
  *
431
+ * A value longer than {@link maxLengthOf} is refused without running the
432
+ * pattern: see {@link SchemaAttribute.maxLength}.
433
+ *
398
434
  * @param attr schema definition
399
435
  * @param value value submitted
400
- * @returns false when one value does not match
436
+ * @returns false when one value does not match, or is too long
401
437
  */
402
438
  export declare function matchesPattern(attr: SchemaAttribute, value: AttributeValue): boolean;
439
+ /**
440
+ * The value past the length its attribute accepts, if one is: what a refusal
441
+ * says, where the pattern would only have said the value is wrong.
442
+ *
443
+ * The default length applies to an attribute carrying a pattern, which it
444
+ * keeps within what the pattern was written for; an attribute without one is
445
+ * bounded only by the `maxLength` its schema declares.
446
+ *
447
+ * @param attr schema definition
448
+ * @param value value submitted
449
+ * @returns the too long value, or undefined when none is
450
+ */
451
+ export declare function tooLongValue(attr: SchemaAttribute, value: AttributeValue): AttributeValue | undefined;
403
452
  /**
404
453
  * Refuse a payload naming an attribute the server owns.
405
454
  *