ldap-authentication 4.2.1 → 4.4.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.
@@ -31,15 +31,20 @@ jobs:
31
31
  language: [ 'javascript' ]
32
32
  # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33
33
  # Learn more:
34
- # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
34
+ # https://docs.github.com/en/code-security/code-scanning/defining-your-code-scanning-policy/configuring-code-scanning-for-your-personal-account-and-repositories/changing-your-code-scanning-configuration#changing-the-languages-that-are-analyzed
35
35
 
36
36
  steps:
37
37
  - name: Checkout repository
38
- uses: actions/checkout@v2
38
+ uses: actions/checkout@v4
39
+
40
+ - name: Use Node.js 24.x
41
+ uses: actions/setup-node@v4
42
+ with:
43
+ node-version: 24.x
39
44
 
40
45
  # Initializes the CodeQL tools for scanning.
41
46
  - name: Initialize CodeQL
42
- uses: github/codeql-action/init@v1
47
+ uses: github/codeql-action/init@v4
43
48
  with:
44
49
  languages: ${{ matrix.language }}
45
50
  # If you wish to specify custom queries, you can do so here or in a config file.
@@ -47,13 +52,10 @@ jobs:
47
52
  # Prefix the list here with "+" to use these queries and those in the config file.
48
53
  # queries: ./path/to/local/query, your-org/your-repo/queries@main
49
54
 
50
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51
- # If this step fails, then you should remove it and run the build manually (see below)
55
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
56
+ # If this step fails, then you should remove it and run the build manually (see below).
52
57
  - name: Autobuild
53
- uses: github/codeql-action/autobuild@v1
54
-
55
- # ℹ️ Command-line programs to run using the OS shell.
56
- # 📚 https://git.io/JvXDl
58
+ uses: github/codeql-action/autobuild@v4
57
59
 
58
60
  # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59
61
  # and modify them (or add more) to build your code if your project
@@ -64,4 +66,4 @@ jobs:
64
66
  # make release
65
67
 
66
68
  - name: Perform CodeQL Analysis
