ldap-authentication 4.0.3 → 4.0.5

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.
@@ -8,14 +8,13 @@ services:
8
8
  command: sleep infinity
9
9
 
10
10
  ldap:
11
- image: symas/openldap:2.6
11
+ build:
12
+ context: ..
13
+ dockerfile: docker/ldap/Dockerfile
12
14
  ports:
13
- - '1389:1389'
14
- - '1636:1636'
15
+ - '1389:389'
16
+ - '1636:636'
15
17
  environment:
16
- - LDAP_ROOT=dc=example,dc=com
17
- - LDAP_ADMIN_USERNAME=read-only-admin
18
+ - LDAP_DOMAIN=example.com
18
19
  - LDAP_ADMIN_PASSWORD=password
19
- - LDAP_USERS=gauss,einstein
20
- - LDAP_PASSWORDS=password,password
21
- - LDAP_GROUP=科学A部
20
+ - LDAP_TLS=true
@@ -0,0 +1,53 @@
1
+ name: 'Publish to npm'
2
+
3
+ env:
4
+ INGITHUB: true
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ name: 'Publish ${{ github.ref_name }}'
14
+
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+
18
+ - name: Install LDAP tools
19
+ run: sudo apt-get install -y ldap-utils
20
+
21
+ - name: Start containers
22
+ run: docker compose -f "docker-compose.yml" up -d
23
+
24
+ - name: Wait for LDAP server to become available
25
+ uses: nick-fields/retry@v2
26
+ with:
27
+ timeout_minutes: 5
28
+ max_attempts: 10
29
+ polling_interval_seconds: 3
30
+ command: |
31
+ ldapsearch -LLL -o 'ldif-wrap=no' \
32
+ -H ldap://localhost:1389 \
33
+ -D 'cn=read-only-admin,dc=example,dc=com' \
34
+ -w password \
35
+ -b 'cn=einstein,ou=users,dc=example,dc=com' DN
36
+
37
+ - name: Use Node.js 22.x
38
+ uses: actions/setup-node@v3
39
+ with:
40
+ node-version: 22.x
41
+ registry-url: 'https://registry.npmjs.org'
42
+
43
+ - run: npm ci
44
+ - run: npm run test
45
+
46
+ - name: Stop containers
47
+ if: always()
48
+ run: docker compose -f "docker-compose.yml" down
49
+
50
+ - name: Publish to npm
51
+ run: npm publish
52
+ env:
53
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md CHANGED
@@ -104,6 +104,14 @@ let authenticated = await authenticate({
104
104
 
105
105
  #### Complete example
106
106
 
107
+ The library works with both CommonJS and ES modules:
108
+
109
+ ```javascript
110
+ import { authenticate } from 'ldap-authentication'
111
+ // or
112
+ const { authenticate } = require('ldap-authentication')
113
+ ```
114
+
107
115
  ```javascript
108
116
  const { authenticate } = require('ldap-authentication')
109
117
 
@@ -204,6 +212,9 @@ auth()
204
212
  if this value is not set, then authenticate will return true right after user bind succeed. No user details
205
213
  from LDAP search will be performed and returned.
206
214
  Example: `uid`
215
+ - `usernameFilter`: Prioritized alternative to usernameAttribute, allows you to provide a filter where `{{username}}` will
216
+ be replaced with the username provided
217
+ Example: `(|(uid={{username}})(mail={{username}}))`
207
218
  - `username`: The username to authenticate with. It is used together with the name in `usernameAttribute`
208
219
  to construct an ldap filter as `({attribute}={username})`
209
220
  to find the user and get user details in LDAP. Example: `some user input`
@@ -0,0 +1,47 @@
1
+ # Test directory seed data for ldap-authentication integration tests.
2
+ # Loaded by osixia/openldap at first start via
3
+ # /container/service/slapd/assets/config/bootstrap/ldif/custom
4
+ # (directory bind-mounted in docker-compose.yml).
5
+ # See https://github.com/osixia/container-openldap (v1, tag 1.5.0)
6
+
7
+ dn: ou=users,dc=example,dc=com
8
+ objectClass: organizationalUnit
9
+ ou: users
10
+
11
+ dn: ou=groups,dc=example,dc=com
12
+ objectClass: organizationalUnit
13
+ ou: groups
14
+
15
+ dn: cn=read-only-admin,dc=example,dc=com
16
+ objectClass: simpleSecurityObject
17
+ objectClass: organizationalRole
18
+ cn: read-only-admin
19
+ description: LDAP admin user for tests (password: password)
20
+ userPassword: {SSHA}u/l8yXCyiWhP3iXpt0UWMyM9ch5sZGFwLXRlc3QtMjAyNg==
21
+
22
+ dn: cn=gauss,ou=users,dc=example,dc=com
23
+ objectClass: inetOrgPerson
24
+ objectClass: posixAccount
25
+ cn: gauss
26
+ sn: Bar1
27
+ uid: gauss
28
+ uidNumber: 1000
29
+ gidNumber: 1000
30
+ homeDirectory: /home/gauss
31
+ userPassword: {SSHA}u/l8yXCyiWhP3iXpt0UWMyM9ch5sZGFwLXRlc3QtMjAyNg==
32
+
33
+ dn: cn=einstein,ou=users,dc=example,dc=com
34
+ objectClass: inetOrgPerson
35
+ objectClass: posixAccount
36
+ cn: einstein
37
+ sn: Bar2
38
+ uid: einstein
39
+ uidNumber: 1001
40
+ gidNumber: 1001
41
+ homeDirectory: /home/einstein
42
+ userPassword: {SSHA}u/l8yXCyiWhP3iXpt0UWMyM9ch5sZGFwLXRlc3QtMjAyNg==
43
+
44
+ dn: cn=科学A部,ou=groups,dc=example,dc=com
45
+ objectClass: groupOfNames
46
+ cn: 科学A部
47
+ member: cn=gauss,ou=users,dc=example,dc=com
@@ -0,0 +1,21 @@
1
+ # ACLs for the test LDAP directory, applied on top of the osixia/openldap
2
+ # defaults (replaces the data database access rules at first start).
3
+ #
4
+ # The userPassword rule must come before the generic "to *" rule: OpenLDAP
5
+ # evaluates the first matching "to" clause for an attribute.
6
+ #
7
+ # - EXTERNAL (slapd local socket): full manage (used by the image bootstrap)
8
+ # - cn=admin,dc=example,dc=com: full manage
9
+ # - cn=read-only-admin,dc=example,dc=com: full manage (tests use it as the
10
+ # admin, including the entry modify in binary.spec.js)
11
+ # - normal users: read own entry and read group entries (group lookup tests)
12
+ # - anonymous: no access (simple bind does not consult other ACLs)
13
+
14
+ dn: olcDatabase={1}mdb,cn=config
15
+ changetype: modify
16
+ delete: olcAccess
17
+ -
18
+ add: olcAccess
19
+ olcAccess: to attrs=userPassword,shadowLastChange by self write by dn="cn=admin,dc=example,dc=com" manage by dn.exact="cn=read-only-admin,dc=example,dc=com" manage by anonymous auth by * none
20
+ olcAccess: to dn.subtree="ou=groups,dc=example,dc=com" by users read by dn.exact="cn=read-only-admin,dc=example,dc=com" read by dn="cn=admin,dc=example,dc=com" manage
21
+ olcAccess: to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by dn="cn=admin,dc=example,dc=com" manage by dn.exact="cn=read-only-admin,dc=example,dc=com" manage by users read by self read by * none
@@ -0,0 +1,7 @@
1
+ FROM osixia/openldap:1.5.0
2
+
3
+ # Seed the test directory (dc=example,dc=com) with the users, groups and
4
+ # admin account used by the integration tests, and grant the test admin
5
+ # (cn=read-only-admin) the rights it needs. Files are applied by the
6
+ # osixia/openldap bootstrap at the container's first start.
7
+ COPY docker/ldap/10-ldap-test-data.ldif docker/ldap/20-ldap-test-acl.ldif /container/service/slapd/assets/config/bootstrap/ldif/custom/
@@ -1,13 +1,15 @@
1
1
  services:
2
2
  ldap:
3
- image: symas/openldap:2.6
3
+ # LDAP server used by the integration tests, based on osixia/openldap.
4
+ # The Dockerfile in docker/ldap/ seeds the directory with the users,
5
+ # groups and admin account the tests rely on.
6
+ build:
7
+ context: .
8
+ dockerfile: docker/ldap/Dockerfile
4
9
  ports:
5
- - '1389:1389'
6
- - '1636:1636'
10
+ - '1389:389'
11
+ - '1636:636'
7
12
  environment:
8
- - LDAP_ROOT=dc=example,dc=com
9
- - LDAP_ADMIN_USERNAME=read-only-admin
13
+ - LDAP_DOMAIN=example.com
10
14
  - LDAP_ADMIN_PASSWORD=password
11
- - LDAP_USERS=gauss,einstein
12
- - LDAP_PASSWORDS=password,password
13
- - LDAP_GROUP=科学A部
15
+ - LDAP_TLS=true
package/index.d.ts CHANGED
@@ -8,6 +8,7 @@ declare module 'ldap-authentication' {
8
8
  adminPassword?: string
9
9
  userSearchBase?: string
10
10
  usernameAttribute?: string
11
+ usernameFilter?:string
11
12
  username?: string
12
13
  verifyUserExists?: boolean
13
14
  starttls?: boolean
@@ -20,7 +21,24 @@ declare module 'ldap-authentication' {
20
21
  explicitBufferAttributes?: string[]
21
22
  }
22
23
 
24
+ export const AUTH_RESULT_FAILURE = 0
25
+ export const AUTH_RESULT_SUCCESS = 1
26
+ export const AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND = -1
27
+ export const AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS = -2
28
+ export const AUTH_RESULT_FAILURE_CREDENTIAL_INVALID = -3
29
+ export const AUTH_RESULT_FAILURE_UNCATEGORIZED = -4
30
+
31
+ export class AuthenticationResult {
32
+ constructor(authCode: number, identity: string, user: any, messages: string[], client: any)
33
+ readonly code: number
34
+ readonly identity: string
35
+ readonly user: any
36
+ readonly messages: string[]
37
+ readonly client: any
38
+ }
39
+
23
40
  export function authenticate(options: AuthenticationOptions): Promise<any>
41
+ export function authenticateResult(options: AuthenticationOptions): Promise<AuthenticationResult>
24
42
 
25
43
  export class LdapAuthenticationError extends Error {
26
44
  constructor(message: any)
package/index.js CHANGED
@@ -124,19 +124,30 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
124
124
  return client
125
125
  }
126
126
 
127
+ // replace username in filter
128
+
129
+
127
130
  // search a user and return the object
128
131
  async function _searchUser(
129
132
  ldapClient,
130
133
  searchBase,
134
+ usernameFilter,
131
135
  usernameAttribute,
132
136
  username,
133
137
  attributes = null,
134
138
  explicitBufferAttributes = null
135
139
  ) {
136
- let filter = new ldapts.EqualityFilter({
137
- attribute: usernameAttribute,
138
- value: username,
139
- })
140
+ let filter;
141
+ if(usernameFilter){
142
+ filter = usernameFilter.replaceAll("{{username}}",username.replaceAll(/[&|!*()]/g,""));
143
+ }
144
+ else{
145
+ filter = new ldapts.EqualityFilter({
146
+ attribute: usernameAttribute,
147
+ value: username,
148
+ })
149
+ }
150
+
140
151
  let searchOptions = {
141
152
  filter: filter,
142
153
  scope: 'sub',
@@ -266,6 +277,7 @@ async function authenticateWithAdmin(
266
277
  adminDn,
267
278
  adminPassword,
268
279
  userSearchBase,
280
+ usernameFilter,
269
281
  usernameAttribute,
270
282
  username,
271
283
  userPassword,
@@ -303,6 +315,7 @@ async function authenticateWithAdmin(
303
315
  let searchResult = await _searchUser(
304
316
  ldapAdminClient,
305
317
  userSearchBase,
318
+ usernameFilter,
306
319
  usernameAttribute,
307
320
  username,
308
321
  attributes,
@@ -368,6 +381,7 @@ async function authenticateWithAdmin(
368
381
  async function authenticateWithUser(
369
382
  userDn,
370
383
  userSearchBase,
384
+ usernameFilter,
371
385
  usernameAttribute,
372
386
  username,
373
387
  userPassword,
@@ -411,6 +425,7 @@ async function authenticateWithUser(
411
425
  let searchResult = await _searchUser(
412
426
  ldapUserClient,
413
427
  userSearchBase,
428
+ usernameFilter,
414
429
  usernameAttribute,
415
430
  username,
416
431
  attributes,
@@ -463,6 +478,7 @@ async function verifyUserExists(
463
478
  adminDn,
464
479
  adminPassword,
465
480
  userSearchBase,
481
+ usernameFilter,
466
482
  usernameAttribute,
467
483
  username,
468
484
  starttls,
@@ -498,6 +514,7 @@ async function verifyUserExists(
498
514
  let searchResult = await _searchUser(
499
515
  ldapAdminClient,
500
516
  userSearchBase,
517
+ usernameFilter,
501
518
  usernameAttribute,
502
519
  username,
503
520
  attributes,
@@ -562,8 +579,8 @@ async function authenticateResult(options) {
562
579
  assert(options.adminPassword, 'Admin mode adminPassword must be provided')
563
580
  assert(options.userSearchBase, 'Admin mode userSearchBase must be provided')
564
581
  assert(
565
- options.usernameAttribute,
566
- 'Admin mode usernameAttribute must be provided'
582
+ options.usernameAttribute || options.usernameFilter,
583
+ 'Admin mode usernameAttribute or usernameFilter must be provided'
567
584
  )
568
585
  assert(options.username, 'Admin mode username must be provided')
569
586
  } else {
@@ -584,6 +601,7 @@ async function authenticateResult(options) {
584
601
  options.adminDn,
585
602
  options.adminPassword,
586
603
  options.userSearchBase,
604
+ options.usernameFilter,
587
605
  options.usernameAttribute,
588
606
  options.username,
589
607
  options.starttls,
@@ -607,6 +625,7 @@ async function authenticateResult(options) {
607
625
  options.adminDn,
608
626
  options.adminPassword,
609
627
  options.userSearchBase,
628
+ options.usernameFilter,
610
629
  options.usernameAttribute,
611
630
  options.username,
612
631
  options.userPassword,
@@ -625,6 +644,7 @@ async function authenticateResult(options) {
625
644
  return await authenticateWithUser(
626
645
  options.userDn,
627
646
  options.userSearchBase,
647
+ options.usernameFilter,
628
648
  options.usernameAttribute,
629
649
  options.username,
630
650
  options.userPassword,
package/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import ldapAuthentication from './index.js'
2
+
3
+ export {
4
+ AUTH_RESULT_FAILURE,
5
+ AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
6
+ AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
7
+ AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
8
+ AUTH_RESULT_FAILURE_UNCATEGORIZED,
9
+ AUTH_RESULT_SUCCESS,
10
+ AuthenticationResult,
11
+ LdapAuthenticationError,
12
+ authenticate,
13
+ authenticateResult,
14
+ } from './index.js'
15
+
16
+ export default ldapAuthentication
package/package.json CHANGED
@@ -1,9 +1,18 @@
1
1
  {
2
2
  "name": "ldap-authentication",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "A simple async nodejs library for LDAP user authentication",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "import": "./index.mjs",
11
+ "require": "./index.js",
12
+ "default": "./index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
7
16
  "engines": {
8
17
  "node": ">=20.0.0"
9
18
  },