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,34 +1,144 @@
1
1
  # Core hooks
2
2
 
3
- The hooks a plugin can register. Their arguments and return values are
4
- those typed in [hooks.ts](../../src/hooks.ts), which is the reference.
3
+ Hooks are how plugins talk to each other. Each one is declared in
4
+ [hooks.ts](../../src/hooks.ts), which is the reference for the arguments it
5
+ gets and what it must answer; a plugin subscribing to a hook it does not
6
+ name there does not compile.
5
7
 
6
- ## Demo hooks
8
+ ## The bus
7
9
 
8
- - **hello**: called by [helloworld demo plugin](../../src/plugins/demo/helloworld.ts)
10
+ `this.hookBus` (or `server.hookBus`) is how a plugin raises a hook, and how a
11
+ test waits for one. It has one method per kind of hook, told apart by what a
12
+ subscriber answers:
9
13
 
10
- ## [LDAP](../../src/lib/ldapActions.ts) hooks
14
+ | Kind | Method | Subscriber |
15
+ | -------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
16
+ | chained | `chain(name, value)` | answers the value, changed or not |
17
+ | notified | `notify(name, ...args)` | answers nothing, not awaited |
18
+ | notified | `notifyAndWait(name, ...args)` | answered nothing, awaited |
19
+ | notified | `notifyStrict(name, ...args)` | as `notifyAndWait`, but a failure goes to the caller |
20
+ | any | `has(name)`, `subscribers(name)` | — |
21
+ | any | `settled()` | resolves once every notification so far has run |
22
+ | any | `set`, `subscribe`, `insert`, `unsubscribe`, `toLast` | add a subscriber, or move one: for a test, or for a plugin changing what its base declares |
11
23
 