67
- uses: github/codeql-action/analyze@v1
69
+ uses: github/codeql-action/analyze@v4
package/AGENTS.md ADDED
@@ -0,0 +1,99 @@
1
+ # AGENTS.md - guidance for AI coding agents working in this repo
2
+
3
+ ## What this repo is
4
+
5
+ `ldap-authentication` - a small Node.js library that authenticates users against an
6
+ LDAP/AD server. It is a thin wrapper around `ldapts` (its only runtime dependency).
7
+
8
+ There is **no build step**. The package is published as-is:
9
+
10
+ - `index.js` - CJS implementation (all runtime logic lives here)
11
+ - `index.mjs` - ESM re-export entry point
12
+ - `index.d.ts` - hand-written TypeScript types (keep in sync with `index.js` and the README)
13
+ - `test/` - jasmine integration specs (require a running LDAP server)
14
+ - `example/` - runnable usage examples (require a running LDAP server)
15
+ - `docker/` + `docker-compose.yml` - the seeded OpenLDAP test server
16
+ - `dist/` - **stale legacy build (gitignored, not referenced by package.json, not
17
+ published). Ignore it; do not edit or "fix" it.**
18
+
19
+ ## Running the tests
20
+
21
+ The specs are integration tests against a seeded OpenLDAP container:
22
+
23
+ 1. `docker compose -f docker-compose.yml up -d` - LDAP on `ldap://localhost:1389`,
24
+ LDAPS on `ldaps://localhost:1636`
25
+ 2. Wait until an admin bind succeeds (server is ready when this works)
26
+ 3. `INGITHUB=true npm test` - the `INGITHUB` env var switches the specs from the
27
+ docker-internal URL (`ldap://ldap:1389`) to `ldap://localhost:1389`
28
+ 4. `docker compose -f docker-compose.yml down`
29
+
30
+ Or all of the above in one shot: `npm run test:local` (`scripts/test-local.sh`).
31
+
32
+ After changing the seed ldifs, rebuild the container first (`docker compose -f
33
+ docker-compose.yml build`) - `up -d` reuses the existing image and the seed
34
+ data is baked into it at build time.
35
+
36
+ Seeded data (see `docker/ldap/*.ldif`): domain `dc=example,dc=com`; users
37
+ `cn=gauss`, `cn=einstein` and `cn=Doe, John` (uid `doe`, used to test DNs with a
38
+ comma in the CN) in `ou=users` (all with password `password`); group
39
+ `cn=科学A部` in `ou=groups` containing gauss; admin
40
+ `cn=read-only-admin,dc=example,dc=com` (password `password`).
41
+ `test/binary.spec.js` MUTATES the directory (adds `jpegPhoto` to gauss), so spec
42
+ order matters - keep the deterministic order: in jasmine 7 the env options
43
+ (`random`, `seed`, `stopSpecOnExpectationFailure`) must be nested under the `env`
44
+ key in `loadConfig` (see `test/jasmine.js`).
45
+
46
+ ## Code style
47
+
48
+ Prettier (`.prettierrc`): no semicolons, single quotes, 2-space indent, trailing
49
+ commas in `es5`. Match the existing style when editing `index.js`.
50
+
51
+ ## Version control - IMPORTANT: this is a Jujutsu (jj) repo
52
+
53
+ This repo is managed with **jj** (see `.jj/`). Do NOT use `git commit` for commits;
54
+ use jj:
55
+
56
+ 1. `jj describe -m "<message>"` - commits the current working-copy changes
57
+ (after `jj describe`, jj may warn that the commit became immutable and create an
58
+ empty working-copy commit on top - that is normal and harmless)
59
+ 2. `jj bookmark move master` - move the local `master` bookmark to the new commit
60
+ (defaults to the working copy, `@`)
61
+ 3. `jj git push --bookmark master` - push to `origin`
62
+ (`git@github.com:shaozi/ldap-authentication.git`). The remote prints a
63
+ "Bypassed rule violations ... Changes must be made through a pull request"
64
+ notice on master - that is expected (maintainer bypass) and not an error.
65
+
66
+ The maintainer works detached (no branch); commits go on the `master` bookmark
67
+ with a single descriptive message, e.g.
68
+ `"Add fetchUsers() to search all users via paged admin-bound lookup (#3); bump to 4.1.0"`.
69
+
70
+ ## Releases
71
+
72
+ 1. Bump `version` in **both** `package.json` and `package-lock.json` (the lockfile
73
+ has it twice: top-level and under `packages."").version`). Follow semver
74
+ (new feature = e.g. 4.1.0, docs/deps-only = patch).
75
+ 2. Commit + push as above.
76
+ 3. `jj tag set vN.N.N -r @-` (tag the just-pushed master commit), then
77
+ `git push origin vN.N.N` (jj has no tag push in this version).
78
+ 4. `gh release create vN.N.N --title "vN.N.N" --notes "..."` - creating the
79
+ release triggers `.github/workflows/npm-publish.yml` (OIDC), which runs
80
+ `npm ci && npm publish`.
81
+ 5. Verify: `gh run list` (the `Publish Package` run should succeed) and
82
+ `npm view ldap-authentication@N.N.N`. Note that the npm registry's packument
83
+ can lag the publish by a couple of minutes - check the
84
+ `https://registry.npmjs.org/ldap-authentication` `time`/`versions` before
85
+ re-publishing.
86
+
87
+ ## CI
88
+
89
+ `integration-test.yml` runs on push/PR to `master`: builds the LDAP container from
90
+ `docker-compose.yml`, runs `npm ci` + `npm run test` with `INGITHUB=true` on a
91
+ Node 22.x/24.x matrix. Release events trigger `npm-publish.yml` (OIDC publish).
92
+
93
+ ## When adding a new option
94
+
95
+ Update all of: the validation in `index.js` (`authenticateResult` /
96
+ `fetchUsers`), the JSDoc in `index.js`, `index.d.ts` (`AuthenticationOptions` and
97
+ / or `FetchUsersOptions`), and the README (Parameters list, the
98
+ options-by-mode table, and an example if the option changes behavior). Add or
99
+ extend the specs in `test/` (integration specs run against the seeded container).
package/README.md CHANGED
@@ -212,6 +212,18 @@ auth()
212
212
  - For `ldaps://` URLs, omit `starttls` and the connection will use TLS from the start
