ldap-rest 1.2.1 → 1.3.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 (337) 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/ldapActions.js +1 -1
  18. package/dist/lib/ldapSchema.js +1 -1
  19. package/dist/lib/parseConfig.js +1 -1
  20. package/dist/lib/route.js +1 -0
  21. package/dist/lib/schemaFile.js +1 -1
  22. package/dist/lib/storage/store.js +1 -1
  23. package/dist/lib/utils.js +1 -1
  24. package/dist/lib/version.js +1 -0
  25. package/dist/logger/winston.js +1 -1
  26. package/dist/plugins/auth/auth.options.js +1 -0
  27. package/dist/plugins/auth/authzDynamic.js +1 -1
  28. package/dist/plugins/auth/authzLinid1.js +1 -1
  29. package/dist/plugins/auth/authzPerBranch.js +1 -1
  30. package/dist/plugins/auth/authzPerRoute.js +1 -1
  31. package/dist/plugins/auth/authzScope.js +1 -1
  32. package/dist/plugins/auth/crowdsec.js +1 -1
  33. package/dist/plugins/auth/fake.js +1 -1
  34. package/dist/plugins/auth/hmac.js +1 -1
  35. package/dist/plugins/auth/llng.js +1 -1
  36. package/dist/plugins/auth/openidconnect.js +1 -1
  37. package/dist/plugins/auth/rateLimit.js +1 -1
  38. package/dist/plugins/auth/token.js +1 -1
  39. package/dist/plugins/auth/totp.js +1 -1
  40. package/dist/plugins/auth/trustedProxy.js +1 -1
  41. package/dist/plugins/bcl.js +1 -1
  42. package/dist/plugins/bcl.options.js +1 -0
  43. package/dist/plugins/configApi.js +1 -1
  44. package/dist/plugins/demo/helloworld.js +1 -1
  45. package/dist/plugins/ldap/accountLifecycle.js +1 -1
  46. package/dist/plugins/ldap/bulkImport.js +1 -1
  47. package/dist/plugins/ldap/departmentSync.js +1 -1
  48. package/dist/plugins/ldap/enterpriseRules.js +1 -1
  49. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  50. package/dist/plugins/ldap/flatGeneric.js +1 -1
  51. package/dist/plugins/ldap/groups.js +1 -1
  52. package/dist/plugins/ldap/ldap.options.js +1 -0
  53. package/dist/plugins/ldap/onChange.js +1 -1
  54. package/dist/plugins/ldap/organizations.js +1 -1
  55. package/dist/plugins/ldap/passwordPolicy.js +1 -1
  56. package/dist/plugins/ldap/raw.js +1 -1
  57. package/dist/plugins/ldap/trash.js +1 -1
  58. package/dist/plugins/metrics.js +1 -1
  59. package/dist/plugins/metrics.options.js +1 -0
  60. package/dist/plugins/rabbitmq.js +1 -1
  61. package/dist/plugins/rabbitmq.options.js +1 -0
  62. package/dist/plugins/scim/baseResolver.js +1 -1
  63. package/dist/plugins/scim/bulk.js +1 -1
  64. package/dist/plugins/scim/discovery.js +1 -1
  65. package/dist/plugins/scim/errors.js +1 -1
  66. package/dist/plugins/scim/filter.js +1 -1
  67. package/dist/plugins/scim/groups.js +1 -1
  68. package/dist/plugins/scim/mapping.js +1 -1
  69. package/dist/plugins/scim/patch.js +1 -1
  70. package/dist/plugins/scim/scim.js +1 -1
  71. package/dist/plugins/scim/scim.options.js +1 -0
  72. package/dist/plugins/scim/users.js +1 -1
  73. package/dist/plugins/static.js +1 -1
  74. package/dist/plugins/static.options.js +1 -0
  75. package/dist/plugins/storage/file.js +1 -1
  76. package/dist/plugins/storage/ldap.js +1 -1
  77. package/dist/plugins/storage/postgres.js +1 -1
  78. package/dist/plugins/storage/valkey.js +1 -1
  79. package/dist/plugins/storage.js +1 -1
  80. package/dist/plugins/storage.options.js +1 -0
  81. package/dist/plugins/twake/appAccounts.options.js +1 -0
  82. package/dist/plugins/twake/appAccountsApi.js +1 -1
  83. package/dist/plugins/twake/appAccountsConsistency.js +1 -1
  84. package/dist/plugins/twake/calendar.js +1 -1
  85. package/dist/plugins/twake/calendar.options.js +1 -0
  86. package/dist/plugins/twake/calendarResources.js +1 -1
  87. package/dist/plugins/twake/cloudery.options.js +1 -0
  88. package/dist/plugins/twake/clouderyProvision.js +1 -1
  89. package/dist/plugins/twake/cozy.options.js +1 -0
  90. package/dist/plugins/twake/cozyProvision.js +1 -1
  91. package/dist/plugins/twake/drive.js +1 -1
  92. package/dist/plugins/twake/drive.options.js +1 -0
  93. package/dist/plugins/twake/groups.js +1 -1
  94. package/dist/plugins/twake/instances.js +1 -1
  95. package/dist/plugins/twake/james.js +1 -1
  96. package/dist/plugins/twake/james.options.js +1 -0
  97. package/dist/plugins/twake/lifecycleAttributes.js +1 -1
  98. package/dist/plugins/twake/lifecycleEvents.js +1 -1
  99. package/dist/plugins/twake/migrationProxy.js +1 -1
  100. package/dist/plugins/twake/migrationProxy.options.js +1 -0
  101. package/dist/plugins/twake/spaces.js +1 -1
  102. package/dist/plugins/twake/tombstone.js +1 -1
  103. package/dist/plugins/twake/twakeGroups.options.js +1 -0
  104. package/dist/plugins/twake/twakeInstances.options.js +1 -0
  105. package/dist/plugins/twake/twakeLifecycle.options.js +1 -0
  106. package/dist/plugins/twake/twakeSpaces.options.js +1 -0
  107. package/dist/plugins/twake/twakeTombstone.options.js +1 -0
  108. package/dist/plugins/weblogs.js +1 -1
  109. package/dist/src/abstract/ldapFlat.d.ts +28 -10
  110. package/dist/src/abstract/plugin.d.ts +10 -5
  111. package/dist/src/abstract/twakePlugin.d.ts +14 -4
  112. package/dist/src/bin/index.d.ts +17 -4
  113. package/dist/src/config/args.d.ts +2 -4
  114. package/dist/src/config/options.d.ts +89 -0
  115. package/dist/src/config/schema.d.ts +50 -1
  116. package/dist/src/hooks.d.ts +60 -22
  117. package/dist/src/lib/authz/base.d.ts +4 -51
  118. package/dist/src/lib/authz/composition.d.ts +2 -2
  119. package/dist/src/lib/entitySchema/validator.d.ts +63 -6
  120. package/dist/src/lib/errors.d.ts +56 -1
  121. package/dist/src/lib/expressFormatedResponses.d.ts +49 -7
  122. package/dist/src/lib/hookBus.d.ts +226 -0
  123. package/dist/src/lib/instanceProviders.d.ts +0 -8
  124. package/dist/src/lib/ldapActions.d.ts +7 -0
  125. package/dist/src/lib/parseConfig.d.ts +5 -0
  126. package/dist/src/lib/route.d.ts +99 -0
  127. package/dist/src/lib/utils.d.ts +31 -36
  128. package/dist/src/lib/version.d.ts +12 -0
  129. package/dist/src/plugins/auth/auth.options.d.ts +88 -0
  130. package/dist/src/plugins/auth/authzDynamic.d.ts +10 -1
  131. package/dist/src/plugins/auth/authzLinid1.d.ts +6 -0
  132. package/dist/src/plugins/auth/authzPerBranch.d.ts +9 -0
  133. package/dist/src/plugins/auth/authzPerRoute.d.ts +3 -0
  134. package/dist/src/plugins/auth/authzScope.d.ts +23 -14
  135. package/dist/src/plugins/auth/crowdsec.d.ts +5 -0
  136. package/dist/src/plugins/auth/fake.d.ts +3 -0
  137. package/dist/src/plugins/auth/hmac.d.ts +4 -0
  138. package/dist/src/plugins/auth/llng.d.ts +4 -0
  139. package/dist/src/plugins/auth/openidconnect.d.ts +7 -0
  140. package/dist/src/plugins/auth/rateLimit.d.ts +4 -0
  141. package/dist/src/plugins/auth/token.d.ts +3 -0
  142. package/dist/src/plugins/auth/totp.d.ts +5 -0
  143. package/dist/src/plugins/auth/trustedProxy.d.ts +4 -0
  144. package/dist/src/plugins/bcl.d.ts +3 -0
  145. package/dist/src/plugins/bcl.options.d.ts +8 -0
  146. package/dist/src/plugins/configApi.d.ts +24 -5
  147. package/dist/src/plugins/demo/helloworld.d.ts +8 -19
  148. package/dist/src/plugins/ldap/accountLifecycle.d.ts +17 -0
  149. package/dist/src/plugins/ldap/bulkImport.d.ts +6 -76
  150. package/dist/src/plugins/ldap/departmentSync.d.ts +1 -0
  151. package/dist/src/plugins/ldap/enterpriseRules.d.ts +8 -1
  152. package/dist/src/plugins/ldap/externalUsersInGroups.d.ts +4 -0
  153. package/dist/src/plugins/ldap/flatGeneric.d.ts +4 -0
  154. package/dist/src/plugins/ldap/groups.d.ts +27 -11
  155. package/dist/src/plugins/ldap/ldap.options.d.ts +75 -0
  156. package/dist/src/plugins/ldap/onChange.d.ts +10 -2
  157. package/dist/src/plugins/ldap/organizations.d.ts +16 -0
  158. package/dist/src/plugins/ldap/passwordPolicy.d.ts +32 -1
  159. package/dist/src/plugins/ldap/raw.d.ts +50 -19
  160. package/dist/src/plugins/ldap/trash.d.ts +6 -0
  161. package/dist/src/plugins/metrics.d.ts +3 -0
  162. package/dist/src/plugins/metrics.options.d.ts +8 -0
  163. package/dist/src/plugins/rabbitmq.d.ts +3 -0
  164. package/dist/src/plugins/rabbitmq.options.d.ts +8 -0
  165. package/dist/src/plugins/scim/baseResolver.d.ts +2 -1
  166. package/dist/src/plugins/scim/bulk.d.ts +6 -7
  167. package/dist/src/plugins/scim/discovery.d.ts +3 -0
  168. package/dist/src/plugins/scim/errors.d.ts +2 -1
  169. package/dist/src/plugins/scim/groups.d.ts +6 -4
  170. package/dist/src/plugins/scim/scim.d.ts +37 -3
  171. package/dist/src/plugins/scim/scim.options.d.ts +35 -0
  172. package/dist/src/plugins/scim/users.d.ts +6 -7
  173. package/dist/src/plugins/static.d.ts +9 -0
  174. package/dist/src/plugins/static.options.d.ts +13 -0
  175. package/dist/src/plugins/storage.d.ts +12 -1
  176. package/dist/src/plugins/storage.options.d.ts +16 -0
  177. package/dist/src/plugins/twake/appAccounts.options.d.ts +13 -0
  178. package/dist/src/plugins/twake/appAccountsApi.d.ts +18 -0
  179. package/dist/src/plugins/twake/appAccountsConsistency.d.ts +8 -0
  180. package/dist/src/plugins/twake/calendar.d.ts +25 -2
  181. package/dist/src/plugins/twake/calendar.options.d.ts +19 -0
  182. package/dist/src/plugins/twake/cloudery.options.d.ts +26 -0
  183. package/dist/src/plugins/twake/clouderyProvision.d.ts +18 -0
  184. package/dist/src/plugins/twake/cozy.options.d.ts +23 -0
  185. package/dist/src/plugins/twake/cozyProvision.d.ts +14 -0
  186. package/dist/src/plugins/twake/drive.d.ts +7 -0
  187. package/dist/src/plugins/twake/drive.options.d.ts +15 -0
  188. package/dist/src/plugins/twake/groups.d.ts +29 -1
  189. package/dist/src/plugins/twake/instances.d.ts +43 -10
  190. package/dist/src/plugins/twake/james.d.ts +17 -0
  191. package/dist/src/plugins/twake/james.options.d.ts +21 -0
  192. package/dist/src/plugins/twake/lifecycleEvents.d.ts +12 -0
  193. package/dist/src/plugins/twake/migrationProxy.d.ts +11 -11
  194. package/dist/src/plugins/twake/migrationProxy.options.d.ts +16 -0
  195. package/dist/src/plugins/twake/spaces.d.ts +30 -0
  196. package/dist/src/plugins/twake/tombstone.d.ts +16 -14
  197. package/dist/src/plugins/twake/twakeGroups.options.d.ts +20 -0
  198. package/dist/src/plugins/twake/twakeInstances.options.d.ts +43 -0
  199. package/dist/src/plugins/twake/twakeLifecycle.options.d.ts +21 -0
  200. package/dist/src/plugins/twake/twakeSpaces.options.d.ts +19 -0
  201. package/dist/src/plugins/twake/twakeTombstone.options.d.ts +17 -0
  202. package/docs/README.md +1 -0
  203. package/docs/client-development/api/rest-api.md +9 -3
  204. package/docs/client-development/schemas/README.md +96 -32
  205. package/docs/plugin-development/README.md +45 -94
  206. package/docs/plugin-development/dependencies.md +11 -6
  207. package/docs/plugin-development/hooks.md +142 -23
  208. package/docs/plugin-development/openapi.md +14 -1
  209. package/docs/plugin-development/options.md +109 -0
  210. package/docs/plugin-development/routes.md +152 -0
  211. package/docs/plugin-development/testing.md +3 -3
  212. package/docs/usage/configuration.md +18 -11
  213. package/docs/usage/operations.md +19 -4
  214. package/docs/usage/plugins/ldap/enterprise-rules.md +10 -1
  215. package/docs/usage/plugins/ldap/flat-generic.md +4 -0
  216. package/docs/usage/plugins/ldap/raw.md +17 -1
  217. package/docs/usage/upgrading.md +194 -0
  218. package/package.json +358 -277
  219. package/static/browser/ldap-browser/LdapBrowser.js +8 -6
  220. package/static/browser/ldap-browser/LdapBrowser.js.map +1 -1
  221. package/static/browser/ldap-browser/api/RawApiClient.js +2 -0
  222. package/static/browser/ldap-browser/api/RawApiClient.js.map +1 -1
  223. package/static/browser/ldap-browser/components/EntryTree.js +9 -5
  224. package/static/browser/ldap-browser/components/EntryTree.js.map +1 -1
  225. package/static/browser/ldap-browser/components/EntryView.js +2 -0
  226. package/static/browser/ldap-browser/components/EntryView.js.map +1 -1
  227. package/static/browser/ldap-browser/schema.js +3 -2
  228. package/static/browser/ldap-browser/schema.js.map +1 -1
  229. package/static/browser/ldap-browser.esm.js +27 -18
  230. package/static/browser/ldap-browser.esm.js.map +1 -1
  231. package/static/browser/ldap-browser.js +27 -18
  232. package/static/browser/ldap-browser.js.map +1 -1
  233. package/static/browser/ldap-browser.min.js +1 -1
  234. package/static/browser/ldap-browser.min.js.map +1 -1
  235. package/static/browser/ldap-group-editor/LdapGroupEditor.js +8 -6
  236. package/static/browser/ldap-group-editor/LdapGroupEditor.js.map +1 -1
  237. package/static/browser/ldap-group-editor/api/GroupApiClient.js +1 -0
  238. package/static/browser/ldap-group-editor/api/GroupApiClient.js.map +1 -1
  239. package/static/browser/ldap-group-editor/components/CreateGroupModal.js +5 -1
  240. package/static/browser/ldap-group-editor/components/CreateGroupModal.js.map +1 -1
  241. package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js +6 -3
  242. package/static/browser/ldap-group-editor/components/GroupPropertyEditor.js.map +1 -1
  243. package/static/browser/ldap-group-editor/components/GroupTree.js +6 -3
  244. package/static/browser/ldap-group-editor/components/GroupTree.js.map +1 -1
  245. package/static/browser/ldap-group-editor/components/MoveGroupModal.js +6 -3
  246. package/static/browser/ldap-group-editor/components/MoveGroupModal.js.map +1 -1
  247. package/static/browser/ldap-group-editor.esm.js +32 -16
  248. package/static/browser/ldap-group-editor.esm.js.map +1 -1
  249. package/static/browser/ldap-group-editor.js +32 -16
  250. package/static/browser/ldap-group-editor.js.map +1 -1
  251. package/static/browser/ldap-group-editor.min.js +1 -1
  252. package/static/browser/ldap-group-editor.min.js.map +1 -1
  253. package/static/browser/ldap-resource-editor/api/ResourceApiClient.js +3 -0
  254. package/static/browser/ldap-resource-editor/api/ResourceApiClient.js.map +1 -1
  255. package/static/browser/ldap-resource-editor/cache/CacheManager.js +3 -1
  256. package/static/browser/ldap-resource-editor/cache/CacheManager.js.map +1 -1
  257. package/static/browser/ldap-tree-viewer/LdapTreeViewer.js +6 -1
  258. package/static/browser/ldap-tree-viewer/LdapTreeViewer.js.map +1 -1
  259. package/static/browser/ldap-tree-viewer/api/LdapApiClient.js +2 -0
  260. package/static/browser/ldap-tree-viewer/api/LdapApiClient.js.map +1 -1
  261. package/static/browser/ldap-tree-viewer/components/TreeNode.js +4 -0
  262. package/static/browser/ldap-tree-viewer/components/TreeNode.js.map +1 -1
  263. package/static/browser/ldap-tree-viewer/components/TreeRoot.js +3 -0
  264. package/static/browser/ldap-tree-viewer/components/TreeRoot.js.map +1 -1
  265. package/static/browser/ldap-tree-viewer/store/Store.js +3 -1
  266. package/static/browser/ldap-tree-viewer/store/Store.js.map +1 -1
  267. package/static/browser/ldap-tree-viewer.esm.js +18 -2
  268. package/static/browser/ldap-tree-viewer.esm.js.map +1 -1
  269. package/static/browser/ldap-tree-viewer.js +18 -2
  270. package/static/browser/ldap-tree-viewer.js.map +1 -1
  271. package/static/browser/ldap-tree-viewer.min.js +1 -1
  272. package/static/browser/ldap-tree-viewer.min.js.map +1 -1
  273. package/static/browser/ldap-unit-editor/LdapUnitEditor.js +7 -5
  274. package/static/browser/ldap-unit-editor/LdapUnitEditor.js.map +1 -1
  275. package/static/browser/ldap-unit-editor/api/UnitApiClient.js +1 -0
  276. package/static/browser/ldap-unit-editor/api/UnitApiClient.js.map +1 -1
  277. package/static/browser/ldap-unit-editor/components/MoveUnitModal.js +7 -4
  278. package/static/browser/ldap-unit-editor/components/MoveUnitModal.js.map +1 -1
  279. package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js +7 -4
  280. package/static/browser/ldap-unit-editor/components/UnitPropertyEditor.js.map +1 -1
  281. package/static/browser/ldap-unit-editor/components/UnitTree.js +6 -3
  282. package/static/browser/ldap-unit-editor/components/UnitTree.js.map +1 -1
  283. package/static/browser/ldap-unit-editor.esm.js +28 -16
  284. package/static/browser/ldap-unit-editor.esm.js.map +1 -1
  285. package/static/browser/ldap-unit-editor.js +28 -16
  286. package/static/browser/ldap-unit-editor.js.map +1 -1
  287. package/static/browser/ldap-unit-editor.min.js +1 -1
  288. package/static/browser/ldap-unit-editor.min.js.map +1 -1
  289. package/static/browser/ldap-user-editor/LdapUserEditor.js +9 -7
  290. package/static/browser/ldap-user-editor/LdapUserEditor.js.map +1 -1
  291. package/static/browser/ldap-user-editor/api/UserApiClient.js +2 -0
  292. package/static/browser/ldap-user-editor/api/UserApiClient.js.map +1 -1
  293. package/static/browser/ldap-user-editor/cache/CacheManager.js +3 -1
  294. package/static/browser/ldap-user-editor/cache/CacheManager.js.map +1 -1
  295. package/static/browser/ldap-user-editor/components/MoveUserModal.js +7 -4
  296. package/static/browser/ldap-user-editor/components/MoveUserModal.js.map +1 -1
  297. package/static/browser/ldap-user-editor/components/PointerField.js +6 -1
  298. package/static/browser/ldap-user-editor/components/PointerField.js.map +1 -1
  299. package/static/browser/ldap-user-editor/components/UserEditor.js +10 -5
  300. package/static/browser/ldap-user-editor/components/UserEditor.js.map +1 -1
  301. package/static/browser/ldap-user-editor/components/UserList.js +8 -4
  302. package/static/browser/ldap-user-editor/components/UserList.js.map +1 -1
  303. package/static/browser/ldap-user-editor/components/UserTree.js +7 -3
  304. package/static/browser/ldap-user-editor/components/UserTree.js.map +1 -1
  305. package/static/browser/ldap-user-editor.esm.js +55 -30
  306. package/static/browser/ldap-user-editor.esm.js.map +1 -1
  307. package/static/browser/ldap-user-editor.js +55 -30
  308. package/static/browser/ldap-user-editor.js.map +1 -1
  309. package/static/browser/ldap-user-editor.min.js +1 -1
  310. package/static/browser/ldap-user-editor.min.js.map +1 -1
  311. package/static/browser/shared/components/DisposableComponent.js +3 -5
  312. package/static/browser/shared/components/DisposableComponent.js.map +1 -1
  313. package/static/browser/shared/components/Modal.js +2 -1
  314. package/static/browser/shared/components/Modal.js.map +1 -1
  315. package/static/browser/shared/components/StatusMessage.js +3 -2
  316. package/static/browser/shared/components/StatusMessage.js.map +1 -1
  317. package/static/browser/shared/utils/form.js.map +1 -1
  318. package/static/browser/shared/utils/hmac.js +1 -0
  319. package/static/browser/shared/utils/hmac.js.map +1 -1
  320. package/static/browser/shared/utils/totp.js +1 -0
  321. package/static/browser/shared/utils/totp.js.map +1 -1
  322. package/static/browser/shared.esm.js +9 -8
  323. package/static/browser/shared.esm.js.map +1 -1
  324. package/static/browser/shared.js +9 -8
  325. package/static/browser/shared.js.map +1 -1
  326. package/static/browser/shared.min.js +1 -1
  327. package/static/browser/shared.min.js.map +1 -1
  328. package/static/schemas/ad/groups.json +1 -1
  329. package/static/schemas/ad/users.json +3 -3
  330. package/static/schemas/example/organizations.json +2 -2
  331. package/static/schemas/example/users.json +2 -2
  332. package/static/schemas/obm/users.json +2 -2
  333. package/static/schemas/standard/groups.json +1 -1
  334. package/static/schemas/standard/users.json +2 -2
  335. package/static/schemas/twake/groups.json +4 -4
  336. package/static/schemas/twake/teamMailboxes.json +3 -3
  337. package/static/schemas/twake/users.json +7 -7
