ldap-rest 0.4.7 → 0.5.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 (62) hide show
  1. package/bin/sync-james.mjs +6 -2
  2. package/dist/bin/index.js +1 -1
  3. package/dist/config/args.js +1 -1
  4. package/dist/lib/auth/base.js +1 -1
  5. package/dist/lib/ldapSchema.js +1 -0
  6. package/dist/plugins/auth/authzPerRoute.js +1 -1
  7. package/dist/plugins/auth/rateLimit.js +1 -1
  8. package/dist/plugins/ldap/organizations.js +1 -1
  9. package/dist/plugins/ldap/raw.js +1 -0
  10. package/dist/plugins/twake/appAccountsApi.js +1 -1
  11. package/dist/src/bin/index.d.ts +112 -0
  12. package/dist/src/browser/ldap-browser/LdapBrowser.d.ts +52 -0
  13. package/dist/src/browser/ldap-browser/api/RawApiClient.d.ts +64 -0
  14. package/dist/src/browser/ldap-browser/components/EntryTree.d.ts +75 -0
  15. package/dist/src/browser/ldap-browser/components/EntryView.d.ts +56 -0
  16. package/dist/src/browser/ldap-browser/index.d.ts +9 -0
  17. package/dist/src/browser/ldap-browser/schema.d.ts +62 -0
  18. package/dist/src/browser/ldap-browser/types.d.ts +96 -0
  19. package/dist/src/config/args.d.ts +6 -0
  20. package/dist/src/lib/auth/base.d.ts +82 -1
  21. package/dist/src/lib/ldapSchema.d.ts +159 -0
  22. package/dist/src/plugins/ldap/raw.d.ts +251 -0
  23. package/docs/client-development/browser/libraries.md +142 -1
  24. package/docs/usage/configuration.md +20 -0
  25. package/docs/usage/plugins/README.md +10 -9
  26. package/docs/usage/plugins/auth/README.md +57 -0
  27. package/docs/usage/plugins/auth/rate-limit.md +9 -0
  28. package/docs/usage/plugins/integrations/app-accounts.md +32 -0
  29. package/docs/usage/plugins/ldap/README.md +12 -0
  30. package/docs/usage/plugins/ldap/raw.md +226 -0
  31. package/examples/web/README.md +18 -0
  32. package/examples/web/ldap-browser.html +94 -0
  33. package/package.json +37 -1
  34. package/static/browser/ldap-browser/LdapBrowser.js +199 -0
  35. package/static/browser/ldap-browser/LdapBrowser.js.map +1 -0
  36. package/static/browser/ldap-browser/api/RawApiClient.js +103 -0
  37. package/static/browser/ldap-browser/api/RawApiClient.js.map +1 -0
  38. package/static/browser/ldap-browser/components/EntryTree.js +235 -0
  39. package/static/browser/ldap-browser/components/EntryTree.js.map +1 -0
  40. package/static/browser/ldap-browser/components/EntryView.js +183 -0
  41. package/static/browser/ldap-browser/components/EntryView.js.map +1 -0
  42. package/static/browser/ldap-browser/index.js +4 -0
  43. package/static/browser/ldap-browser/index.js.map +1 -0
  44. package/static/browser/ldap-browser/schema.js +135 -0
  45. package/static/browser/ldap-browser/schema.js.map +1 -0
  46. package/static/browser/ldap-browser/types.js +2 -0
  47. package/static/browser/ldap-browser/types.js.map +1 -0
  48. package/static/browser/ldap-browser.css +2 -0
  49. package/static/browser/ldap-browser.css.map +1 -0
  50. package/static/browser/ldap-browser.esm.js +915 -0
  51. package/static/browser/ldap-browser.esm.js.map +1 -0
  52. package/static/browser/ldap-browser.js +925 -0
  53. package/static/browser/ldap-browser.js.map +1 -0
  54. package/static/browser/ldap-browser.min.js +2 -0
  55. package/static/browser/ldap-browser.min.js.map +1 -0
  56. package/static/browser/ldap-unit-editor/components/UnitTree.js +0 -1
  57. package/static/browser/ldap-unit-editor/components/UnitTree.js.map +1 -1
  58. package/static/browser/ldap-unit-editor.esm.js +0 -1
  59. package/static/browser/ldap-unit-editor.esm.js.map +1 -1
  60. package/static/browser/ldap-unit-editor.js +0 -1
  61. package/static/browser/ldap-unit-editor.js.map +1 -1
  62. package/static/browser/ldap-unit-editor.min.js.map +1 -1