213
213
  - TLS options like `rejectUnauthorized`, `minVersion`, and `servername` can be specified in `ldapOpts.tlsOptions`
214
214
 
215
+ #### Runnable examples
216
+
217
+ The [example/](example/) directory contains complete, runnable scripts: admin auth, self auth, group lookup,
218
+ `fetchUsers`, `verifyUserExists`, and StartTLS. They run against the bundled seeded test server
219
+ (start it via `docker compose up -d`, or point `LDAP_URL` at your own server):
220
+
221
+ ```sh
222
+ docker compose up -d # seeded OpenLDAP on localhost:1389 / 1636
223
+ node example/fetch-users.mjs # or any other script in example/
224
+ docker compose down
225
+ ```
226
+
215
227
  ## Parameters
216
228
 
217
229
  - `ldapOpts`: This is passed to `ldapts` client directly
@@ -254,12 +266,28 @@ auth()
254
266
  - `groupMemberAttribute`: if specified with groupClass and groupsSearchBase, will be used as member name (if not specified this defaults to `member`) in search filter for authenticated user groups
255
267
  - `groupMemberUserAttribute`: if specified with groupClass and groupsSearchBase, will be used as the attribute on the user object (if not specified this defaults to `dn`) in search filter for authenticated user groups
256
268
 
269
+ ### Which options for which mode?
270
+
271
+ | Mode (call) | Required | Commonly used in addition |
272
+ |---|---|---|
273
+ | Admin authenticate (`authenticate`) | `ldapOpts`, `adminDn`, `adminPassword`, `userPassword`, `userSearchBase`, `usernameAttribute` or `usernameFilter`, `username` | `attributes`, `groupsSearchBase`, `groupClass`, `starttls` |
274
+ | Self authenticate (`authenticate`) | `ldapOpts`, `userDn`, `userPassword` | `userSearchBase`, `usernameAttribute`, `attributes`, `groupsSearchBase`, `starttls` |
275
+ | Verify user exists (`authenticate` with `verifyUserExists: true`) | `ldapOpts`, `adminDn`, `adminPassword`, `userSearchBase`, `usernameAttribute` or `usernameFilter`, `username` | `attributes`, `groupsSearchBase`, `starttls` |
276
+ | Fetch all users (`fetchUsers`) | `ldapOpts`, `adminDn`, `adminPassword`, `userSearchBase` | `userFilter`, `attributes`, `pageSize`, `starttls` |
277
+
257
278
  ## Returns
258
279
 
259
280
  The user object if `authenticate()` is success.
260
281
 
261
282
  In version 4, a new function is added: `authenticateResult()`. It has the same call signature as `authenticate()` but returns an object `AuthenticationResult` with more details.
262
283
 
284
+ `authenticate()` and `fetchUsers()` throw a `LdapAuthenticationError` on failure:
285
+ - `authenticate()`: when the failure corresponds to a known outcome, the
286
+ error's `code` property holds the matching AUTH_RESULT_* constant (the same
287
+ value `authenticateResult()` reports).
288
+ - Missing required options throw a `LdapAuthenticationError` too, with all
289
+ missing fields listed in the message in a single error.
290
+
263
291
  `fetchUsers()` returns an array of user objects, one per matched LDAP entry (each with its `dn` and the returned attributes), or an empty array if no user matches the filter.
264
292
 
265
293
 
