ldap-rest 0.7.0 → 0.8.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 (79) hide show
  1. package/dist/abstract/ldapFlat.js +1 -1
  2. package/dist/abstract/plugin.js +1 -1
  3. package/dist/bin/index.js +1 -1
  4. package/dist/config/args.js +1 -1
  5. package/dist/config/schema.js +1 -0
  6. package/dist/lib/expressFormatedResponses.js +1 -1
  7. package/dist/lib/ldapActions.js +1 -1
  8. package/dist/lib/utils.js +1 -1
  9. package/dist/plugins/auth/authzScope.js +1 -0
  10. package/dist/plugins/auth/llng.js +1 -1
  11. package/dist/plugins/ldap/accountLifecycle.js +1 -0
  12. package/dist/plugins/ldap/bulkImport.js +1 -1
  13. package/dist/plugins/ldap/departmentSync.js +1 -1
  14. package/dist/plugins/ldap/enterpriseRules.js +1 -0
  15. package/dist/plugins/ldap/externalUsersInGroups.js +1 -1
  16. package/dist/plugins/ldap/flatGeneric.js +1 -1
  17. package/dist/plugins/ldap/groups.js +1 -1
  18. package/dist/plugins/ldap/organizations.js +1 -1
  19. package/dist/plugins/scim/groups.js +1 -1
  20. package/dist/plugins/twake/calendar.js +1 -0
  21. package/dist/plugins/twake/calendarResources.js +1 -1
  22. package/dist/plugins/twake/james.js +1 -1
  23. package/dist/src/abstract/ldapFlat.d.ts +159 -1
  24. package/dist/src/abstract/plugin.d.ts +13 -0
  25. package/dist/src/config/args.d.ts +2 -0
  26. package/dist/src/config/schema.d.ts +382 -12
  27. package/dist/src/lib/expressFormatedResponses.d.ts +14 -0
  28. package/dist/src/lib/ldapActions.d.ts +33 -6
  29. package/dist/src/lib/utils.d.ts +65 -0
  30. package/dist/src/plugins/auth/authzScope.d.ts +53 -0
  31. package/dist/src/plugins/auth/llng.d.ts +30 -1
  32. package/dist/src/plugins/ldap/accountLifecycle.d.ts +56 -0
  33. package/dist/src/plugins/ldap/departmentSync.d.ts +43 -0
  34. package/dist/src/plugins/ldap/enterpriseRules.d.ts +436 -0
  35. package/dist/src/plugins/ldap/flatGeneric.d.ts +29 -0
  36. package/dist/src/plugins/ldap/groups.d.ts +22 -0
  37. package/dist/src/plugins/ldap/organizations.d.ts +25 -4
  38. package/dist/src/plugins/twake/calendar.d.ts +113 -0
  39. package/dist/src/plugins/twake/calendarResources.d.ts +9 -58
  40. package/docs/README.md +1 -0
  41. package/docs/client-development/api/openapi.md +1 -1
  42. package/docs/client-development/browser/libraries.md +1 -1
  43. package/docs/plugin-development/dependencies.md +10 -7
  44. package/docs/presentations/en.md +1 -1
  45. package/docs/presentations/fr.md +1 -1
  46. package/docs/usage/README.md +1 -0
  47. package/docs/usage/configuration.md +35 -15
  48. package/docs/usage/directory-audit.md +105 -0
  49. package/docs/usage/plugins/README.md +6 -6
  50. package/docs/usage/plugins/auth/README.md +6 -5
  51. package/docs/usage/plugins/auth/authz-scope.md +103 -0
  52. package/docs/usage/plugins/auth/llng.md +17 -0
  53. package/docs/usage/plugins/integrations/README.md +5 -5
  54. package/docs/usage/plugins/integrations/{calendar-resources.md → calendar.md} +70 -13
  55. package/docs/usage/plugins/ldap/README.md +13 -11
  56. package/docs/usage/plugins/ldap/account-lifecycle.md +129 -0
  57. package/docs/usage/plugins/ldap/bulk-import.md +8 -1
  58. package/docs/usage/plugins/ldap/enterprise-rules.md +167 -0
  59. package/docs/usage/plugins/ldap/flat-generic.md +172 -0
  60. package/docs/usage/plugins/ldap/groups.md +10 -4
  61. package/docs/usage/plugins/ldap/organizations.md +59 -33
  62. package/docs/usage/upgrading.md +322 -0
  63. package/package.json +50 -5
  64. package/static/schemas/example/README.md +36 -0
  65. package/static/schemas/example/groups.json +94 -0
  66. package/static/schemas/example/nomenclature/domains.json +49 -0
  67. package/static/schemas/example/organizations.json +106 -0
  68. package/static/schemas/example/positions.json +35 -0
  69. package/static/schemas/example/users.json +246 -0
  70. package/static/schemas/twake/groups.json +81 -15
  71. package/static/schemas/twake/nomenclature/twakeAccountStatus.json +26 -0
  72. package/static/schemas/twake/nomenclature/twakeDeliveryMode.json +22 -0
  73. package/static/schemas/twake/nomenclature/twakeDomain.json +60 -0
  74. package/static/schemas/twake/nomenclature/twakeListType.json +22 -0
  75. package/static/schemas/twake/nomenclature/twakeMailboxType.json +22 -0
  76. package/static/schemas/twake/nomenclature/twakeTitle.json +42 -0
  77. package/static/schemas/twake/organizations.json +92 -12
  78. package/static/schemas/twake/positions.json +22 -4
  79. package/static/schemas/twake/users.json +269 -32