@@ -0,0 +1,226 @@
1
+ # LDAP Raw Plugin (low-level browsing)
2
+
3
+ Read-only, low-level access to the directory itself: root DSE, schema, and entries addressed by their DN. Where the other LDAP plugins expose business objects (users, groups, organizations), this one exposes the tree as it is stored — the server side of a phpLDAPadmin-like interface.
4
+
5
+ The companion browser library is [`ldap-browser`](../../../client-development/browser/libraries.md#ldapbrowser).
6
+
7
+ ## Features
8
+
9
+ - **Root DSE**: naming contexts, supported controls and extensions
10
+ - **Schema**: object classes, attribute types, syntaxes and matching rules, parsed from the subschema entry (RFC 4512) and cached
11
+ - **Entry read**: every attribute, operational ones included, binary values base64-encoded
12
+ - **Tree navigation**: direct children of a DN, with an optional "has children" probe
13
+ - **Raw search**: arbitrary base, scope and LDAP filter
14
+ - **Read-only**: this plugin registers no write route
15
+
16
+ ## Configuration
17
+
18
+ ### Environment Variables
19
+
20
+ - `DM_LDAP_RAW_BASE`: comma-separated subtrees to expose (default: `--ldap-base`)
21
+ - `DM_LDAP_RAW_HIDDEN_ATTRIBUTES`: comma-separated attributes never returned, on top of the credential ones (default: none)
22
+ - `DM_LDAP_RAW_SHOW_SECRETS`: serve credential attributes instead of hiding them (default: `false`)
23
+ - `DM_LDAP_RAW_MAX_RESULTS`: maximum entries returned by a search or a children listing (default: `200`)
24
+ - `DM_LDAP_RAW_SCHEMA_CACHE_TTL`: schema cache lifetime in seconds (default: `3600`)
25
+
26
+ ### Command Line
27
+
28
+ ```bash
29
+ ldap-rest \
30
+ --plugin core/ldap/raw \
31
+ --ldap-raw-base "ou=users,dc=example,dc=com" \
32
+ --ldap-raw-base "ou=groups,dc=example,dc=com" \
33
+ --ldap-raw-hidden-attribute employeeNumber \
34
+ --ldap-raw-max-results 500
35
+ ```
36
+
37
+ With no `--ldap-raw-base`, the plugin exposes `--ldap-base`.
38
+
39
+ ## API Endpoints
40
+
41
+ ```
42
+ GET /api/v1/ldap/raw/bases # Exposed subtrees (roots of the tree)
43
+ GET /api/v1/ldap/raw/rootdse # Server capabilities
44
+ GET /api/v1/ldap/raw/schema # Parsed directory schema
45
+ GET /api/v1/ldap/raw/entry/{dn} # One entry, operational attributes included
46
+ GET /api/v1/ldap/raw/children/{dn} # Direct children of an entry
47
+ GET /api/v1/ldap/raw/search # Arbitrary search
48
+ ```
49
+
50
+ `{dn}` is the URL-encoded fully-qualified DN.
51
+
52
+ ### Entry format
53
+
54
+ Attribute values are always arrays. A `binary` flag tells whether the values are base64-encoded octets rather than text:
55
+
56
+ ```json
57
+ {
58
+ "dn": "uid=alice,ou=users,dc=example,dc=com",
59
+ "attributes": {
60
+ "objectClass": { "values": ["top", "inetOrgPerson"], "binary": false },
61
+ "uid": { "values": ["alice"], "binary": false },
62
+ "cn": { "values": ["Alice Smith"], "binary": false },
63
+ "jpegPhoto": { "values": ["/9j/4AAQSkZJRg…"], "binary": true }
64
+ }
65
+ }
66
+ ```
67
+
68
+ A value is flagged binary when the directory returned octets that are not valid UTF-8, or when the attribute type is binary.
69
+
70
+ Credential attributes are **not** part of the response by default — see [Hidden attributes](#hidden-attributes).
71
+
72
+ ### Children
73
+
74
+ ```bash
75
+ curl "$URL/api/v1/ldap/raw/children/$(urlencode 'dc=example,dc=com')?children=1"
76
+ ```
77
+
78
+ ```json
79
+ {
80
+ "children": [
81
+ {
82
+ "dn": "ou=users,dc=example,dc=com",
83
+ "rdn": "ou=users",
84
+ "objectClass": ["organizationalUnit"],
85
+ "hasChildren": true
86
+ }
87
+ ],
88
+ "truncated": false
89
+ }
90
+ ```
91
+
92
+ `hasChildren` is only computed when the `children` query parameter is set: it costs one extra search per child.
93
+
94
+ At most `--ldap-raw-max-results` children are returned. `truncated` is true when the branch holds more, so a browsing UI can say so instead of silently showing a partial list — a branch of 5000 users must not look like a branch of 200.
95
+
96
+ ### Search
97
+
98
+ | Parameter | Default | Description |
99
+ | ------------ | ------------------------ | ---------------------------------------------- |
100
+ | `base` | first exposed base | Search base, must be inside an exposed subtree |
101
+ | `scope` | `sub` | `base`, `one` or `sub` |
102
+ | `filter` | `(objectClass=*)` | LDAP filter |
103
+ | `attributes` | `*` | Comma-separated attribute list |
104
+ | `limit` | `--ldap-raw-max-results` | Capped by `--ldap-raw-max-results` |
105
+
106
+ ```bash
107
+ curl "$URL/api/v1/ldap/raw/search?base=ou=users,dc=example,dc=com&filter=(mail=*@example.com)&attributes=uid,mail"
108
+ ```
109
+
110
+ ```json
111
+ { "entries": [...], "truncated": false }
112
+ ```
113
+
114
+ `truncated` is true when the limit cut the result set short.
115
+
116
+ The filter is parsed before the search runs, so a malformed one comes back as a 400 naming it rather than as a server error — a search box receives whatever the user typed, and `gov` is a typo, not a server fault:
117
+
118
+ ```json
119
+ {
120
+ "error": "Invalid LDAP filter \"gov\": Invalid expression: gov. A filter must be parenthesised, e.g. (cn=foo) or (|(cn=*foo*)(ou=*foo*))"
121
+ }
122
+ ```
123
+
124
+ ### Schema
125
+
126
+ ```json
127
+ {
128
+ "objectClasses": [
129
+ {
130
+ "oid": "2.5.6.6",
131
+ "names": ["person"],
132
+ "desc": "RFC2256: a person",
133
+ "kind": "STRUCTURAL",
134
+ "sup": ["top"],
135
+ "must": ["sn", "cn"],
136
+ "may": ["userPassword", "telephoneNumber", "seeAlso", "description"],
137
+ "obsolete": false
138
+ }
139
+ ],
140
+ "attributeTypes": [
141
+ {
142
+ "oid": "2.5.4.3",
143
+ "names": ["cn", "commonName"],
144
+ "sup": "name",
145
+ "singleValue": false,
146
+ "noUserModification": false,
147
+ "usage": "userApplications",
148
+ "obsolete": false,
149
+ "collective": false
150
+ }
151
+ ],
152
+ "syntaxes": [...],
153
+ "matchingRules": [...]
154
+ }
155
+ ```
156
+
157
+ Inheritance is **not** flattened: a client resolving the attributes of an entry must walk the `sup` chains. `SchemaView` (browser) and `SchemaIndex` (`src/lib/ldapSchema.ts`, server) do it for you.
158
+
159
+ ## Hidden attributes
160
+
161
+ Attributes holding credential material are stripped from **every** response — entry reads and searches alike — before it leaves the server. Asking for one explicitly (`?attributes=uid,userPassword`) does not get around the filter: the removal happens when the entry is serialised, not when the query is built.
162
+
163
+ The built-in list covers the usual OpenLDAP, Samba, Kerberos and Active Directory spellings:
164
+
165
+ ```
166
+ userPassword, authPassword, pwdHistory, sambaNTPassword, sambaLMPassword,
167
+ sambaPasswordHistory, krbPrincipalKey, krbExtraData, krbPwdHistory,
168
+ unicodePwd, dbcsPwd, lmPwdHistory, ntPwdHistory, supplementalCredentials,
169
+ userPKCS12
170
+ ```
171
+
172
+ A password hash served over HTTP is an offline cracking target, and browsing a directory does not require reading one — hence hidden by default, visible only on request:
173
+
174
+ ```bash
175
+ --ldap-raw-show-secrets true # logs a warning at startup
176
+ ```
177
+
178
+ Site-specific attributes are added with `--ldap-raw-hidden-attribute`, which stacks on top of the built-in list rather than replacing it:
179
+
180
+ ```bash
181
+ --ldap-raw-hidden-attribute twakeApiKey --ldap-raw-hidden-attribute employeeNumber
182
+ ```
183
+
184
+ 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
+
186
+ ## Security
187
+
188
+ Access is restricted in three independent layers:
189
+
190
+ 1. **Exposed bases** — any DN outside `--ldap-raw-base` is refused with a 403, whatever the caller's rights.
191
+ 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).
193
+
194
+ 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
+
196
+ Combine with an authentication plugin ([token](../auth/token.md), [oidc](../auth/oidc.md), …) — this plugin adds no authentication of its own.
197
+
198
+ ## How It Works
199
+
200
+ - **Schema fetch**: the root DSE is read for `subschemaSubentry` (falling back to `cn=Subschema`), then the subschema entry is fetched and parsed once per `--ldap-raw-schema-cache-ttl`. Concurrent callers share a single fetch.
201
+ - **Operational attributes**: entries are read with `*` and `+`. When the server rejects `+`, the read is retried with `*` alone.
202
+ - **Missing entries**: the LDAP "no such object" result (code 32) is mapped to a 404.
203
+
204
+ ## Limitations
205
+
206
+ - **Read-only.** Creating, modifying, renaming and deleting entries is not exposed yet.
207
+ - **No LDIF** import or export.
208
+ - **No paging**: searches and children listings are capped by `--ldap-raw-max-results` and flagged as `truncated`; there is no cursor to fetch the rest. Use a narrower search to reach entries beyond the cap.
209
+
210
+ ## Usage Example
211
+
212
+ ```bash
213
+ ldap-rest \
214
+ --plugin core/auth/token --auth-token "$TOKEN" \
215
+ --plugin core/ldap/raw \
216
+ --plugin core/static
217
+ ```
218
+
219
+ The demo page ([`examples/web/ldap-browser.html`](../../../../examples/web/ldap-browser.html)) is then served at
220
+ `/static/examples/web/ldap-browser.html`.
221
+
222
+ ## See Also
223
+
224
+ - [ldap-browser client library](../../../client-development/browser/libraries.md#ldapbrowser)
225
+ - [organizations plugin](organizations.md) — high-level tree of organizational units
226
+ - [authz-per-branch plugin](../auth/authz-per-branch.md) — branch-level access control
@@ -82,6 +82,23 @@ Manage organizational units.
82
82
  - Move units
83
83
  - Create/delete units
84
84
 
85
+ ### 🗂️ LDAP Browser (`ldap-browser.html`)
86
+
87
+ Low-level, read-only view of the directory, in the spirit of phpLDAPadmin.
88
+
89
+ **Features:**
90
+
91
+ - Lazy tree of the whole directory
92
+ - Raw entry content, operational attributes included
93
+ - Schema annotations: mandatory attributes, descriptions, unknown attributes flagged
94
+ - LDAP filter search with scope selection
95
+
96
+ Requires the `core/ldap/raw` plugin:
97
+
98
+ ```bash
99
+ npm start -- --plugin core/ldap/raw
100
+ ```
101
+
85
102
  ## Running Examples
86
103
 
87
104
  1. **Start the server:**
@@ -109,6 +126,7 @@ Manage organizational units.
109
126
  http://localhost:8081/static/examples/web/ldap-user-editor.html
110
127
  http://localhost:8081/static/examples/web/ldap-group-editor.html
111
128
  http://localhost:8081/static/examples/web/ldap-unit-editor.html
129
+ http://localhost:8081/static/examples/web/ldap-browser.html
112
130
  ```
113
131
 
114
132
  ## Using Browser Libraries in Your Application
@@ -0,0 +1,94 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>LDAP Browser Demo</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <link
14
+ href="https://fonts.googleapis.com/icon?family=Material+Icons"
15
+ rel="stylesheet"
16
+ />
17
+
18
+ <!-- Shared CSS -->
19
+ <link rel="stylesheet" href="/static/browser/common-demo.css" />
20
+ <!-- Library CSS -->
21
+ <link rel="stylesheet" href="/static/browser/ldap-browser.css" />
22
+
23
+ <style>
24
+ :root {
25
+ --primary-color: #3949ab;
26
+ --primary-color-dark: #283593;
27
+ }
28
+
29
+ body {
30
+ background: linear-gradient(135deg, #8ea7e9 0%, #3949ab 100%);
31
+ }
32
+
33
+ .demo-header h1 {
34
+ color: #3949ab;
35
+ }
36
+
37
+ #ldap-browser {
38
+ height: 70vh;
39
+ min-height: 520px;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div class="demo-container">
45
+ <div class="demo-header">
46
+ <h1>🗂️ LDAP Browser</h1>
47
+ <p>
48
+ Low-level, read-only view of the directory: tree, raw entries and
49
+ schema
50
+ </p>
51
+ </div>
52
+
53
+ <div id="status-container"></div>
54
+
55
+ <div id="ldap-browser"></div>
56
+ </div>
57
+
58
+ <!-- Shared utilities -->
59
+ <script src="/static/browser/shared.js"></script>
60
+ <!-- Library JS -->
61
+ <script src="/static/browser/ldap-browser.js"></script>
62
+
63
+ <script>
64
+ const { LdapBrowser } = window.LdapBrowser;
65
+ const { showStatus } = window.MiniDmShared;
66
+
67
+ async function initBrowser() {
68
+ try {
69
+ const browser = new LdapBrowser({
70
+ containerId: 'ldap-browser',
71
+ apiBaseUrl: window.location.origin,
72
+ onEntrySelected: dn => console.log('Entry selected:', dn),
73
+ onError: error => {
74
+ console.error('Browser error:', error);
75
+ showStatus(
76
+ 'status-container',
77
+ `Error: ${error.message}`,
78
+ 'error'
79
+ );
80
+ },
81
+ });
82
+
83
+ await browser.init();
84
+ showStatus('status-container', 'LDAP Browser loaded', 'success');
85
+ } catch (error) {
86
+ console.error('Failed to initialize browser:', error);
87
+ showStatus('status-container', `Error: ${error.message}`, 'error');
88
+ }
89
+ }
90
+
91
+ initBrowser();
92
+ </script>
93
+ </body>
94
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-rest",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "description": "Light directory manager",
5
5
  "keywords": [
6
6
  "Identity-Manager"
@@ -132,6 +132,10 @@
132
132
  "types": "./dist/src/plugins/ldap/passwordPolicy.d.ts",
133
133
  "import": "./dist/plugins/ldap/passwordPolicy.js"
134
134
  },
135
+ "./plugin-ldap-raw": {
136
+ "types": "./dist/src/plugins/ldap/raw.d.ts",
137
+ "import": "./dist/plugins/ldap/raw.js"
138
+ },
135
139
  "./plugin-ldap-trash": {
136
140
  "types": "./dist/src/plugins/ldap/trash.d.ts",
137
141
  "import": "./dist/plugins/ldap/trash.js"
@@ -232,6 +236,34 @@
232
236
  "types": "./dist/src/abstract/twakePlugin.d.ts",
233
237
  "import": "./dist/abstract/twakePlugin.js"
234
238
  },
239
+ "./browser-ldap-browser-ldapbrowser": {
240
+ "types": "./dist/src/browser/ldap-browser/LdapBrowser.d.ts",
241
+ "import": "./static/browser/ldap-browser/LdapBrowser.js"
242
+ },
243
+ "./browser-ldap-browser-api-rawapiclient": {
244
+ "types": "./dist/src/browser/ldap-browser/api/RawApiClient.d.ts",
245
+ "import": "./static/browser/ldap-browser/api/RawApiClient.js"
246
+ },
247
+ "./browser-ldap-browser-components-entrytree": {
248
+ "types": "./dist/src/browser/ldap-browser/components/EntryTree.d.ts",
249
+ "import": "./static/browser/ldap-browser/components/EntryTree.js"
250
+ },
251
+ "./browser-ldap-browser-components-entryview": {
252
+ "types": "./dist/src/browser/ldap-browser/components/EntryView.d.ts",
253
+ "import": "./static/browser/ldap-browser/components/EntryView.js"
254
+ },
255
+ "./browser-ldap-browser-index": {
256
+ "types": "./dist/src/browser/ldap-browser/index.d.ts",
257
+ "import": "./static/browser/ldap-browser/index.js"
258
+ },
259
+ "./browser-ldap-browser-schema": {
260
+ "types": "./dist/src/browser/ldap-browser/schema.d.ts",
261
+ "import": "./static/browser/ldap-browser/schema.js"
262
+ },
263
+ "./browser-ldap-browser-types": {
264
+ "types": "./dist/src/browser/ldap-browser/types.d.ts",
265
+ "import": "./static/browser/ldap-browser/types.js"
266
+ },
235
267
  "./browser-ldap-group-editor-ldapgroupeditor": {
236
268
  "types": "./dist/src/browser/ldap-group-editor/LdapGroupEditor.d.ts",
237
269
  "import": "./static/browser/ldap-group-editor/LdapGroupEditor.js"
@@ -578,6 +610,8 @@
578
610
  "check": "npm run check:ts && npm run lint && npm run format:check",
579
611
  "check:ts": "tsc --noEmit",
580
612
  "clean": "rimraf dist/* static/browser/* coverage",
613
+ "coverage": "c8 --config .c8rc.json mocha",
614
+ "coverage:check": "npm run check:ts && c8 --config .c8rc.json --exclude 'src/browser/**' --check-coverage --statements 84 --lines 84 --branches 75 --functions 86 mocha",
581
615
  "dev": "npm run start:dev",
582
616
  "fix": "npm run lint:fix && npm run format:fix",
583
617
  "format:check": "prettier --check .",
@@ -626,6 +660,7 @@
626
660
  "@types/supertest": "^6.0.3",
627
661
  "@typescript-eslint/eslint-plugin": "^8.44.0",
628
662
  "@typescript-eslint/parser": "^8.44.0",
663
+ "c8": "^10.1.3",
629
664
  "chai": "^6.0.1",
630
665
  "eslint": "^9.35.0",
631
666
  "eslint-config-prettier": "^10.1.8",
@@ -633,6 +668,7 @@
633
668
  "eslint-plugin-node": "^11.1.0",
634
669
  "eslint-plugin-prettier": "^5.5.4",
635
670
  "fast-glob": "^3.3.3",
671
+ "globals": "^14.0.0",
636
672
  "js-yaml": "^4.1.1",
637
673
  "mocha": "^11.7.2",
638
674
  "nock": "^14.0.10",
@@ -0,0 +1,199 @@
1
+ import { escapeHtml } from '../shared/utils/dom.js';
2
+ import { RawApiClient } from './api/RawApiClient.js';
3
+ import { EntryTree } from './components/EntryTree.js';
4
+ import { EntryView } from './components/EntryView.js';
5
+ import { SchemaView } from './schema.js';
6
+
7
+ /**
8
+ * Low-level LDAP browser: a tree of the directory on the left, the raw
9
+ * content of the selected entry on the right, and a filter search on top.
10
+ *
11
+ * Read-only: it renders what the `core/ldap/raw` plugin serves.
12
+ *
13
+ * @module browser/ldap-browser/LdapBrowser
14
+ */
15
+ class LdapBrowser {
16
+ constructor(options) {
17
+ this.container = null;
18
+ this.tree = null;
19
+ this.entryView = null;
20
+ this.schema = null;
21
+ this.bases = [];
22
+ this.currentDn = null;
23
+ this.options = options;
24
+ this.api = new RawApiClient(options.apiBaseUrl || '', options.authToken);
25
+ }
26
+ /**
27
+ * Build the interface, load the schema and open the first base.
28
+ *
29
+ * @throws Error when the container does not exist or the API is unreachable
30
+ */
31
+ async init() {
32
+ const container = document.getElementById(this.options.containerId);
33
+ if (!container)
34
+ throw new Error(`Container element with id '${this.options.containerId}' not found`);
35
+ this.container = container;
36
+ this.bases = await this.api.getBases();
37
+ this.render();
38
+ // The schema only decorates the display: a directory that refuses it
39
+ // must not prevent browsing
40
+ try {
41
+ this.schema = new SchemaView(await this.api.getSchema());
42
+ }
43
+ catch (err) {
44
+ this.reportError(err);
45
+ }
46
+ const treeContainer = container.querySelector('.ldap-browser__tree');
47
+ const entryContainer = container.querySelector('.ldap-browser__entry');
48
+ if (!treeContainer || !entryContainer)
49
+ return;
50
+ this.entryView = new EntryView(entryContainer, this.schema);
51
+ this.entryView.renderEmpty();
52
+ this.tree = new EntryTree(treeContainer, this.api, dn => this.showEntry(dn), error => this.reportError(error));
53
+ await this.tree.init(this.bases);
54
+ this.bindToolbar();
55
+ const initial = this.options.initialDn || this.bases[0];
56
+ if (initial)
57
+ await this.tree.revealAndSelect(initial);
58
+ }
59
+ /** Release the listeners held by the tree */
60
+ destroy() {
61
+ this.tree?.destroy();
62
+ this.tree = null;
63
+ }
64
+ /**
65
+ * DN currently displayed.
66
+ *
67
+ * @returns selected DN, or null when none
68
+ */
69
+ getCurrentDn() {
70
+ return this.currentDn;
71
+ }
72
+ /** Draw the static layout */
73
+ render() {
74
+ if (!this.container)
75
+ return;
76
+ this.container.classList.add('ldap-browser-container');
77
+ this.container.innerHTML = `
78
+ <div class="ldap-browser">
79
+ <div class="ldap-browser__toolbar">
80
+ <input class="ldap-browser__filter" type="text"
81
+ placeholder="LDAP filter, e.g. (uid=alice)" />
82
+ <select class="ldap-browser__scope">
83
+ <option value="sub">sub</option>
84
+ <option value="one">one</option>
85
+ <option value="base">base</option>
86
+ </select>
87
+ <select class="ldap-browser__base">
88
+ ${this.bases
89
+ .map(base => `<option value="${escapeHtml(base)}">${escapeHtml(base)}</option>`)
90
+ .join('')}
91
+ </select>
92
+ <button class="ldap-browser__search" type="button">
93
+ <span class="material-icons">search</span> Search
94
+ </button>
95
+ <button class="ldap-browser__refresh" type="button" title="Reload">
96
+ <span class="material-icons">refresh</span>
97
+ </button>
98
+ </div>
99
+ <div class="ldap-browser__body">
100
+ <div class="ldap-browser__tree"></div>
101
+ <div class="ldap-browser__entry"></div>
102
+ </div>
103
+ <div class="ldap-browser__results" hidden></div>
104
+ </div>`;
105
+ }
106
+ /** Wire the search and refresh controls */
107
+ bindToolbar() {
108
+ const filter = this.container?.querySelector('.ldap-browser__filter');
109
+ const searchButton = this.container?.querySelector('.ldap-browser__search');
110
+ const refreshButton = this.container?.querySelector('.ldap-browser__refresh');
111
+ const results = this.container?.querySelector('.ldap-browser__results');
112
+ searchButton?.addEventListener('click', () => void this.runSearch());
113
+ filter?.addEventListener('keydown', event => {
114
+ if (event.key === 'Enter')
115
+ void this.runSearch();
116
+ });
117
+ refreshButton?.addEventListener('click', () => {
118
+ this.tree?.invalidate();
119
+ if (this.currentDn)
120
+ void this.showEntry(this.currentDn);
121
+ });
122
+ results?.addEventListener('click', event => {
123
+ const target = event.target.closest('[data-dn]');
124
+ if (target?.dataset.dn)
125
+ void this.tree?.revealAndSelect(target.dataset.dn);
126
+ });
127
+ }
128
+ /**
129
+ * Load an entry and display it.
130
+ *
131
+ * @param dn DN of the entry
132
+ */
133
+ async showEntry(dn) {
134
+ if (!this.entryView)
135
+ return;
136
+ this.currentDn = dn;
137
+ this.entryView.renderLoading();
138
+ try {
139
+ const entry = await this.api.getEntry(dn);
140
+ this.entryView.render(entry);
141
+ this.options.onEntrySelected?.(dn);
142
+ }
143
+ catch (err) {
144
+ this.entryView.renderError(err.message);
145
+ this.reportError(err);
146
+ }
147
+ }
148
+ /** Run the search typed in the toolbar and list the matching DNs */
149
+ async runSearch() {
150
+ const filter = this.container?.querySelector('.ldap-browser__filter')
151
+ ?.value || '';
152
+ const scope = this.container?.querySelector('.ldap-browser__scope')
153
+ ?.value || 'sub';
154
+ const base = this.container?.querySelector('.ldap-browser__base')
155
+ ?.value || this.bases[0];
156
+ const results = this.container?.querySelector('.ldap-browser__results');
157
+ if (!results)
158
+ return;
159
+ if (!filter.trim()) {
160
+ results.hidden = true;
161
+ results.innerHTML = '';
162
+ return;
163
+ }
164
+ results.hidden = false;
165
+ results.innerHTML = '<p>Searching…</p>';
166
+ try {
167
+ const found = await this.api.search({
168
+ base,
169
+ scope: scope,
170
+ filter,
171
+ attributes: ['dn'],
172
+ });
173
+ results.innerHTML = found.entries.length
174
+ ? `<p>${found.entries.length} result(s)${found.truncated ? ' (truncated)' : ''}</p>
175
+ <ul>${found.entries
176
+ .map(entry => `<li><a href="#" data-dn="${escapeHtml(entry.dn)}">${escapeHtml(entry.dn)}</a></li>`)
177
+ .join('')}</ul>`
178
+ : '<p>No result</p>';
179
+ }
180
+ catch (err) {
181
+ results.innerHTML = `<p class="ldap-browser__error">${escapeHtml(err.message)}</p>`;
182
+ this.reportError(err);
183
+ }
184
+ }
185
+ /**
186
+ * Forward an error to the host page, or log it when no handler was given.
187
+ *
188
+ * @param error error to report
189
+ */
190
+ reportError(error) {
191
+ if (this.options.onError)
192
+ this.options.onError(error);
193
+ else
194
+ console.error('[ldap-browser]', error);
195
+ }
196
+ }
197
+
198
+ export { LdapBrowser };
199
+ //# sourceMappingURL=LdapBrowser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LdapBrowser.js","sources":["../../../../src/browser/ldap-browser/LdapBrowser.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}