@@ -279,6 +307,30 @@ AuthenticationResult object has the following fields:
279
307
  - `message`: authentication message array, which contains server messages
280
308
  - `client`: ldapClient instance
281
309
 
310
+ ## Active Directory notes
311
+
312
+ - A typical admin bind DN is a service or admin account, e.g. `cn=Administrator,cn=users,dc=example,dc=com`,
313
+ or a dedicated LDAP sync account.
314
+ - Username attributes: `sAMAccountName` for logins like `jdoe`, `userPrincipalName` for `jdoe@example.com`.
315
+ To look a user up by either at once, use
316
+ `usernameFilter: '(|(sAMAccountName={{username}})(userPrincipalName={{username}}))'`.
317
+ - Set `userSearchBase` to the OU containing the users (e.g. `ou=users,dc=example,dc=com`):
318
+ the search is faster and avoids `AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS`.
319
+ - `fetchUsers()` uses LDAP paged results, so Active Directory's usual 1000-entry limit per search
320
+ is not an issue (adjust the page size with `pageSize` if needed).
321
+ - Binary attributes such as `thumbnailPhoto` should be requested as `thumbnailPhoto;binary`;
322
+ they are returned as base64-encoded strings.
323
+
324
+ ## Troubleshooting
325
+
326
+ | Symptom | Likely cause / fix |
327
+ |---|---|
328
+ | `ECONNREFUSED`, `ETIMEDOUT`, or other connect errors | `ldapOpts.url` is wrong or the server is unreachable. Check the URL, the network/firewall, and `connectTimeout`. |
329
+ | `LdapAuthenticationError` with `admin bind failed` / `user bind failed` | Wrong `adminDn`/`adminPassword`, or `userDn`/`userPassword` in self mode. Verify the bind manually, e.g. `ldapsearch -b dc=example,dc=com -D <dn> -w <password> dn`. |
330
+ | `identity not found` (`AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND`) | The user does not exist under `userSearchBase`, or `usernameAttribute`/`username`/`usernameFilter` does not match the attribute(s) stored on the server. |
331
+ | `identity ambiguous` (`AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS`) | The search matched multiple entries - narrow `userSearchBase` or make the filter more specific. |
332
+ | `Invalid credentials` (`AUTH_RESULT_FAILURE_CREDENTIAL_INVALID`) | The user was found but the password is wrong. |
333
+ | TLS certificate errors | For self-signed certificates use `tlsOptions: { rejectUnauthorized: false }`; add `servername` for SNI. Use `ldaps://` (without `starttls`) or `ldap://` with `starttls: true`. |
282
334
 
283
335
  ## Old Stuff
284
336
 
@@ -30,6 +30,20 @@ gidNumber: 1000
30
30
  homeDirectory: /home/gauss
31
31
  userPassword: {SSHA}u/l8yXCyiWhP3iXpt0UWMyM9ch5sZGFwLXRlc3QtMjAyNg==
32
32
 
33
+ # Note: the DN line needs the comma in the RDN value escaped (\,), the
34
+ # attribute value line does not - LDIF values are raw strings, but `dn:` is
35
+ # parsed as LDAP DN syntax (RFC 4514)
36
+ dn: cn=Doe\, John,ou=users,dc=example,dc=com
37
+ objectClass: inetOrgPerson
38
+ objectClass: posixAccount
39
+ cn: Doe, John
40
+ sn: Doe
41
+ uid: doe
42
+ uidNumber: 1002
43
+ gidNumber: 1002
44
+ homeDirectory: /home/doe
45
+ userPassword: {SSHA}u/l8yXCyiWhP3iXpt0UWMyM9ch5sZGFwLXRlc3QtMjAyNg==
46
+
33
47
  dn: cn=einstein,ou=users,dc=example,dc=com
34
48
  objectClass: inetOrgPerson
35
49
  objectClass: posixAccount