@@ -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.
@@ -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
@@ -149,6 +149,11 @@ of the environment variable. An empty value on the command line, such as an
149
149
  unset variable expanding to nothing, stops the server: leave the option out
150
150
  to keep the default.
151
151
 
152
+ An option or `DM_*` variable that no declared option reads is named in a
153
+ warning at startup. `--strict-options` refuses it instead. Kubernetes injects
154
+ `DM_*` variables for services whose name starts with `dm`; leave the flag off
155
+ where those names are in use.
156
+
152
157
  ## General Options
153
158
 
154
159
  | CLI | Plural | Env | Default | Description |
@@ -160,6 +165,7 @@ to keep the default.
160
165
  | `--logger` | | `DM_LOGGER` | `console` | `console` or `file`, see [Logger](#logger---logger) |
161
166
  | `--log-file` | | `DM_LOG_FILE` | `error.log` | File `--logger file` writes to |
162
167
  | `--debug` | | `DM_DEBUG` | `false` | Name the failure in the 5xx answers of the error middleware and `serverError`; not for production |
168
+ | `--strict-options` | | `DM_STRICT_OPTIONS` | `false` | Refuse an option or `DM_*` variable no declared option reads |
163
169
  | `--api-prefix` | | `DM_API_PREFIX` | `/api` | API URL prefix |
164
170
  | `--mail-domain` | `--mail-domains` | `DM_MAIL_DOMAIN` | `[]` | Mail domains |
165
171
  | `--schemas-path` | | `DM_SCHEMAS_PATH` | `static/schemas` | Path to JSON schemas |
@@ -229,15 +235,15 @@ at `0` otherwise.
229
235
 
230
236
  #### `core/ldap/groups`
231
237
 
232
- | CLI | Plural | Env | Default | Description |
233
- | ---------------------------------- | ----------------- | ------------------------------- | ---------------------------------- | --------------------------- |
234
- | `--ldap-group-base` | | `DM_LDAP_GROUP_BASE` | | Groups base DN |
235
- | `--ldap-groups-main-attribute` | | `DM_LDAP_GROUPS_MAIN_ATTRIBUTE` | `cn` | Group identifier attribute |
236
- | `--group-class` | `--group-classes` | `DM_GROUP_CLASSES` | `top,groupOfNames` | Group objectClasses |
237
- | `--group-allow-unexistent-members` | | `DM_ALLOW_UNEXISTENT_MEMBERS` | `false` | Allow non-existent members |
238
- | `--group-default-attributes` | | `DM_GROUP_DEFAULT_ATTRIBUTES` | `{}` | Default attributes (JSON) |
239
- | `--group-dummy-user` | | `DM_GROUP_DUMMY_USER` | `cn=fakeuser` | Dummy user for empty groups |
240
- | `--group-schema` | | `DM_GROUP_SCHEMA` | `static/schemas/twake/groups.json` | Group JSON schema path |
238
+ | CLI | Plural | Env | Default | Description |
239
+ | ----------------------------------- | ----------------- | ------------------------------- | ---------------------------------- | --------------------------- |
240
+ | `--ldap-group-base` | | `DM_LDAP_GROUP_BASE` | | Groups base DN |
241
+ | `--ldap-groups-main-attribute` | | `DM_LDAP_GROUPS_MAIN_ATTRIBUTE` | `cn` | Group identifier attribute |
242
+ | `--group-class` | `--group-classes` | `DM_GROUP_CLASSES` | `top,groupOfNames` | Group objectClasses |
243
+ | `--groups-allow-unexistent-members` | | `DM_ALLOW_UNEXISTENT_MEMBERS` | `false` | Allow non-existent members |
244
+ | `--group-default-attributes` | | `DM_GROUP_DEFAULT_ATTRIBUTES` | `{}` | Default attributes (JSON) |
245
+ | `--group-dummy-user` | | `DM_GROUP_DUMMY_USER` | `cn=fakeuser` | Dummy user for empty groups |
246
+ | `--group-schema` | | `DM_GROUP_SCHEMA` | `static/schemas/twake/groups.json` | Group JSON schema path |
241
247
 
242
248
  #### `core/ldap/externalUsersInGroups`
243
249
 
@@ -429,6 +435,7 @@ whichever of the two names it selects. A value other than `req.user` or
429
435
  | ---------------------------------- | ----------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
430
436
  | `--authz-per-branch-config` | `DM_AUTHZ_PER_BRANCH_CONFIG` | `{default:{read:true,write:false,delete:false}}` | Authorization config (JSON) |
431
437
  | `--authz-per-branch-cache-ttl` | `DM_AUTHZ_PER_BRANCH_CACHE_TTL` | `60` | Cache TTL (seconds) |
438
+ | `--ldap-group-member-attribute` | `DM_LDAP_GROUP_MEMBER_ATTRIBUTE` | `member` | Attribute a group lists its members in |
432
439
  | `--authz-filter-attached-entries` | `DM_AUTHZ_FILTER_ATTACHED_ENTRIES` | `false` | Judge an attached entry by the organization it hangs off, and filter listings accordingly. Honoured by `authzPerBranch`; always on under `authzLinid1`; does nothing under `authzDynamic`. |
433
440
  | `--authz-transit-branch` | `DM_AUTHZ_TRANSIT_BRANCH` | | Organization entries are handed over through when they are judged by attachment: every administrator sees and may claim what is there, and puts an entry there with write on the organization it leaves. An entry attached to no organization is in transit too. See [authzLinid1](plugins/auth/authz-linid1.md#transit-handing-an-entry-over). |
434
441
  | `--authz-transit-origin-attribute` | `DM_AUTHZ_TRANSIT_ORIGIN_ATTRIBUTE` | | Attribute an entry put in transit keeps the organization it leaves in, removed when it is claimed. Empty records nothing; it must exist in the directory's schema. See [authzLinid1](plugins/auth/authz-linid1.md#where-an-entry-comes-from). |
@@ -523,7 +530,7 @@ Formerly `core/twake/calendarResources`, still accepted as a deprecated alias.
523
530
  | `--calendar-concurrency` | `DM_CALENDAR_CONCURRENCY` | `10` | API concurrency |
524
531
  | `--calendar-resource-base` | `DM_CALENDAR_RESOURCE_BASE` | | Resource base DN |
525
532
  | `--calendar-resource-objectclass` | `DM_CALENDAR_RESOURCE_OBJECTCLASS` | | Resource objectClass |
526
- | `--calendar-resource-creator` | `DM_CALENDAR_RESOURCE_CREATOR` | | Resource creator |
533
+ | `--calendar-resource-creator` | `DM_CALENDAR_RESOURCE_CREATOR` | `admin@example.com` | Resource creator |
527
534
  | `--calendar-resource-domain` | `DM_CALENDAR_RESOURCE_DOMAIN` | | Resource domain |
528
535
  | `--calendar-firstname-attribute` | `DM_CALENDAR_FIRSTNAME_ATTRIBUTE` | `givenName` | Registered user first name |
529
536
  | `--calendar-lastname-attribute` | `DM_CALENDAR_LASTNAME_ATTRIBUTE` | `sn` | Registered user last name |
@@ -722,7 +729,7 @@ see [Storage](plugins/utilities/storage.md).
722
729
 
723
730
  #### `core/configApi`
724
731
 
725
- Exposes API configuration at `/api/v1/config` for client applications. Returns available features, schemas, and endpoints. No configuration options.
732
+ Exposes API configuration at `/api/v1/config` for client applications. Returns available features, schemas, endpoints, and the server's `version` — read from the `package.json` that ships with it, so a client can tell an administrator the server is older than it supports. No configuration options.
726
733
 
727
734
  ## Configuration File
728
735
 
@@ -41,7 +41,17 @@ and `/readyz` names the loaded plugins that report: on an ingress exposing
41
41
  the server to the internet, keep the two paths to the cluster.
42
42
 
43
43
  The Helm chart probes both routes over HTTP (`livenessProbe` and
44
- `readinessProbe` in `values.yaml`).
44
+ `readinessProbe` in `values.yaml`). The published image also declares a
45
+ `HEALTHCHECK` calling `GET /healthz` from inside the container, on `DM_PORT`:
46
+ `docker ps` reports the container unhealthy when it stops answering, and a
47
+ `--restart` policy restarts it as it would after a crash, while an
48
+ orchestrator with probes of its own ignores the declaration. A port given
49
+ any other way (`--port` in a configuration file, a plugin) leaves the check
50
+ on 8081.
51
+
52
+ The image runs as its unprivileged `node` user: with `docker run`, a
53
+ `DM_PORT` below 1024 stops the server at startup, where the default 8081
54
+ changes nothing.
45
55
 
46
56
  ### What the shipped plugins report
47
57
 
@@ -110,6 +120,7 @@ the rate limiting and the request id, to keep to the cluster or the host.
110
120
  | `ldap_rest_ldap_pool_wait_seconds` | histogram | | Time to get a pooled connection, opening one included |
111
121
  | `ldap_rest_ldap_pool_connections` | gauge | `state` | Pooled connections, `busy` or `idle` |
112
122
  | `ldap_rest_ldap_pool_waiting` | gauge | | Operations waiting for a connection, the pool being full |
123
+ | `ldap_rest_ldap_pool_opening` | gauge | | Connections being opened, within `--ldap-pool-size` |
113
124
  | `ldap_rest_ldap_pool_max` | gauge | | `--ldap-pool-size` |
114
125
  | `ldap_rest_ldap_search_cache_total` | counter | `result` | Cacheable searches, `hit` or `miss`; nothing when the cache is off |
115
126
  | `ldap_rest_hook_failures_total` | counter | `hook`, `plugin` | Notification hooks (`*done`, `onLdap*`) that threw, which the server logs and goes on |
@@ -117,6 +128,10 @@ the rate limiting and the request id, to keep to the cluster or the host.
117
128
  | `ldap_rest_unhandled_rejections_total` | counter | | Promise rejections nothing handled; counted by the binary, a host skipping `exitOnCrash()` leaves it at zero |
118
129
  | `process_*`, `nodejs_*` | | | The process: CPU, memory, event loop lag, handles, GC |
119
130
 
131
+ A burst of requests that has to open connections no longer opens one per
132
+ request: a connection being opened takes its place in the pool, and the
133
+ requests over `--ldap-pool-size` wait for a free one.
134
+
120
135
  A search the caller pages is timed page by page, what the caller does
121
136
  between two pages left out; the readiness ping is not timed. The route
122
137
  label is the template a route was declared with, never the URL: a label
@@ -128,9 +143,9 @@ James's does, is a call like another. A plugin calling a service through
128
143
  `this.fetch` has its calls counted under its name; the OpenID Connect
129
144
  client's calls to its provider are made by its library, and not timed.
130
145
 
131
- The instruments come from `prom-client` 15, kept while the server supports
132
- Node 20, which its successor `@prometheus-io/client` dropped: the
133
- deprecation notice `npm install` prints is expected.
146
+ The instruments come from `prom-client` 15, which `npm install` reports as
147
+ deprecated: its successor `@prometheus-io/client` is the migration to make,
148
+ and the notice is expected until it is done.
134
149
 
135
150
  The port listens on every interface. What to alert on: `/readyz` for a
136
151
  replica out of rotation, `result="error"` in
@@ -119,7 +119,10 @@ whenever the link changes. Mark the path `generated` so a client cannot set it.
119
119
 
120
120
  `5GB`, `500 MB`, `2KB` and `2048` all become a number of bytes, with decimal
121
121
  multipliers (1 GB = 10⁹ bytes) as mail servers count them. The same conversion
122
- applies to creations and to updates.
122
+ applies to creations and to updates. A size above 2⁵³ − 1 bytes is refused
123
+ (400), since its number of bytes would be rounded. A schema `number` or
124
+ `integer` refuses a whole number beyond 2⁵³ on its own, whatever the type;
125
+ see [what a value must look like](../../../client-development/schemas/README.md#what-a-value-must-look-like).
123
126
 
124
127
  ### Server-owned defaults
125
128
 
@@ -144,6 +147,12 @@ An `accountExpiry` attribute must not carry a date already past. The comparison
144
147
  is against the start of the current day, so scheduling something for today is
145
148
  allowed.
146
149
 
150
+ The date is an LDAP generalized time, read as written: the year keeps its
151
+ four digits (`0029123123` is a date of year 29, not 1929), and a leap second
152
+ (`20991231235960Z`) is the instant that follows the day. The zone is `Z` or a
153
+ difference of hours and minutes from UTC (`+00`, `+02`, `-0530`, `+14:00`); a
154
+ value past 23 hours or 59 minutes is answered 400 as invalid.
155
+
147
156
  ### Deletion and references
148
157
 
149
158
  ```json
@@ -451,6 +451,10 @@ Content-Type: application/json
451
451
  }
452
452
  ```
453
453
 
454
+ The identifier may also be the whole DN of the entry, in the entity's
455
+ branch: `"uid": "uid=Smith\\,John,ou=users,dc=example,dc=com"` creates the
456
+ entry `Smith,John`, whose DN spells the name with its escapes.
457
+
454
458
  ### Modify Entry
455
459
 
456
460
  ```bash
@@ -181,6 +181,22 @@ Site-specific attributes are added with `--ldap-raw-hidden-attribute`, which sta
181
181
  --ldap-raw-hidden-attribute twakeApiKey --ldap-raw-hidden-attribute employeeNumber
182
182
  ```
183
183
 
184
+ A hidden attribute is resolved to its directory type, so the type's OID and
185
+ every alias it declares are hidden too — asking for `2.5.4.35` or
186
+ `homePhone` does not serve `userPassword` or a configured
187
+ `homeTelephoneNumber`. A search filter that tests a hidden type, at any
188
+ depth and under any of its names, is refused with a 400: the search runs with
189
+ the service account's rights, so leaving it through would answer the same
190
+ question one match at a time. An assertion on a supertype of a hidden type is
191
+ refused as well, since the directory evaluates an attribute's subtypes. An
192
+ extensible match without an attribute (`(:octetStringMatch:=…)`) runs over
193
+ every attribute and is refused too while attributes are hidden.
194
+
195
+ When the directory schema cannot be read, an entry read or a search is
196
+ refused with a 503 rather than answered with only the configured names
197
+ compared, which could serve an alias or an OID. The root DSE still answers:
198
+ it carries no entry values and is how a client discovers the schema.
199
+
184
200
  Note that hiding an attribute here keeps it out of the API, not out of the directory: an account able to bind to LDAP directly still reads it. This is a guard against casual exposure through the browsing UI, not an access control — use [authz-per-branch](../auth/authz-per-branch.md) or directory ACLs for that.
185
201
 
186
202
  ## Security
@@ -189,7 +205,7 @@ Access is restricted in three independent layers:
189
205
 
190
206
  1. **Exposed bases** — any DN outside `--ldap-raw-base` is refused with a 403, whatever the caller's rights.
191
207
  2. **Authorization hooks** — every entry read, children listing and search goes through `server.ldap`, so the authorization plugins ([authz-per-branch](../auth/authz-per-branch.md), authz-dynamic, …) apply exactly as on the high-level APIs.
192
- 3. **Hidden attributes** — credential attributes, plus anything in `--ldap-raw-hidden-attribute`, are removed from every response, including searches. See [Hidden attributes](#hidden-attributes).
208
+ 3. **Hidden attributes** — credential attributes, plus anything in `--ldap-raw-hidden-attribute`, are removed from every response, including searches, under every name their type answers to; a filter that tests one is refused with a 400. See [Hidden attributes](#hidden-attributes).
193
209
 
194
210
  Two exceptions, both deliberate: the **root DSE** and the **schema** are read with the service account, not with the caller's rights. They are directory metadata and carry no user data; reading them with a restricted account would leave the UI unable to name and type attributes. Do not expose this plugin publicly if your directory publishes sensitive information in its root DSE.
195
211
 
@@ -4,6 +4,200 @@ What to check before deploying, newest first. Only releases that need a
4
4
  decision or a configuration change appear here; see the
5
5
  [CHANGELOG](../../CHANGELOG.md) for everything else.
6
6
 
7
+ ## From 1.2.1
8
+
9
+ ### A plugin written against the old hooks API
10
+
11
+ **Who is affected:** a deployment loading a plugin built outside this
12
+ repository that calls `launchHooks` / `launchHooksChained`, or subscribes
13
+ to a `*done` hook with its arguments packed in one tuple.
14
+
15
+ Hooks are raised through `this.hookBus`, and every hook is declared in
16
+ `src/hooks.ts`: a plugin raising or subscribing to a hook the server does
17
+ not declare no longer compiles. A notified hook — `ldapadddone`,
18
+ `ldapaddafter`, `ldapmodifydone`, `ldaprenamedone`, `ldapgroupadddone`,
19
+ `ldapgroupmodifydone`, `ldapgrouprenamedone`, `ldapexternaluseradded` — now
20
+ gets its arguments one by one: `(dn, entry, context) => …`, not
21
+ `([dn, entry], context) => …`. The packed form still compiles — parameters
22
+ are bivariant — and then reads the DN's first character, so a plugin left
23
+ as it was stops reacting without saying so. `ldapgroupdeletemember` threads
24
+ `[cn, member]`, a single member DN, as it always handed one.
25
+
26
+ A test waiting for a hook to land waits on `await server.hookBus.settled()`
27
+ rather than sleeping. See [hooks](../plugin-development/hooks.md#the-bus).
28
+
29
+ ### A plugin with its own `options` or `optionDefinitions` member
30
+
31
+ **Who is affected:** a plugin built outside this repository that declares a
32
+ field, getter or method named `options`, or a static field named
33
+ `optionDefinitions`.
34
+
35
+ `DmPlugin` now has `options()`, which returns the values a plugin declared
36
+ through `optionDefinitions`. Rename the plugin's own `options` member; a
37
+ plugin that does not declare options keeps reading `this.config` as before.
38
+
39
+ ### A plugin extending `TwakePlugin`
40
+
41
+ **Who is affected:** a plugin built outside this repository whose class extends
42
+ `TwakePlugin`.
43
+
44
+ The base constructor takes the WebAdmin URL, token and concurrency instead of
45
+ the names of the options holding them:
46
+
47
+ ```ts
48
+ import { jamesOptionsOf } from 'ldap-rest/plugin-twake-james.options';
49
+
50
+ const options = jamesOptionsOf(server.config);
51
+ super(server, {
52
+ url: options.james_webadmin_url,
53
+ token: options.james_webadmin_token,
54
+ concurrency: options.james_concurrency,
55
+ urlOption: 'james_webadmin_url',
56
+ tokenOption: 'james_webadmin_token',
57
+ concurrencyOption: 'james_concurrency',
58
+ });
59
+ ```
60
+
61
+ The three `*Option` fields only name the option in the startup error and the
62
+ missing-token warning. `concurrency` must be a positive integer; the base
63
+ refuses anything else, naming `concurrencyOption`.
64
+
65
+ ### A plugin building SCIM's exported helpers
66
+
67
+ **Who is affected:** a plugin built outside this repository that constructs
68
+ `ScimUsers`, `ScimGroups`, `ScimBulk`, `ScimDiscovery` or `BaseResolver`, or
69
+ calls `scimBodyParser`.
70
+
71
+ These take the SCIM options instead of a plain `Config`:
72
+
73
+ ```ts
74
+ import { scimOptionsOf } from 'ldap-rest/plugin-scim-scim.options';
75
+
76
+ const options = scimOptionsOf(config);
77
+ ```
78
+
79
+ `BaseResolver` keeps its `(config, options)` form; the others take the checked
80
+ values beside what they already require.
81
+
82
+ ### An invalid SCIM or Twake value
83
+
84
+ **Who is affected:** a deployment setting a SCIM limit to zero or a negative
85
+ number, giving a per-instance override of the wrong JSON type, or setting the
86
+ James, Calendar, Drive or migration proxy concurrency to the wrong type.
87
+
88
+ These options now refuse a value below 1 when the plugin loads: a zero used
89
+ to be silently replaced by the default, and a negative value used as it was.
90
+ Remove the option to keep the default, or give it a positive number.
91
+
92
+ A per-instance override (`--plugin core/scim:name:{...}`) must also use the
93
+ option's JSON type: `{"scim_max_results": "50"}` and `{"scim_user_base":
94
+ null}` are refused, where the quoted number used to be coerced and the null
95
+ treated as unset. The applicative accounts options refuse the same:
96
+ `{"max_app_accounts": "5"}` used to be kept as the string, and
97
+ `{"applicative_account_attribute": "cn"}` was spread into `['c', 'n']`.
98
+
99
+ The James, Calendar, Drive and migration proxy concurrency used to replace a
100
+ value that was not a number with `10`, and now refuses it. A zero or a
101
+ negative value was already refused by `p-limit`, and the message now names
102
+ the option. The other Twake options also refuse a value of the wrong JSON
103
+ type in an override.
104
+
105
+ A zero `--twake-group-max-page-limit`, `--twake-instance-timeout`,
106
+ `--cloudery-workflow-poll-interval-ms` or
107
+ `--cloudery-workflow-max-attempts` used to be replaced by the default and is
108
+ now refused.
109
+
110
+ The authentication and authorization options are checked the same way. A zero
111
+ `--rate-limit-window-ms`, `--rate-limit-max`, `--crowdsec-cache-ttl`,
112
+ `--authz-per-branch-cache-ttl` or `--authz-dynamic-cache-ttl` used to be
113
+ replaced by the default and is now refused. A zero `--auth-totp-step` or
114
+ `--auth-hmac-window` was accepted and broke the plugin — every counter was
115
+ `Infinity`, every signature not made in the same millisecond was refused — and
116
+ is now refused at startup. A zero `--ldap-organization-max-subnodes`,
117
+ `--ldap-raw-max-results`, `--ppolicy-warn-days`, `--ppolicy-min-length` or
118
+ `--bcl-retention` used to be replaced by the default and is now refused.
119
+
120
+ `ConfigParser` keeps an empty string for a string option. A plugin reads the
121
+ declared default instead when that default is not empty, as its `|| default`
122
+ used to: `DM_COZY_ADMIN_USER=` gives `admin`, `DM_CLOUDERY_OFFER=` gives
123
+ `b2b_twake_default`. An option whose empty value means something says so in
124
+ its declaration (`--cozy-apps`, `--cozy-user-deleted-routing-key`,
125
+ `--twake-instance-cozy-apps`, `--twake-instance-cozy-context`,
126
+ `--twake-instance-cloudery-offer`) and keeps it; an option whose default is
127
+ empty keeps the empty string.
128
+
129
+ ### The group member check option
130
+
131
+ **Who is affected:** a deployment passing `--group-allow-unexistent-members`.
132
+
133
+ That spelling was stored under a key the groups plugin never read, so it never
134
+ disabled member validation. The option is now `--groups-allow-unexistent-members`
135
+ and `DM_ALLOW_UNEXISTENT_MEMBERS`, which the plugin reads; the old spelling is
136
+ gone.
137
+
138
+ ### A config key a plugin did not declare
139
+
140
+ **Who is affected:** a plugin built outside this repository reading an option
141
+ through `this.config.<name>` without declaring it.
142
+
143
+ `Config` no longer has an index signature, so such a read does not compile.
144
+ Declare the option with `defineOptions` and read it through `this.options()`
145
+ (see [plugin options](../plugin-development/options.md)); the declaration also
146
+ makes the option known to the startup report and its `env` variable read.
147
+
148
+ ### Node 22 or later is required
149
+
150
+ **Who is affected:** a deployment running on Node 20 (end of life
151
+ 2026-04-30). A deployment of the published image is not: it is built on Node 24.
152
+
153
+ `engines` declares `>=22`, and the CI runs the suite on 22, 24 and 26. Node
154
+ 20 is neither tested nor supported.
155
+
156
+ ### Values past 4096 characters, and an address with a trailing or empty label
157
+
158
+ **Who is affected:** a deployment whose schemas accept a value longer than
159
+ 4096 characters, or an address such as `a@b.c.` or `a@b..c` in an attribute
160
+ whose pattern came with the shipped schemas.
161
+
162
+ A schema `test` pattern is no longer run on a value longer than 4096
163
+ characters; the value is answered 400, naming the length, before the pattern
164
+ sees it, so that a pattern cannot be made to walk several kilobytes. This
165
+ holds for every attribute in every schema which carries a pattern, a
166
+ deployment's own included, and none of the shipped schemas declares a longer
167
+ limit. An attribute holding longer values declares its own `maxLength`, a
168
+ whole number of characters, or the server refuses to load the schema:
169
+
170
+ ```json
171
+ {
172
+ "sshPublicKey": {
173
+ "type": "array",
174
+ "items": { "type": "string", "maxLength": 16384, "test": "^[^\\r\\n]*$" }
175
+ }
176
+ }
177
+ ```
178
+
179
+ The address patterns shipped with the schemas no longer accept a trailing dot
180
+ or an empty label: `a@b.c.`, `a@b..c` and `a@.b.c` are refused. Entries already holding
181
+ such a value stay as they are; the value is refused the next time that
182
+ attribute is written. A deployment that copied one of these patterns keeps
183
+ its own copy, unbounded only if it declares no `maxLength`; the patterns to
184
+ use are in the
185
+ [schema reference](../../docs/client-development/schemas/README.md#test-optional).
186
+
187
+ ### A `null` under a name the schema does not declare
188
+
189
+ **Who is affected:** a client sending `null` to an entity whose schema does
190
+ not declare the attribute, or to an entity without a schema — the
191
+ `dhcpHosts` and `dnsRecords` schemas shipped with `"strict": false` among
192
+ them.
193
+
194
+ Such a write answered 500, with nothing written. It now succeeds: the key is
195
+ left out of a creation or an `add`, and clears the attribute in a `replace`.
196
+ No action is required. A strict schema still answers 400 for such a name, and
197
+ the value is written as if the key were left out, so a client relying on the
198
+ 500 to notice a misspelt attribute no longer hears about it — see
199
+ [what a value must look like](../client-development/schemas/README.md#what-a-value-must-look-like).
200
+
7
201
  ## From 1.2.0 to 1.2.1
8
202
 
9
203
  ### `{org}` once in a Twake branch pattern