@@ -4,6 +4,328 @@ 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
+ ## To 0.8.0
8
+
9
+ ### Node 20 is the floor
10
+
11
+ **Who is affected:** anyone installing on Node 18 or older.
12
+
13
+ `engines` declares `>=20` — the version Debian 13 ships — and the CI runs the
14
+ suite on 20, 22, 24 and 26. Older runtimes are neither tested nor supported.
15
+
16
+ **`core/auth/llng` is the one plugin that does not follow.** It needs
17
+ `lemonldap-ng-handler`, which depends on the native `re2`, and no single `re2`
18
+ release installs on every supported Node: up to `1.24.0` it builds on 20 but
19
+ not on 26, and from `1.24.1` it declares `engines: >=22`. npm answers an
20
+ unsatisfied dependency of this kind by leaving an _optional_ package out
21
+ without a word, so on some runtimes the handler is simply absent.
22
+
23
+ Everything else builds, tests and runs there regardless. A server that
24
+ configures `core/auth/llng` on a runtime where the handler could not be
25
+ installed now fails at startup, naming the plugin and the missing package,
26
+ rather than accepting requests an authentication plugin cannot check. Check
27
+ after upgrading that the runtime you deploy on carries it:
28
+
29
+ ```bash
30
+ node -e "require('lemonldap-ng-handler'); console.log('present')"
31
+ ```
32
+
33
+ ### The LLNG handler now needs a working configuration at startup
34
+
35
+ **Who is affected:** every deployment configuring `core/auth/llng`.
36
+
37
+ The handler used to do nothing at startup: `--llng-ini` was read into the
38
+ configuration but never handed to it, so every request failed with a `500`
39
+ regardless of what the file said. It is now initialized once, before the
40
+ server starts serving — which means the file finally matters, and two ways
41
+ it can be wrong now stop the server instead of answering `500`:
42
+
43
+ - `--llng-ini` defaults to `/etc/lemonldap-ng/lemonldap-ng.ini`, and a file at
44
+ that path with no `[node-handler] nodeVhosts` listing this server — the
45
+ normal state until now, since nothing read it — refuses to start where it
46
+ used to start and answer `500` to every request instead;
47
+ - an LLNG configuration store (`[configuration] baseConfigUrl` and the rest)
48
+ unreachable when the server boots crash-loops it under an orchestrator,
49
+ until the portal or config store it depends on comes up.
50
+
51
+ List this server before upgrading:
52
+
53
+ ```ini
54
+ [node-handler]
55
+ nodeVhosts = api.example.com
56
+ ```
57
+
58
+ and, if the LLNG configuration store starts after this server does, sequence
59
+ the two or give the container a restart policy that tolerates a few
60
+ failures at boot.
61
+
62
+ ### The Twake schemas now need `core/ldap/enterpriseRules`
63
+
64
+ **Who is affected:** every deployment loading `static/schemas/twake/*`.
65
+
66
+ Those schemas mark `twakeDepartmentPath`, `twakeAccountStatus` and
67
+ `twakeDeliveryMode` both `required` and `generated` — a client may not send
68
+ them, and a plugin has to fill them. Load the one that does:
69
+
70
+ ```bash
71
+ --plugin core/ldap/enterpriseRules
72
+ ```
73
+
74
+ Without it, a creation answers `400` naming the attribute, on the flat routes
75
+ as on `POST /ldap/organizations` and `POST /ldap/groups`. That refusal is
76
+ deliberate: the previous release wrote the entry anyway, missing an attribute
77
+ its own schema called required and that no client could ever add.
78
+
79
+ **The nomenclature has to hold what the schema defaults name.** The user
80
+ schema points at `cn=normal,ou=twakeDeliveryMode,ou=nomenclature,<base>` and
81
+ `cn=active,ou=twakeAccountStatus,ou=nomenclature,<base>`. A directory never
82
+ seeded with those entries answers `400` on every creation, naming the DN that
83
+ does not resolve — rather than storing a dangling one on each account.
84
+
85
+ ### Clients must stop sending the computed attributes
86
+
87
+ **Who is affected:** anything that creates or updates users, groups or
88
+ organizations against the Twake schemas.
89
+
90
+ `uid` (generated from the local part of `mail`), `twakeDepartmentPath`,
91
+ `twakeAccountStatus` and `twakeDeliveryMode` are refused in a request body
92
+ with a `400` naming the attribute. Drop them from the payload — the server
93
+ fills them.
94
+
95
+ To keep the old behaviour, copy the schema and remove the markers: each of
96
+ these is a `generated` or `readOnly` flag in the JSON, not code.
97
+
98
+ ### Run the audit before switching
99
+
100
+ **Who is affected:** every directory holding entries written before this
101
+ release.
102
+
103
+ ```bash
104
+ npm run audit:directory -- --schema static/schemas/twake/users.json \
105
+ --plugin core/ldap/enterpriseRules
106
+ ```
107
+
108
+ It reads the branch as it stands and reports what the schema would now refuse,
109
+ quoting each rule's own `hint`. Two rules tightened in ways that only show on
110
+ stored data:
111
+
112
+ - an array's `items.test` and `items.branch` are enforced on the flat,
113
+ organization and group routes, where they never were —
114
+ `mailAlternateAddress` has carried a pattern since 0.7.0 and accepted
115
+ anything — and an array of pointers must name existing entries;
116
+ - a pointer's `branch` is compared RDN by RDN. A DN that merely ended with the
117
+ branch as text, `uid=x,xou=users,dc=example,dc=com` against
118
+ `ou=users,dc=example,dc=com`, used to pass.
119
+
120
+ Stored values are left alone. The refusal comes at the next update of an
121
+ offending entry, which is why it is worth knowing beforehand.
122
+
123
+ ### `employeeNumber` is unique, apart from the `UNIT` placeholder
124
+
125
+ **Who is affected:** directories where accounts share a placeholder employee
126
+ number other than `UNIT`.
127
+
128
+ `static/schemas/twake/users.json` declares `employeeNumber` unique, exempting
129
+ the value `UNIT` — the placeholder the interface these schemas replace uses
130
+ for an account standing for a unit rather than a person. Once
131
+ `core/ldap/enterpriseRules` is loaded, every creation or update carrying a
132
+ value another account already holds answers `409`; `UNIT` is let through
133
+ however many accounts carry it.
134
+
135
+ A deployment whose placeholder is spelled otherwise has to say so, or its
136
+ second holder of that value is refused, and every existing holder at its next
137
+ update of the attribute. The audit cannot warn: uniqueness spans the whole
138
+ directory, and it only checks what each entry says on its own.
139
+
140
+ Find the shared values before switching:
141
+
142
+ ```bash
143
+ ldapsearch -LLL -b "ou=users,<base>" "(employeeNumber=*)" employeeNumber \
144
+ | awk '/^employeeNumber:/ {print $2}' | sort | uniq -cd
145
+ ```
146
+
147
+ Then name yours in a copy of the schema, as
148
+ `static/schemas/example/users.json` does:
149
+
150
+ ```json
151
+ "unique": { "sentinel": "YOUR-PLACEHOLDER" }
152
+ ```
153
+
154
+ ### Calendar resource ids, and the branch they are looked for in
155
+
156
+ **Who is affected:** deployments loading `core/twake/calendar` (the plugin
157
+ `calendarResources` was renamed to).
158
+
159
+ Three things changed in how an LDAP entry is matched to a Calendar resource.
160
+ A resource whose DN is `cn=…` or `uid=…` directly under the configured base,
161
+ with no escape in its value, keeps the identifier it had and needs nothing.
162
+
163
+ **`--calendar-resource-base` must be a full DN.** The branch was tested by
164
+ asking whether the DN _contained_ the configured value as text, so a partial
165
+ value like `ou=resources` matched — and matched a sibling branch such as
166
+ `ou=resourcesArchive` with it. It is compared by DN components now, which
167
+ also fixes the other direction: a DN written with spaces after its commas was
168
+ not recognised as a resource at all. Give the option the whole DN; the plugin
169
+ warns at startup when the value is not one under `--ldap-base`.
170
+
171
+ **The identifier is read from the entry's own RDN.** It used to come from an
172
+ unanchored search for `cn=` or `uid=` anywhere in the DN, so an entry whose
173
+ own RDN was neither borrowed the one of an ancestor; failing that, creation
174
+ fell back to a slug of the entry's name, under which modification and
175
+ deletion then found nothing. An RDN value carrying an escape — `cn=Salle\, 2`
176
+ — was cut at the escape.
177
+
178
+ So an entry of either of those shapes is now created, patched and deleted in
179
+ Calendar under a different identifier than before. **The resource Calendar
180
+ already holds under the old one is not migrated**: rename it there, or delete
181
+ it and let the next write recreate it. List what is affected before
182
+ upgrading — every resource whose RDN attribute is neither `cn` nor `uid`, and
183
+ every one whose RDN value contains a `\`.
184
+
185
+ **A modification or a deletion outside the base no longer reaches Calendar.**
186
+ Only creation checked the branch; the other two acted on any entry of the
187
+ entity whose DN yielded an identifier.
188
+
189
+ ### Renaming an entry, and what the cascade reaches
190
+
191
+ **Who is affected:** anyone calling the new
192
+ `POST /v1/ldap/{resource}/{id}/rename`. Nothing changes for a deployment
193
+ that does not.
194
+
195
+ The identifier of a flat entry can be changed. The entry's DN changes with
196
+ it, so everything naming that DN has to be rewritten, and the endpoint waits
197
+ for that before it answers.
198
+
199
+ What it rewrites is read from the schemas, never from a list of attribute
200
+ names: every `pointer` — single or in an array — whose `branch` admits the
201
+ renamed entry, and every attribute carrying the `members` or `owners` role.
202
+ **Load `core/ldap/enterpriseRules`**: without it the entry is renamed and
203
+ nothing else is touched.
204
+
205
+ What it cannot reach is a DN held in a plain `string` attribute that carries
206
+ no role. Declare it a `pointer`, or give it its role, and it is covered.
207
+
208
+ A directory running OpenLDAP's `refint` overlay already fixes the attributes
209
+ it is configured for — usually `member`, `owner`, `uniqueMember` and
210
+ `memberOf` — in a task of its own, after the rename has answered. The server
211
+ converges with it rather than fighting it: what the overlay has already
212
+ fixed counts as done. `refint` is not a substitute, though. It never sees a
213
+ deployment's own pointers, `twakeManagerLink` and `twakeLocalAdminLink`
214
+ among them — and losing the second one silently costs an administrator every
215
+ branch they administer.
216
+
217
+ ### A rename answers `207` when it could not finish
218
+
219
+ The rename of the entry and the rewrite of what points at it are separate
220
+ writes, and a directory has no transaction to hold them together.
221
+
222
+ If the rename itself fails, nothing else has happened and the directory's own
223
+ refusal is what you get. Once it succeeds the rename is a fact, and an error
224
+ would tell you the opposite — so a rewrite that fails answers `207` with the
225
+ attributes and the counts that could not be written. The referring DNs are
226
+ not in the body, where the caller has no business reading them; they are in
227
+ the log, at `error`, with the attribute and both DNs.
228
+
229
+ **Re-issue the identical request to finish it.** A rename whose source is
230
+ already gone and whose target is already there runs the rewrite alone. That
231
+ is also what to do if the server dies between the two writes.
232
+
233
+ ### `unique` is not widened on a rename
234
+
235
+ A schema marking its identifier `unique: { "branches": [...] }` gets the
236
+ RDN's own branch checked on a rename, and nothing more: the wider check runs
237
+ from the add and modify hooks, which a rename does not go through. The
238
+ endpoint's own description says so too.
239
+
240
+ ### Creating an entry that already exists answers `409`
241
+
242
+ **Who is affected:** any client that reads a failed creation by its status
243
+ code or by the text of its message — a bulk import recording per-line errors
244
+ most of all.
245
+
246
+ Two creations of the same entry racing past the existence checks answered
247
+ `500`, the add path having wrapped every LDAP error in a plain `Error`. The
248
+ directory's own `entryAlreadyExists` is now recognised once, in
249
+ `lib/ldapActions`, so every creation route answers `409` alike: the flat
250
+ routes, `POST /ldap/groups`, the organization routes, and the external
251
+ members `core/ldap/externalUsersInGroups` inserts.
252
+
253
+ The message is `Entry <dn> already exists`. The flat routes used to say
254
+ `<entity> <dn> already exists`, and `core/ldap/bulkImport` used to record
255
+ `LDAP add error: …` in its per-line `errors[]`; both read the same now.
256
+ SCIM still answers `uniqueness`, the numeric code being carried on the
257
+ error it is given.
258
+
259
+ ### A flat schema may not claim a URL an LDAP plugin serves
260
+
261
+ **Who is affected:** anyone passing `--ldap-flat-schema` a schema whose
262
+ `entity.pluralName` is `groups`, `organizations`, `raw` or `bulk-import`
263
+ beside the plugin of the same name — `static/schemas/twake/groups.json` is
264
+ exactly that.
265
+
266
+ Both used to load, sharing a hook prefix and a URL; Express answered with
267
+ whichever registered first, and the loser stayed advertised by the
268
+ configuration API while being unreachable in fact. The schema is now dropped
269
+ with an error naming the holder. **Load one or the other, not both.**
270
+
271
+ ### Organization paths: nothing to convert
272
+
273
+ **Who is affected:** directories written before the path order was settled.
274
+
275
+ `twakeDepartmentPath` reads from the root down, the entry's own name last.
276
+ What is already stored in the old order — the entry's own name first, the top
277
+ organization's last — is still accepted as it stands, so no organization
278
+ becomes unwritable on upgrade. Only paths the server computes from now on
279
+ follow the new order, and the two forms coexist until an entry is rewritten.
280
+
281
+ ### The search cache works now, and is off by default
282
+
283
+ **Who is affected:** deployments that set `--ldap-cache-ttl` (or
284
+ `DM_LDAP_CACHE_TTL`) explicitly. Everyone else keeps exactly the behaviour
285
+ they had.
286
+
287
+ The base-scope search cache stored nothing since it was written: the branch
288
+ that would have kept a result could not be reached, so every read went to the
289
+ directory whatever the option said. It caches now — which means a deployment
290
+ that had set the option gets a working cache for the first time, where it used
291
+ to get none.
292
+
293
+ What a write through this service drops is what it changed, its subtree
294
+ included, whatever case the DN is written in. What this service cannot know
295
+ about, it cannot drop: another replica of it, an LSC synchronisation, a
296
+ hand-run `ldapmodify`. A cached read stays as it was until its TTL runs out.
297
+
298
+ So `--ldap-cache-ttl` now defaults to `0`, which means no caching at all. Set
299
+ it only where this service is the sole writer of the entries it reads, and
300
+ keep the value under the staleness you are willing to serve.
301
+
302
+ ### `calendarResources` is renamed `calendar`
303
+
304
+ **Who is affected:** deployments loading `core/twake/calendarResources`, and
305
+ plugins importing `ldap-rest/plugin-twake-calendarresources` or looking the
306
+ plugin up by name.
307
+
308
+ Besides calendar resources, the plugin propagates user email and name changes
309
+ to the Twake Calendar registered users; the old name hid that. Nothing breaks
310
+ yet: `core/twake/calendarResources` still loads the same plugin, registered
311
+ under its old name, and logs a deprecation warning. It will be removed in a
312
+ future major release, so switch now by **replacing** the old name with the new
313
+ one:
314
+
315
+ ```bash
316
+ # before
317
+ DM_PLUGINS="…,core/twake/calendarResources"
318
+ # after
319
+ DM_PLUGINS="…,core/twake/calendar"
320
+ ```
321
+
322
+ Do not list both: they register under different names, so the loader does not
323
+ see a duplicate and runs two instances — every resource change and every user
324
+ email or name change is then sent to Calendar twice.
325
+
326
+ Plugin code should import `ldap-rest/plugin-twake-calendar` and look the
327
+ plugin up as `calendar`. Configuration flags (`--calendar-*`) are unchanged.
328
+
7
329
  ## To 0.7.0
8
330
 
9
331
  ### `externalId` on Groups is no longer served
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -56,6 +56,10 @@
56
56
  "types": "./dist/src/plugins/auth/authzPerRoute.d.ts",
57
57
  "import": "./dist/plugins/auth/authzPerRoute.js"
58
58
  },
59
+ "./plugin-auth-authzscope": {
60
+ "types": "./dist/src/plugins/auth/authzScope.d.ts",
61
+ "import": "./dist/plugins/auth/authzScope.js"
62
+ },
59
63
  "./plugin-auth-crowdsec": {
60
64
  "types": "./dist/src/plugins/auth/crowdsec.d.ts",
61
65
  "import": "./dist/plugins/auth/crowdsec.js"
@@ -96,6 +100,10 @@
96
100
  "types": "./dist/src/plugins/demo/helloworld.d.ts",
97
101
  "import": "./dist/plugins/demo/helloworld.js"
98
102
  },
103
+ "./plugin-ldap-accountlifecycle": {
104
+ "types": "./dist/src/plugins/ldap/accountLifecycle.d.ts",
105
+ "import": "./dist/plugins/ldap/accountLifecycle.js"
106
+ },
99
107
  "./plugin-ldap-bulkimport": {
100
108
  "types": "./dist/src/plugins/ldap/bulkImport.d.ts",
101
109
  "import": "./dist/plugins/ldap/bulkImport.js"
@@ -104,6 +112,10 @@
104
112
  "types": "./dist/src/plugins/ldap/departmentSync.d.ts",
105
113
  "import": "./dist/plugins/ldap/departmentSync.js"
106
114
  },
115
+ "./plugin-ldap-enterpriserules": {
116
+ "types": "./dist/src/plugins/ldap/enterpriseRules.d.ts",
117
+ "import": "./dist/plugins/ldap/enterpriseRules.js"
118
+ },
107
119
  "./plugin-ldap-externalusersingroups": {
108
120
  "types": "./dist/src/plugins/ldap/externalUsersInGroups.d.ts",
109
121
  "import": "./dist/plugins/ldap/externalUsersInGroups.js"
@@ -208,6 +220,10 @@
208
220
  "types": "./dist/src/plugins/twake/appAccountsConsistency.d.ts",
209
221
  "import": "./dist/plugins/twake/appAccountsConsistency.js"
210
222
  },
223
+ "./plugin-twake-calendar": {
224
+ "types": "./dist/src/plugins/twake/calendar.d.ts",
225
+ "import": "./dist/plugins/twake/calendar.js"
226
+ },
211
227
  "./plugin-twake-calendarresources": {
212
228
  "types": "./dist/src/plugins/twake/calendarResources.d.ts",
213
229
  "import": "./dist/plugins/twake/calendarResources.js"
@@ -480,6 +496,26 @@
480
496
  "import": "./static/schemas/ad/users.json",
481
497
  "require": "./static/schemas/ad/users.json"
482
498
  },
499
+ "./schema-example-groups": {
500
+ "import": "./static/schemas/example/groups.json",
501
+ "require": "./static/schemas/example/groups.json"
502
+ },
503
+ "./schema-example-nomenclature-domains": {
504
+ "import": "./static/schemas/example/nomenclature/domains.json",
505
+ "require": "./static/schemas/example/nomenclature/domains.json"
506
+ },
507
+ "./schema-example-organizations": {
508
+ "import": "./static/schemas/example/organizations.json",
509
+ "require": "./static/schemas/example/organizations.json"
510
+ },
511
+ "./schema-example-positions": {
512
+ "import": "./static/schemas/example/positions.json",
513
+ "require": "./static/schemas/example/positions.json"
514
+ },
515
+ "./schema-example-users": {
516
+ "import": "./static/schemas/example/users.json",
517
+ "require": "./static/schemas/example/users.json"
518
+ },
483
519
  "./schema-obm-organizations": {
484
520
  "import": "./static/schemas/obm/organizations.json",
485
521
  "require": "./static/schemas/obm/organizations.json"
@@ -564,6 +600,10 @@
564
600
  "import": "./static/schemas/twake/nomenclature/twakeDeliveryMode.json",
565
601
  "require": "./static/schemas/twake/nomenclature/twakeDeliveryMode.json"
566
602
  },
603
+ "./schema-twake-nomenclature-twakedomain": {
604
+ "import": "./static/schemas/twake/nomenclature/twakeDomain.json",
605
+ "require": "./static/schemas/twake/nomenclature/twakeDomain.json"
606
+ },
567
607
  "./schema-twake-nomenclature-twakelisttype": {
568
608
  "import": "./static/schemas/twake/nomenclature/twakeListType.json",
569
609
  "require": "./static/schemas/twake/nomenclature/twakeListType.json"
@@ -607,6 +647,7 @@
607
647
  "package.json"
608
648
  ],
609
649
  "scripts": {
650
+ "audit:directory": "tsx scripts/audit-directory.ts",
610
651
  "build": "npm run build:prod",
611
652
  "build:browser": "rollup -c rollup.browser.config.mjs",
612
653
  "build:dev": "./.dev.mk builddev",
@@ -615,7 +656,8 @@
615
656
  "build:pages": "tsx scripts/build-pages.ts",
616
657
  "build:prod": "npm run clean && NODE_ENV=production rollup -c && npm run build:browser && node scripts/moveBrowserLibs.mjs && npm run build:dockerfile",
617
658
  "build:watch": "rollup -c --watch",
618
- "check": "npm run check:ts && npm run lint && npm run format:check",
659
+ "check": "npm run check:ts && npm run lint && npm run format:check && npm run check:no-client-values",
660
+ "check:no-client-values": "sh scripts/check-no-client-values.sh",
619
661
  "check:ts": "tsc --noEmit",
620
662
  "clean": "rimraf dist/* static/browser/* coverage",
621
663
  "coverage": "c8 --config .c8rc.json mocha",
@@ -643,7 +685,7 @@
643
685
  },
644
686
  "dependencies": {
645
687
  "body-parser": "^2.2.0",
646
- "csv-parse": "^6.1.0",
688
+ "csv-parse": "^7.0.2",
647
689
  "express": "^5.1.0",
648
690
  "express-rate-limit": "^8.1.0",
649
691
  "ldapts": "^8.0.9",
@@ -668,7 +710,7 @@
668
710
  "@types/supertest": "^6.0.3",
669
711
  "@typescript-eslint/eslint-plugin": "^8.44.0",
670
712
  "@typescript-eslint/parser": "^8.44.0",
671
- "c8": "^10.1.3",
713
+ "c8": "^12.0.0",
672
714
  "chai": "^6.0.1",
673
715
  "eslint": "^9.35.0",
674
716
  "eslint-config-prettier": "^10.1.8",
@@ -678,7 +720,7 @@
678
720
  "fast-glob": "^3.3.3",
679
721
  "globals": "^14.0.0",
680
722
  "js-yaml": "^4.1.1",
681
- "mocha": "^11.7.2",
723
+ "mocha": "^12.0.0",
682
724
  "nock": "^14.0.10",
683
725
  "prettier": "^3.6.2",
684
726
  "rimraf": "^6.0.1",
@@ -695,5 +737,8 @@
695
737
  "express-openid-connect": "^2.19.2",
696
738
  "lemonldap-ng-handler": "^0.7.3",
697
739
  "re2": "^1.22.1"
740
+ },
741
+ "engines": {
742
+ "node": ">=20"
698
743
  }
699
744
  }
@@ -0,0 +1,36 @@
1
+ # Example deployment configuration
2
+
3
+ These schemas show what a concrete deployment looks like once every
4
+ client-specific value has been moved out of the code: national phone and
5
+ postal-code formats, the mail domain accounts must belong to, the payroll
6
+ number format and the placeholder that exempts a value from its uniqueness
7
+ check, the mailbox quota default, and the naming rules the directory already
8
+ enforces on its existing data.
9
+
10
+ They are **not** part of the product. Nothing in `src/` refers to them, and the
11
+ `npm run check:no-client-values` guard makes sure nothing ever does. Copy the
12
+ directory, replace the patterns and the domains, and the core serves another
13
+ directory unchanged.
14
+
15
+ Each `test` carries the `hint` that explains it, so a client can show the
16
+ expected format under the field instead of only rejecting what was typed.
17
+
18
+ Point a server at them with:
19
+
20
+ ```sh
21
+ node bin/index.mjs \
22
+ --ldap-base dc=example,dc=org \
23
+ --ldap-top-organization ou=organization,dc=example,dc=org \
24
+ --ldap-group-base ou=lists,ou=groups,dc=example,dc=org \
25
+ --group-schema static/schemas/example/groups.json \
26
+ --organization-schema static/schemas/example/organizations.json \
27
+ --ldap-flat-schema static/schemas/example/users.json \
28
+ --ldap-flat-schema static/schemas/example/positions.json \
29
+ --ldap-flat-schema static/schemas/example/nomenclature/domains.json \
30
+ --plugin core/ldap/flatGeneric \
31
+ --plugin core/ldap/groups \
32
+ --plugin core/ldap/organizations \
33
+ --plugin core/ldap/enterpriseRules \
34
+ --plugin core/ldap/accountLifecycle \
35
+ --plugin core/auth/authzScope
36
+ ```
@@ -0,0 +1,94 @@
1
+ {
2
+ "entity": {
3
+ "name": "mailingList",
4
+ "mainAttribute": "cn",
5
+ "objectClass": ["top", "groupOfNames", "twakeStaticGroup"],
6
+ "singularName": "mailingList",
7
+ "pluralName": "mailingLists",
8
+ "base": "ou=lists,ou=groups,__ldap_base__"
9
+ },
10
+ "strict": true,
11
+ "attributes": {
12
+ "objectClass": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "string",
16
+ "test": "^[a-zA-Z][a-zA-Z0-9-]*$"
17
+ },
18
+ "default": ["top", "groupOfNames", "twakeStaticGroup"],
19
+ "required": true,
20
+ "fixed": true
21
+ },
22
+ "cn": {
23
+ "type": "string",
24
+ "searchable": true,
25
+ "test": "^(?!.*\\.\\.)[a-zA-Z0-9!#$%&'*+/=?^_`{|}.-]{1,64}$",
26
+ "hint": "Must match an email local part, at most 64 characters, no consecutive dots",
27
+ "required": true,
28
+ "role": "identifier",
29
+ "label": "Mailing list name"
30
+ },
31
+ "description": {
32
+ "type": "string",
33
+ "searchable": true,
34
+ "required": false
35
+ },
36
+ "mail": {
37
+ "type": "string",
38
+ "searchable": true,
39
+ "test": "^[^@\\s]{1,64}@([^@\\s]{1,245}\\.)?example\\.org$",
40
+ "hint": "Expected an address in example.org or one of its subdomains",
41
+ "required": true,
42
+ "unique": {
43
+ "attributes": ["mailAlternateAddress"],
44
+ "branches": ["__LDAP_BASE__"]
45
+ },
46
+ "mailDomainScope": "organization",
47
+ "allowSubdomains": true,
48
+ "role": "primaryEmail"
49
+ },
50
+ "businessCategory": {
51
+ "type": "pointer",
52
+ "branch": ["ou=twakeListType,ou=nomenclature,__LDAP_BASE__"],
53
+ "required": false,
54
+ "role": "writePolicy",
55
+ "label": "List type"
56
+ },
57
+ "member": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "string",
61
+ "test": "^[a-zA-Z]+=.*,__LDAP_BASE__$",
62
+ "hint": "Expected the DN of an entry of this directory"
63
+ },
64
+ "required": false,
65
+ "deleteGuard": "nonEmpty",
66
+ "role": "members",
67
+ "label": "Members"
68
+ },
69
+ "owner": {
70
+ "type": "array",
71
+ "items": {
72
+ "type": "string",
73
+ "test": "^[a-zA-Z]+=.*,__LDAP_BASE__$",
74
+ "hint": "Expected the DN of an entry of this directory"
75
+ },
76
+ "required": false,
77
+ "role": "owners"
78
+ },
79
+ "twakeDepartmentLink": {
80
+ "type": "pointer",
81
+ "branch": ["ou=organization,__LDAP_BASE__"],
82
+ "required": true,
83
+ "role": "organizationLink",
84
+ "label": "Department"
85
+ },
86
+ "twakeDepartmentPath": {
87
+ "type": "string",
88
+ "test": "^[^\\r\\n]{1,4096}$",
89
+ "required": true,
90
+ "generated": true,
91
+ "role": "organizationPath"
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "entity": {
3
+ "name": "domain",
4
+ "mainAttribute": "dc",
5
+ "objectClass": ["top", "domain", "domainRelatedObject"],
6
+ "singularName": "domain",
7
+ "pluralName": "domains",
8
+ "base": "ou=domains,ou=nomenclature,{ldap_base}",
9
+ "label": {
10
+ "en": "Mail domains",
11
+ "fr": "Domaines de messagerie"
12
+ },
13
+ "singularLabel": {
14
+ "en": "domain",
15
+ "fr": "domaine"
16
+ }
17
+ },
18
+ "strict": true,
19
+ "attributes": {
20
+ "objectClass": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "string"
24
+ },
25
+ "default": ["top", "domain", "domainRelatedObject"],
26
+ "required": true,
27
+ "fixed": true
28
+ },
29
+ "dc": {
30
+ "type": "string",
31
+ "test": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$",
32
+ "hint": "A single domain label: letters, digits and hyphens, e.g. example",
33
+ "required": true,
34
+ "role": "identifier"
35
+ },
36
+ "associatedDomain": {
37
+ "type": "string",
38
+ "test": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$|^\\*$",
39
+ "hint": "A fully qualified domain name (example.org), or * to allow every domain",
40
+ "required": true,
41
+ "role": "domainName",
42
+ "label": "Mail domain"
43
+ },
44
+ "description": {
45
+ "type": "string",
46
+ "required": false
47
+ }
48
+ }
49
+ }