@@ -0,0 +1,36 @@
1
+ // Fetch all users with the admin account - the search is paged, so more
2
+ // than 1000 entries can be returned.
3
+ // Requires the bundled seeded test server: `docker compose up -d`
4
+ // (or set LDAP_URL to point at your own server).
5
+
6
+ import { fetchUsers } from '../index.mjs'
7
+
8
+ const ldapOpts = {
9
+ url: process.env.LDAP_URL || 'ldap://localhost:1389',
10
+ }
11
+
12
+ const baseOptions = {
13
+ ldapOpts,
14
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
15
+ adminPassword: 'password',
16
+ userSearchBase: 'dc=example,dc=com',
17
+ }
18
+
19
+ // 1. All users (default filter: entries with a uid or sAMAccountName), all attributes
20
+ let users = await fetchUsers(baseOptions)
21
+ console.log('all users ->', users.map((user) => user.uid ?? user.cn))
22
+
23
+ // 2. Custom filter + attribute selection
24
+ users = await fetchUsers({
25
+ ...baseOptions,
26
+ userFilter: '(uid=gauss)',
27
+ attributes: ['uid', 'sn'],
28
+ })
29
+ console.log('filtered ->', users)
30
+
31
+ // 3. Small page size (paged results are always on)
32
+ users = await fetchUsers({
33
+ ...baseOptions,
34
+ pageSize: 1,
35
+ })
36
+ console.log('paged ->', users.length, 'entries with pageSize 1')
package/example/index.js CHANGED
@@ -1,58 +1,56 @@
1
+ // Admin and self authentication, with group lookup.
2
+ // Requires the bundled seeded test server: `docker compose up -d`
3
+ // (or set LDAP_URL to point at your own server).
4
+
1
5
  const { authenticate } = require('../index')
2
6
 
7
+ const url = process.env.LDAP_URL || 'ldap://localhost:1389'
8
+
3
9
  async function auth() {
4
- // auth with admin
5
- let options = {
6
- ldapOpts: {
7
- url: 'ldap://localhost:1389',
8
- // tlsOptions: { rejectUnauthorized: false }
9
- },
10
+ // 1. Admin mode: bind as admin, find the user, then bind as the user.
11
+ // Restrict `attributes` so the server does not return everything (including
12
+ // userPassword).
13
+ let user = await authenticate({
14
+ ldapOpts: { url },
10
15
  adminDn: 'cn=read-only-admin,dc=example,dc=com',
11
16
  adminPassword: 'password',
12
17
  userPassword: 'password',
13
18
  userSearchBase: 'dc=example,dc=com',
14
19
  usernameAttribute: 'uid',
15
20
  username: 'gauss',
16
- // starttls: false
17
- }
21
+ attributes: ['uid', 'sn', 'cn'],
22
+ })
23
+ console.log('admin mode ->', JSON.stringify(user, null, 2))
18
24
 
19
- let user = await authenticate(options)
20
- console.log(`user = ${JSON.stringify(user, null, 2)}`)
21
-
22
- // auth with regular user
23
- options = {
24
- ldapOpts: {
25
- url: 'ldap://ldap.forumsys.com',
26
- // tlsOptions: { rejectUnauthorized: false }
27
- },
28
- userDn: 'uid=einstein,dc=example,dc=com',
25
+ // 2. Self mode: the user binds with its own DN and gets its details
26
+ user = await authenticate({
27
+ ldapOpts: { url },
28
+ userDn: 'cn=einstein,ou=users,dc=example,dc=com',
29
29
  userPassword: 'password',
30
30
  userSearchBase: 'dc=example,dc=com',
31
31
  usernameAttribute: 'uid',
32
32
  username: 'einstein',
33
- // starttls: false
34
- }
35
-
36
- user = await authenticate(options)
37
- console.log(`user = ${JSON.stringify(user, null, 2)}`)
33
+ attributes: ['uid', 'sn'],
34
+ })
35
+ console.log('self mode ->', { uid: user.uid, sn: user.sn })
38
36
 
39
- // Getting user group info
40
- options = {
41
- ldapOpts: {
42
- url: 'ldap://ldap.forumsys.com',
43
- },
44
- userDn: 'uid=gauss,dc=example,dc=com',
37
+ // 3. Admin mode with group lookup
38
+ user = await authenticate({
39
+ ldapOpts: { url },
40
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
41
+ adminPassword: 'password',
45
42
  userPassword: 'password',
46
43
  userSearchBase: 'dc=example,dc=com',
47
44
  usernameAttribute: 'uid',
48
45
  username: 'gauss',
49
46
  groupsSearchBase: 'dc=example,dc=com',
50
- groupClass: 'groupOfUniqueNames',
51
- groupMemberAttribute: 'uniqueMember',
52
- }
53
-
54
- user = await authenticate(options)
55
- console.log(`user = ${JSON.stringify(user, null, 2)}`)
47
+ groupClass: 'groupOfNames',
48
+ groupMemberAttribute: 'member',
49
+ })
50
+ console.log('with groups ->', user.groups.map((group) => group.cn))
56
51
  }