12
- - **ldapsearchopts**: called before any ldapsearch to modify search options
13
- - **ldapsearchresult**: called after any ldapsearch to modify search result
14
- - **ldapaddrequest**: called before any ldapadd
15
- - **ldapadddone**: launched once an ldapadd has written the entry, not awaited
16
- - **ldapaddafter**: awaited once an ldapadd has written the entry, before it
17
- answers, see [below](#ldapaddafter)
18
- - **ldapmodifyrequest**: called before any ldapmodify
19
- - **ldapdeleterequest**: called before any ldapdelete
20
- - **ldaprenamerequest**: called before any ldap rename/modifyDN operation
21
- - **ldapmodifyend**, **ldapdeleteend**, **ldaprenameend**: launched once a
22
- modify, delete or rename is over, whatever became of it, see
23
- [below](#ldapmodifyend-ldapdeleteend-ldaprenameend)
24
+ A chained hook is handed its arguments as one value, a tuple when it has
25
+ several. A notified hook is handed them one by one: its subscriber is
26
+ `(dn, entry, context) => …`, never `([dn, entry], context) => …` — that form
27
+ still compiles, the parameters being bivariant, but then reads the DN's first
28
+ character as the DN.
29
+
30
+ - **Chained.** The value goes from one subscriber to the next, each getting
31
+ what the one before answered; the last answer is the result. A subscriber
32
+ that throws stops the chain and the error travels up: a refusal is a
33
+ refusal. Several values are passed as one tuple.
34
+ - **Notified.** Subscribers run one after the other, outside the request
35
+ that caused the write, and are told what happened and was judged: they
36
+ react as the server — synchronizing, publishing, keeping invariants —
37
+ whoever asked. `notify` does not wait for them and never fails: the write
38
+ they react to is done, and a failing subscriber is logged, with its plugin
39
+ and hook, and counted in the [hook failure
40
+ metrics](../usage/operations.md#metrics), then the next one still runs.
41
+ - **`notifyStrict`** is for the rare subscriber whose failure is part of the
42
+ answer: a logout token reaches the provider only once what it closes is
43
+ recorded, so the library answers 400 and the provider retries. Every
44
+ subscriber runs even when one fails, and the first failure is raised once
45
+ they all have.
46
+
47
+ A test that would otherwise sleep waits instead:
48
+
49
+ ```ts
50
+ // One hook's subscribers
51
+ await server.hookBus.notifyAndWait('ldapadddone', dn, entry, {});
52
+ // or whatever the write under test started
53
+ await server.hookBus.settled();
54
+ ```
55
+
56
+ ## Hooks
57
+
58
+ <!-- hooks: generated from src/hooks.ts, do not edit by hand -->
59
+
60
+ - **hello**: raised by the [demo plugin](../../src/plugins/demo/helloworld.ts) for `/hello`, answered by the demo plugins
61
+ - **ldapsearchopts**: chained before any search, with its options
62
+ - **ldapsearchrequest**: chained before any search, with `[base, options, request]`; an authorization plugin refuses by throwing
63
+ - **ldapsearchresult**: chained after any search, with its result, before it is cached; for a paged search, when the first page is read
64
+ - **ldapsearchfilter**: chained on the result of a search after the cache, once per page, with `[result, request, options]`, to drop what the caller may not see
65
+ - **ldapaddrequest**: chained before any add, with `[dn, entry, request]`
66
+ - **ldapadddone**: notified once an add has written the entry, with the DN, the entry and the context, one by one; not awaited
67
+ - **ldapaddafter**: notified once an add has written the entry, before it answers, with the DN, the entry and the context, one by one, see [below](#ldapaddafter)
68
+ - **ldapmodifyrequest**: chained before any modify, with `[dn, changes, op, request]`
69
+ - **ldapmodifydone**: notified once a modify is written, with the DN, the changes, the operation number and the context, one by one
70
+ - **ldapmodifyend**: notified once a modify is over, whatever became of it, and its done subscribers have returned
71
+ - **ldapdeleterequest**: chained before any delete, with `[dn, request]`
72
+ - **ldapdeletedone**: notified once a delete is written, with the DN and the context, one by one
73
+ - **ldapdeleteend**: notified once a delete is over, whatever became of it, and its done subscribers have returned
74
+ - **ldaprenamerequest**: chained before any rename or move, with `[dn, newDn, request]`
75
+ - **ldaprenamedone**: notified once a rename or a move is written, with the DN, its new DN and the context, one by one
76
+ - **ldaprenameend**: notified once a rename is over, whatever became of it, and its done subscribers have returned
77
+ - **oidcsessionvalid**: chained with the claims of a session and the verdict so far, see [the OpenID Connect page](../usage/plugins/auth/openid-connect.md)
78
+ - **oidclogouttoken**: notified with the claims of a session a logout token or `/logout` ends
79
+ - **oidclogin**: notified with the claims of a session just established
80
+ - **ldapgroupvalidatenew**: chained before a group is created, with `[dn, entry]`
81
+ - **ldapgroupadd**: chained before a group is added, with `[dn, entry]`
82
+ - **ldapgroupadddone**: notified once a group is added, with the DN and the entry, one by one
83
+ - **ldapgroupvalidatechanges**: chained before a group is modified, with `[dn, changes]`
84
+ - **ldapgroupmodify**: chained before a group is modified, with `[dn, changes, op]`
85
+ - **ldapgroupmodifydone**: notified once a group is modified, with the DN, the changes and the operation number, one by one
86
+ - **ldapgrouprename**: chained before a group is renamed, with `[dn, newDn]`
87
+ - **ldapgrouprenamedone**: notified once a group is renamed, with the DN and its new DN, one by one
88
+ - **ldapgroupdelete**: chained before a group is deleted, with its DN
89
+ - **ldapgroupdeletedone**: notified once a group is deleted, with its DN
90
+ - **ldapgroupaddmember**: chained before members are added, with `[cn, members]`
91
+ - **ldapgroupdeletemember**: chained before a member is removed, with `[cn, member]`
92
+ - **ldapgroupvalidatemembers**: chained before a member is added, to judge the members, with `[dn, members]`
93
+ - **\_ldapgrouplist**: chained around the list of groups, internal to the groups plugin
94
+ - **onLdapEntryChange**: notified with the entry before and after any write, and who made it
95
+ - **onLdapChange**: notified with the attributes a write changed
96
+ - **onLdapMailChange**: notified with the mail before and after a change, or with no new mail for a delete
97
+ - **onLdapAliasChange**: notified with the mail and the aliases before and after a change
98
+ - **onLdapQuotaChange**: notified with the mail and the quota before and after a change
99
+ - **onLdapForwardChange**: notified with the mail and the forwards before and after a change
100
+ - **onLdapDisplayNameChange**: notified with the name before and after a change
101
+ - **onLdapDriveQuotaChange**: notified with the drive quota before and after a change
102
+ - **externaluserentry**: chained before an external member is created, with `[dn, entry]`
103
+ - **externaluseradded**: notified once an external member is created, with the DN and the entry, one by one
104
+ - **beforeAuth**: chained on a request before it is authenticated, with `[request, response]`
105
+ - **afterAuth**: chained on a request once it is authenticated, with `[request, response]`
106
+ - **getOrganisationTop**: chained before the top organization is answered, with `[request, entry]`
107
+ - **scimusercreate**: chained before a SCIM user is created, with `[user, request]`
108
+ - **scimusercreatedone**: notified once a SCIM user is created
109
+ - **scimuserupdate**: chained before a SCIM user is replaced, with `[id, user, request]`
110
+ - **scimuserupdatedone**: notified once a SCIM user is replaced, with id, user (one by one)
111
+ - **scimuserdelete**: chained before a SCIM user is deleted, with `[id, request]`
112
+ - **scimuserdeletedone**: notified once a SCIM user is deleted, with its id
113
+ - **scimgroupcreate**: chained before a SCIM group is created, with `[group, request, base]`
114
+ - **scimgroupcreatedone**: notified once a SCIM group is created
115
+ - **scimgroupupdate**: chained before a SCIM group is replaced, with `[id, group, request]`
116
+ - **scimgroupupdatedone**: notified once a SCIM group is replaced, with id, group (one by one)
117
+ - **scimgroupdelete**: chained before a SCIM group is deleted, with `[id, request]`
118
+ - **scimgroupdeletedone**: notified once a SCIM group is deleted, with its id
119
+ - **scimbulkdone**: notified once a SCIM bulk request is answered
120
+ - **twakedeletionreplay**: notified with the DN, the entry and the context, one by one when a delete asks of an entry that is already a tombstone
121
+
122
+ <!-- /hooks -->
123
+
124
+ ## Flat entities
125
+
126
+ A plugin built on `LdapFlat` (`--ldap-flat-schema`) raises one set of hooks
127
+ per entity: `ldap${entity.name}` followed by the suffix of the shared hook of
128
+ the same kind — `add` and `adddone`, `modify` and `modifydone`, `rename` and
129
+ `renamedone`, `delete` and `deletedone`, with the same arguments — plus
130
+ `{prefix}move` before an entry is moved, followed by `onLdapChange` as a
131
+ modify is. A plugin watching one declares it beside its other hooks.
132
+
133
+ ## LDAP write hooks
24
134
 
25
135
  ### ldapaddafter
26
136
 
27
137
  For what must follow an add within the same request, such as provisioning
28
138
  the account the entry describes.
29
139
 
30
- - Subscribers get `[dn, entry]` and the change context, as `ldapadddone`
31
- does. They run one after the other, and `add()` answers once the last has
140
+ - Subscribers get the DN, the entry and the change context, one by one, as
141
+ `ldapadddone` does. They run one after the other, and `add()` answers once the last has
32
142
  returned.
33
143
  - An error is logged and goes no further: the add has succeeded, and the
34
144
  next subscriber still runs. A subscriber that can fail owns its repair
@@ -38,7 +148,8 @@ the account the entry describes.
38
148
  empty. Some of these follow an API call all the same, so select entries by
39
149
  their DN or attributes rather than by an empty context.
40
150
  - The entry is the one submitted, after `ldapaddrequest`, not what the
41
- directory holds: no operational attributes, and the password as given.
151
+ directory holds: no operational attributes, and a `Buffer` value — a
152
+ password sent that way — read as a string.
42
153
  - `ldapadddone` and `onLdapEntryChange` fire whatever this hook does, and in
43
154
  no set order with it: an add event may or may not include what a
44
155
  subscriber here wrote.
@@ -65,8 +176,17 @@ never reaches its "done" hook; its "end" hook fires all the same.
65
176
  its `ldaprenamedone` runs outside any operation, even when a request hook
66
177
  made it.
67
178
 
68
- ## [onChange](../usage/plugins/ldap/on-change.md) hooks
179
+ ## onLdapChange
69
180
 
70
181
  - **onLdapEntryChange**: called after any write, with the entry before and
71
- after it, and who made it
72
- - **onLdapChange**, **onLdapMailChange**…: derived from it
182
+ after it, and who made it.
183
+ - **onLdapChange**, **onLdapMailChange**…: derived from it, and the
184
+ attribute ones fire only while the entry has the attribute configured for
185
+ them. A delete fires `onLdapEntryChange`, `onLdapChange` and
186
+ `onLdapMailChange` only.
187
+
188
+ ## Ordering
189
+
190
+ Hooks run in the order their plugins loaded, but for the request hooks of
191
+ authorization plugins, which run before every other plugin's, see
192
+ [dependencies](dependencies.md#hook-execution-order).
@@ -3,7 +3,15 @@
3
3
  The published API reference at <https://linagora.github.io/ldap-rest/api/> is
4
4
  generated from `openapi.json`, which itself is built statically by
5
5
  [`scripts/generate-openapi.ts`](../../scripts/generate-openapi.ts) — it walks
6
- every plugin's `api()` method and reads the JSDoc comments above each route.
6
+ every plugin's `api()` method and reads the JSDoc comments above each route,
7
+ and reads the declaration of every [`defineRoute`](routes.md) call.
8
+
9
+ With `defineRoute` the declaration is the document: the generator takes its
10
+ `summary`, `description`, `tags`, `params`, `query`, `body` and `responses`,
11
+ and imports the exported schema constants it names. An `@openapi` block above
12
+ the call still wins over what the declaration says — a `requestBody` it
13
+ describes replaces the declared `body` schema whole, rather than merging into
14
+ it — and a route registered with `app.<verb>(` still needs one.
7
15
 
8
16
  You don't need to touch the generator to enrich your plugin's documentation.
9
17
  Two JSDoc directives drive the entire pipeline:
@@ -139,6 +147,11 @@ npm run generate:openapi # writes openapi.json
139
147
  npm run build:pages # rebuilds the static site under _site/
140
148
  ```
141
149
 
150
+ `openapi.json` is committed: it is what the site publishes, so a change to a
151
+ route or to a schema it declares must come with a regenerated document. CI
152
+ runs `npm run check:openapi`, which builds the spec and fails when the file
153
+ differs, naming the first line at fault.
154
+
142
155
  Open `_site/api/index.html` to preview the Redoc rendering. The published
143
156
  site at <https://linagora.github.io/ldap-rest/api/> is rebuilt automatically
144
157
  on every `v*` tag push.
@@ -0,0 +1,136 @@
1
+ # Plugin options
2
+
3
+ A core plugin declares its configuration options next to its code, in a
4
+ `<plugin>.options.ts` file. A family that shares imports and lifecycle keeps
5
+ one set per plugin in a single `<family>.options.ts` file, named after the
6
+ family. `src/config/args.ts` concatenates those declarations,
7
+ so the parser, the command line and the environment variables see the same
8
+ entries as before. The plugin then reads typed values instead of casting
9
+ `this.config`.
10
+
11
+ ## Declaring options
12
+
13
+ ```typescript
14
+ // src/plugins/example/example.options.ts
15
+ import { defineOptions, opt, type OptionsOf } from '../../config/options';
16
+
17
+ export const exampleOptions = defineOptions({
18
+ example_url: opt.string({
19
+ env: 'DM_EXAMPLE_URL',
20
+ required: true,
21
+ }),
22
+ example_timeout: opt.number({
23
+ env: 'DM_EXAMPLE_TIMEOUT',
24
+ default: 5000,
25
+ min: 1,
26
+ }),
27
+ example_retry: opt.boolean({
28
+ env: 'DM_EXAMPLE_RETRY',
29
+ default: false,
30
+ }),
31
+ example_domains: opt.array({
32
+ env: 'DM_EXAMPLE_DOMAINS',
33
+ default: [],
34
+ plural: 'example-domains',
35
+ kind: 'identifiers',
36
+ }),
37
+ });
38
+
39
+ export type ExampleOptions = OptionsOf<typeof exampleOptions>;
40
+ ```
41
+
42
+ The key is the `Config` name: `example_timeout` is `--example-timeout`,
43
+ `DM_EXAMPLE_TIMEOUT` when the descriptor names that variable.
44
+
45
+ ### Deprecating an option
46
+
47
+ Rename an option by giving the old name as an alias: the parser still reads
48
+ it, and the server warns once at startup. Keep the alias for two minor
49
+ releases, as [CONTRIBUTING.md](../../CONTRIBUTING.md#deprecating-a-published-contract)
50
+ requires.
51
+
52
+ ```typescript
53
+ export const exampleOptions = defineOptions({
54
+ example_url: opt.string({
55
+ env: 'DM_EXAMPLE_URL',
56
+ aliases: ['example-uri'],
57
+ envAliases: ['DM_EXAMPLE_URI'],
58
+ }),
59
+ });
60
+ ```
61
+
62
+ A CLI alias takes one value per occurrence, as the singular form does, and an
63
+ array option's plural form has no alias: renaming it is a breaking change. An
64
+ environment alias is read when `env` is unset, or empty for a type where an
65
+ empty value means nothing, and only for a non-array option when its canonical
66
+ CLI name is absent; an array option combines the environment value with the
67
+ CLI one, so its alias is still read.
68
+
69
+ Each declaration takes:
70
+
71
+ | Field | Meaning |
72
+ | ------------ | --------------------------------------------------------------------- |
73
+ | `env` | Environment variable read when the CLI does not set the option |
74
+ | `default` | Value kept when neither the CLI nor the environment sets the option |
75
+ | `required` | Refuse an empty value instead of reading it as unset |
76
+ | `plural` | Array only: CLI name of the plural form, without the leading `--` |
77
+ | `kind` | Array only: how a value splits, see `ArrayKind` in `parseConfig.ts` |
78
+ | `min/max` | Number only: bounds checked when the option is read |
79
+ | `choices` | String only: the values the option accepts |
80
+ | `aliases` | Former CLI names still accepted, without the leading `--` |
81
+ | `envAliases` | Former environment variables still accepted, read when `env` is unset |
82
+
83
+ ## Reading options
84
+
85
+ The plugin names the declaration as a static field, and asks for the values in
86
+ its constructor:
87
+
88
+ ```typescript
89
+ export default class Example extends DmPlugin {
90
+ static optionDefinitions = exampleOptions;
91
+ name = 'example';
92
+
93
+ private readonly settings: ExampleOptions;
94
+
95
+ constructor(server: DM) {
96
+ super(server);
97
+ this.settings = this.options(exampleOptions);
98
+ }
99
+ }
100
+ ```
101
+
102
+ `options(exampleOptions)` applies the defaults, copies array defaults and
103
+ checks `required`, `min`, `max` and `choices`. The base constructor asks once,
104
+ so a bad value stops the plugin from loading rather than failing at the first
105
+ request. Each later call reads the configuration again rather than keeping
106
+ the first values. `--plugin example:name:{...}` overrides still work: the view the
107
+ plugin receives carries the merged configuration, and `options()` reads that
108
+ view. Give it the same declaration object the class names in
109
+ `optionDefinitions`: any other one is refused, so the generic type parameter
110
+ cannot describe the wrong view.
111
+
112
+ ## Wiring it in
113
+
114
+ Import the declaration in `src/config/args.ts` and spread it where the plugin's
115
+ options used to be:
116
+
117
+ ```typescript
118
+ import { exampleOptions } from '../plugins/example/example.options';
119
+ import { toConfigEntries } from './options';
120
+
121
+ const configArgs: ConfigTemplate = [
122
+ // ...
123
+ ...toConfigEntries(exampleOptions),
124
+ ];
125
+ ```
126
+
127
+ The `Config` interface keeps the hand-written fields until every plugin has
128
+ moved. A migrated plugin no longer reads them: it uses `options()`.
129
+
130
+ Plugins outside the repository cannot add to `args.ts` yet, but they can
131
+ declare options the same way and name them in `optionDefinitions`: the startup
132
+ report subtracts those keys and their aliases, and `options()` reads a declared
133
+ `env` variable or a deprecated CLI alias the parser did not carry, warning at
134
+ startup for the alias as it does for a core option. An option without a
135
+ declaration still reaches `this.config` from an unknown `--key value` pair, and
136
+ the startup report names it.
@@ -0,0 +1,152 @@
1
+ # Declaring routes
2
+
3
+ `defineRoute` declares an HTTP route once: its inputs, their schemas, what
4
+ the OpenAPI document says of it, and the handler. The schemas are checked
5
+ before the handler runs, the openapi generator describes the route from the
6
+ declaration, and the route probe finds it in the registry.
7
+
8
+ ```typescript
9
+ import { Type } from '@sinclair/typebox';
10
+ import type { Express } from 'express';
11
+
12
+ import DmPlugin, { defineRoute, type Role } from '../../abstract/plugin';
13
+
14
+ /** Exported: the OpenAPI generator reads it without running the plugin. */
15
+ export const GroupParams = Type.Object({
16
+ cn: Type.String({ minLength: 1 }),
17
+ });
18
+
19
+ export const GroupQuery = Type.Object({
20
+ dryRun: Type.Optional(Type.Boolean()),
21
+ });
22
+
23
+ export const GroupMembers = Type.Object(
24
+ { member: Type.Union([Type.String(), Type.Array(Type.String())]) },
25
+ { additionalProperties: false }
26
+ );
27
+
28
+ export const MembersAdded = Type.Object({ added: Type.Number() });
29
+
30
+ export default class MyPlugin extends DmPlugin {
31
+ name = 'myPlugin';
32
+ roles: Role[] = ['api'] as const;
33
+
34
+ api(app: Express): void {
35
+ defineRoute(app, {
36
+ method: 'post',
37
+ path: `${this.config.api_prefix}/v1/ldap/groups/:cn/members`,
38
+ params: GroupParams,
39
+ query: GroupQuery,
40
+ body: GroupMembers,
41
+ responses: {
42
+ 200: { schema: MembersAdded, description: 'Members added' },
43
+ },
44
+ summary: 'Add members to a group',
45
+ tags: ['Groups'],
46
+ handler: async ({ params, query, body, res }) => {
47
+ const added = await this.addMembers(params.cn, body.member, {
48
+ dryRun: query.dryRun ?? false,
49
+ });
50
+ res.json({ added });
51
+ },
52
+ });
53
+ }
54
+ }
55
+ ```
56
+
57
+ The handler has the same duties as an `asyncHandler` callback: it answers
58
+ through `res`, and a rejection is passed to the error middleware.
59
+
60
+ ## Schemas
61
+
62
+ Each schema is an **exported constant**, possibly imported from another
63
+ module. `npm run generate:openapi` does not run the plugin: it finds the
64
+ `defineRoute` call in the source, imports the module that exports each
65
+ schema, and reads its value — a TypeBox schema is JSON Schema already. A
66
+ schema written inline in the declaration is checked at runtime but is not
67
+ in the document, and the generator warns about it.
68
+
69
+ Keep in mind that the schemas are part of the published API: give the
70
+ properties a `description`, or rely on what TypeBox emits.
71
+
72
+ ## Inputs
73
+
74
+ | Field | Source | Checked as |
75
+ | -------- | ------------ | ---------- |
76
+ | `params` | `req.params` | converted |
77
+ | `query` | `req.query` | converted |
78
+ | `body` | `req.body` | as parsed |
79
+
80
+ A route that needs a route middleware — a multipart parser, say — passes it
81
+ as `middleware`. Express runs it before the handler, and before the `Accept`
82
+ and schema checks: a multipart body is read before the request may be
83
+ refused.
84
+
85
+ Path and query values always arrive as strings, so they are converted before
86
+ being checked: a `Type.Number()` query accepts `?limit=10` and hands the
87
+ handler `10`. The body is whatever the registered parser left in `req.body`;
88
+ a request carrying none is checked as an empty object, so an all-optional
89
+ schema accepts it and a required field is refused by name.
90
+
91
+ A part the route declares no schema for is handed to the handler unchecked,
92
+ as before. The declaration feeds the document the same way: `params` becomes
93
+ the path parameters, `query` the query parameters, `body` the JSON request
94
+ body, and `responses` the answers; the standard 400/401/404/500 entries stay.
95
+
96
+ An answer is a schema, or `{ schema, description }` when the document needs
97
+ a sentence of its own:
98
+
99
+ ```typescript
100
+ responses: {
101
+ 200: { schema: MembersAdded, description: 'Members added' },
102
+ 409: { schema: AlreadyMember, description: 'Already a member' },
103
+ 500: ErrorSchema,
104
+ }
105
+ ```
106
+
107
+ Only the parameters the path actually carries (`:cn` → `{cn}`) become path
108
+ parameters: a `params` key the path does not name is reported by the
109
+ generator and left out, since OpenAPI refuses it.
110
+
111
+ ## Refusals
112
+
113
+ A request that fails a schema is answered with `400` and a body naming every
114
+ failing path:
115
+
116
+ ```json
117
+ {
118
+ "error": "Invalid request: body.member: Expected string; query.limit: Expected number"
119
+ }
120
+ ```
121
+
122
+ A caller whose `Accept` excludes JSON is answered `400 Bad request` before
123
+ the handler runs. A route serving another media type sets `json: false` and
124
+ answers it itself; SCIM does, for `application/scim+json`. Its declared
125
+ schemas are still checked first, and a failure there is answered in the REST
126
+ shape (`Invalid request: …`): such a route validates the rest of its input
127
+ itself. The handler also receives Express's `next`, for a route that falls
128
+ through or has already answered.
129
+
130
+ `Type.Object` accepts unknown keys unless the schema says
131
+ `{ additionalProperties: false }`. A route migrated from hand-written checks
132
+ starts without that option, so it refuses no client it accepted before;
133
+ tighten it one route at a time.
134
+
135
+ ## The registry
136
+
137
+ Every declaration is recorded on the app, schemas included, without its
138
+ handler. `registeredRoutes(app)` returns them, and `unregisteredRoutes(app)`
139
+ lists what the Express stack carries that `defineRoute` did not declare.
140
+
141
+ ## The migration guard
142
+
143
+ `npm run lint` refuses `app.<verb>(` under `src/plugins` and `src/abstract`,
144
+ through `eslint-rules/no-raw-route.mjs`. `eslint-rules/route-allowlist.json`
145
+ names the files a raw route is tolerated in; it is empty, so every route in
146
+ the tree is declared. `test/scripts/routeAllowlist.test.ts` keeps the list
147
+ exact: a listed file must still declare a raw route, and an unlisted file
148
+ must not.
149
+
150
+ `test/security/routeValidation.test.ts` reads the registry and sends each
151
+ declared query and body a value it must refuse, so a new declaration is
152
+ probed without touching the test.
@@ -0,0 +1,95 @@
1
+ # Plugin services
2
+
3
+ A plugin sometimes needs what another plugin holds: the group operations, the
4
+ entity schemas, the trash. A service is a typed interface a plugin provides
5
+ under a token, and another plugin reaches with `this.service(token)`.
6
+
7
+ The token carries the interface. The compiler checks that an instance bound
8
+ with `with()` answers it, and gives a consumer the type it declared. Whether a
9
+ provider exists is a runtime question: a plugin that stops declaring the token
10
+ still compiles, and its consumers get `null` and a warning. The plugin
11
+ registry name stays what it is for: loading a plugin and naming it in the
12
+ logs.
13
+
14
+ ## Declaring a service
15
+
16
+ Put the token and its interface in a module both sides import — a plugin
17
+ family's own service in its own module. A service shared between families is
18
+ declared in `src/lib/services/` and re-exported from `ldap-rest/plugin`, one
19
+ of the package's entry points; a module under `src/lib/` has no export path of
20
+ its own.
21
+
22
+ ```typescript
23
+ import { serviceToken } from 'ldap-rest/plugin';
24
+
25
+ export interface GroupsApi {
26
+ listGroups(): Promise<string[]>;
27
+ }
28
+
29
+ export const GroupsService = serviceToken<GroupsApi>('groups');
30
+ ```
31
+
32
+ Declare a token once. The object is the registry key; calling `serviceToken`
33
+ again under the same name declares another service, not the same one. The name
34
+ only identifies it in the logs.
35
+
36
+ ## Providing a service
37
+
38
+ Bind the plugin instance to each token it provides:
39
+
40
+ ```typescript
41
+ import DmPlugin from 'ldap-rest/plugin';
42
+
43
+ export default class LdapGroups extends DmPlugin {
44
+ name = 'ldapGroups';
45
+ provides = [GroupsService.with(this)];
46
+
47
+ async listGroups(): Promise<string[]> {
48
+ /* … */
49
+ }
50
+ }
51
+ ```
52
+
53
+ `with(this)` is checked by the compiler: an instance that does not answer the
54
+ interface does not compile. A plugin can provide several services. A service
55
+ is registered once its plugin's registration completes — after `api()` and
56
+ the hooks — so a consumer loaded afterwards finds it, and a provider does not
57
+ find its own service there. A plugin needing another one declares it in
58
+ [`dependencies`](dependencies.md#explicit-dependencies) so that it loads
59
+ first.
60
+
61
+ The first provider to register wins. A second one offering the same token is
62
+ ignored and named in a warning: which one answered would otherwise depend on
63
+ load order. Loading the same module twice under two names gives two instances
64
+ but one module-level token, so the second instance cannot provide it either.
65
+
66
+ When a host registers a plugin after startup, its `assertComposition` runs
67
+ before the service is registered, so a provider does not find its own service
68
+ there; the configuration's plugins are all registered before their
69
+ `assertComposition` runs.
70
+
71
+ ## Consuming a service
72
+
73
+ ```typescript
74
+ const groups = this.service(GroupsService);
75
+ if (!groups) {
76
+ // Nothing provides it: the plugin skips the feature it would serve
77
+ return;
78
+ }
79
+ await groups.listGroups();
80
+ ```
81
+
82
+ `service()` answers the provided instance, or `null` when nothing provides the
83
+ token. It logs one warning per plugin and service rather than one per call. A
84
+ plugin that cannot work without a service declares the provider in
85
+ `dependencies`, checks the answer in `assertComposition`, and throws naming the
86
+ service — that stops the server at startup instead of leaving a feature
87
+ silently absent. A constructor runs before the dependencies load, so it cannot
88
+ be the place for that check.
89
+
90
+ The registry belongs to the server and is shared by the views `withConfig`
91
+ builds for plugins loaded with overrides. It has no removal: a plugin
92
+ registers once and keeps its services for the life of the server.
93
+
94
+ `this.requirePlugin(name)` remains for a plugin whose interface is not yet
95
+ declared as a service; new code reaches another plugin through a token.
@@ -92,12 +92,12 @@ jobs:
92
92
  runs-on: ubuntu-latest
93
93
 
94
94
  steps:
95
- - uses: actions/checkout@v3
95
+ - uses: actions/checkout@v7
96
96
 
97
97
  - name: Setup Node.js
98
- uses: actions/setup-node@v3
98
+ uses: actions/setup-node@v7
99
99
  with:
100
- node-version: '20'
100
+ node-version: '22'
101
101
 
102
102
  - name: Install dependencies
103
103
  run: npm ci