57
52
 
58
- auth().then()
53
+ auth().catch((error) => {
54
+ console.error(error)
55
+ process.exit(1)
56
+ })
@@ -0,0 +1,40 @@
1
+ // Authenticate over STARTTLS (ldap:// URL upgraded to TLS).
2
+ // Requires an LDAP server that supports TLS certificates.
3
+ // (The bundled test container generates TLS config but does not enable it,
4
+ // so this script prints a notice instead of failing in that case.)
5
+
6
+ import { authenticate } from '../index.mjs'
7
+
8
+ const isTlsNotSupported = (error) =>
9
+ error instanceof Error &&
10
+ /secure TLS connection was established/i.test(error.message)
11
+
12
+ // The bundled container uses a self-signed certificate, so the test
13
+ // disables certificate verification. Do NOT do this in production;
14
+ // provide your CA in tlsOptions.ca instead.
15
+ try {
16
+ const user = await authenticate({
17
+ ldapOpts: {
18
+ url: process.env.LDAP_URL || 'ldap://localhost:1389',
19
+ tlsOptions: {
20
+ rejectUnauthorized: false, // self-signed certificate (test only)
21
+ },
22
+ },
23
+ starttls: true, // upgrade the ldap:// connection to TLS
24
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
25
+ adminPassword: 'password',
26
+ userPassword: 'password',
27
+ userSearchBase: 'dc=example,dc=com',
28
+ usernameAttribute: 'uid',
29
+ username: 'gauss',
30
+ })
31
+ console.log('starttls auth ->', user.uid)
32
+ } catch (error) {
33
+ if (isTlsNotSupported(error)) {
34
+ console.log('This LDAP server does not support TLS - STARTTLS could not be established.')
35
+ console.log('Run this example against a server with TLS enabled (or use an ldaps:// URL).')
36
+ } else {
37
+ console.error(error)
38
+ process.exit(1)
39
+ }
40
+ }
@@ -0,0 +1,45 @@
1
+ // Verify that a user exists (without checking the password).
2
+ // Requires the bundled seeded test server: `docker compose up -d`
3
+ // (or set LDAP_URL to point at your own server).
4
+
5
+ const { authenticate, LdapAuthenticationError } = require('../index')
6
+
7
+ const url = process.env.LDAP_URL || 'ldap://localhost:1389'
8
+
9
+ async function verify() {
10
+ // Existing user
11
+ let user = await authenticate({
12
+ ldapOpts: { url },
13
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
14
+ adminPassword: 'password',
15
+ verifyUserExists: true,
16
+ userSearchBase: 'dc=example,dc=com',
17
+ usernameAttribute: 'uid',
18
+ username: 'gauss',
19
+ })
20
+ console.log('gauss exists ->', user.uid)
21
+
22
+ // Non-existing user throws LdapAuthenticationError
23
+ try {
24
+ await authenticate({
25
+ ldapOpts: { url },
26
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
27
+ adminPassword: 'password',
28
+ verifyUserExists: true,
29
+ userSearchBase: 'dc=example,dc=com',
30
+ usernameAttribute: 'uid',
31
+ username: 'does-not-exist',
32
+ })
33
+ } catch (error) {
34
+ if (error instanceof LdapAuthenticationError) {
35
+ console.log('does-not-exist ->', error.message)
36
+ } else {
37
+ throw error
38
+ }
39
+ }
40
+ }
41
+
42
+ verify().catch((error) => {
43
+ console.error(error)
44
+ process.exit(1)
45
+ })
package/index.d.ts CHANGED
@@ -37,6 +37,29 @@ declare module 'ldap-authentication' {
37
37
  readonly client: any
38
38
  }
39
39
 
40
+ /**
41
+ * A single group entry returned on `user.groups` when group lookup is
42
+ * enabled (`groupsSearchBase` + `groupClass`). `objectName` mirrors `dn`
43
+ * for backward compatibility with the old ldapjs-based API.
44
+ */
45
+ export interface LdapGroupEntry {
46
+ dn: string
47
+ objectName?: string
48
+ [attr: string]: any
49
+ }
50
+
51
+ /**
52
+ * A single user object returned by `authenticate()` / `fetchUsers()`.
53
+ * Always contains the entry's `dn`; other attribute values are
54
+ * `string`/`string[]` (or a base64 string for `;binary` attributes), and
55
+ * `groups` is present when group lookup is enabled.
56
+ */
57
+ export interface LdapUserEntry {
58
+ dn: string
59
+ groups?: LdapGroupEntry[]
60
+ [attr: string]: any
61
+ }
62
+
40
63
  export interface FetchUsersOptions {
41
64
  ldapOpts: ClientOptions
42
65
  adminDn: string
@@ -58,18 +81,41 @@ declare module 'ldap-authentication' {
58
81
  starttls?: boolean
59
82
  }
60
83
 
84
+ /**
85
+ * Authenticate a user against the LDAP server. Kept as `Promise<any>` for
86
+ * backward compatibility; the resolved value has the shape of
87
+ * {@link LdapUserEntry}. Throws {@link LdapAuthenticationError} on failure
88
+ * (the error's `code` then holds the corresponding AUTH_RESULT_* constant)
89
+ * or when required options are missing.
90
+ */
61
91
  export function authenticate(options: AuthenticationOptions): Promise<any>
92
+ /**
93
+ * Same options as {@link authenticate} but never throws on failure; returns an
94
+ * {@link AuthenticationResult}. Throws {@link LdapAuthenticationError} when
95
+ * required options are missing.
96
+ */
62
97
  export function authenticateResult(options: AuthenticationOptions): Promise<AuthenticationResult>
63
98
 
64
99
  /**
65
100
  * Bind with the admin account and search all users under `userSearchBase`.
66
101
  * The search always uses paged results, so results are not limited by the
67
102
  * common server-side limit of 1000 entries.
103
+ *
104
+ * Returns an empty array if no user matches the filter.
68
105
  */
69
- export function fetchUsers(options: FetchUsersOptions): Promise<any[]>
106
+ export function fetchUsers(options: FetchUsersOptions): Promise<LdapUserEntry[]>
70
107
 
108
+ /**
109
+ * Thrown by authenticate()/authenticateResult()/fetchUsers() on failure.
110
+ * `message` describes the failure. When the failure corresponds to a known
111
+ * authentication outcome, `code` holds the matching AUTH_RESULT_* constant
112
+ * (the same value authenticateResult() reports in `AuthenticationResult.code`).
113
+ * Missing required options also throw this error, with all missing fields
114
+ * listed in `message`.
115
+ */
71
116
  export class LdapAuthenticationError extends Error {
72
- constructor(message: any)
117
+ constructor(message: any, code?: number)
73
118
  name: string
119
+ code?: number
74
120
  }
75
121
  }