ldap-authentication 3.2.1 → 3.2.4

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.
@@ -18,3 +18,4 @@ services:
18
18
  - LDAP_ADMIN_PASSWORD=password
19
19
  - LDAP_USERS=gauss,einstein
20
20
  - LDAP_PASSWORDS=password,password
21
+ - LDAP_GROUP=科学A部
@@ -27,7 +27,7 @@ jobs:
27
27
  run: sudo apt-get install -y ldap-utils
28
28
 
29
29
  - name: Start containers
30
- run: docker-compose -f "docker-compose.yml" up -d
30
+ run: docker compose -f "docker-compose.yml" up -d
31
31
 
32
32
  - name: Wait for LDAP server to become available
33
33
  uses: nick-fields/retry@v2
@@ -51,4 +51,4 @@ jobs:
51
51
 
52
52
  - name: Stop containers
53
53
  if: always()
54
- run: docker-compose -f "docker-compose.yml" down
54
+ run: docker compose -f "docker-compose.yml" down
@@ -0,0 +1,33 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
+
4
+ name: Publish to NPM
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ - run: npm ci
19
+
20
+
21
+ publish-npm:
22
+ needs: build
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 20
29
+ registry-url: https://registry.npmjs.org/
30
+ - run: npm ci
31
+ - run: npm publish
32
+ env:
33
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/README.md CHANGED
@@ -187,7 +187,7 @@ In version 2, The user object has a `raw` field that has the raw data from the L
187
187
  Buffer data can now be accessed by `user.raw.profilePhoto`, etc, instead of `user.profilePhoto`.
188
188
 
189
189
  In version 3, the `raw` field is no longer used. Instead, append `;binary` to the attributes you
190
- want to get back as buffer. Check the following example on how to get a user's profile photo:
190
+ want to get back as base64-encoded string. Check the following example on how to get a user's profile photo:
191
191
 
192
192
  ```javascript
193
193
  export async function verifyLogin(email: string, password: string) {
@@ -210,7 +210,7 @@ export async function verifyLogin(email: string, password: string) {
210
210
  const profilePhoto = ldapUser['thumbnailPhoto;binary'];
211
211
 
212
212
  /* using the image
213
- <img src={`data:image/*;base64,${user.profilePhoto}`} />
213
+ <img src={`data:image/*;base64,${profilePhoto}`} />
214
214
  */
215
215
  return { user: ldapUser };
216
216
  }
@@ -10,3 +10,4 @@ services:
10
10
  - LDAP_ADMIN_PASSWORD=password
11
11
  - LDAP_USERS=gauss,einstein
12
12
  - LDAP_PASSWORDS=password,password
13
+ - LDAP_GROUP=科学A部
package/index.js CHANGED
@@ -1,6 +1,46 @@
1
1
  const assert = require('assert')
2
2
  const ldap = require('ldapjs')
3
3
 
4
+ // convert an escaped utf8 string returned from ldapjs
5
+ function _isHex(c) {
6
+ return (
7
+ (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
8
+ )
9
+ }
10
+ function _parseEscapedHexToUtf8(s) {
11
+ // convert 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
12
+ // to 'cn=研发A部,ou=users,dc=example,dc=com'
13
+ let ret = Buffer.alloc(0)
14
+ let len = s.length
15
+ for (let i = 0; i < len; i++) {
16
+ let c = s[i]
17
+ let item
18
+ if (c == '\\' && i < len - 2 && _isHex(s[i + 1]) && _isHex(s[i + 2])) {
19
+ item = Buffer.from(s.substring(i + 1, i + 3), 'hex')
20
+ i += 2
21
+ } else {
22
+ item = Buffer.from(c)
23
+ }
24
+ ret = Buffer.concat([ret, item])
25
+ }
26
+ return ret.toString()
27
+ }
28
+
29
+ function _recursiveParseHexString(obj) {
30
+ if (Array.isArray(obj)) {
31
+ return obj.map((ele) => _recursiveParseHexString(ele))
32
+ }
33
+ if (typeof obj == 'string') {
34
+ return _parseEscapedHexToUtf8(obj)
35
+ }
36
+ if (typeof obj == 'object') {
37
+ for (let key in obj) {
38
+ obj[key] = _recursiveParseHexString(obj[key])
39
+ }
40
+ return obj
41
+ }
42
+ return obj
43
+ }
4
44
  // convert a SearchResultEntry object in ldapjs 3.0
5
45
  // to a user object to maintain backward compatibility
6
46
 
@@ -11,7 +51,7 @@ function _searchResultToUser(pojo) {
11
51
  user[attribute.type] =
12
52
  attribute.values.length == 1 ? attribute.values[0] : attribute.values
13
53
  })
14
- return user
54
+ return _recursiveParseHexString(user)
15
55
  }
16
56
  // bind and return the ldap client
17
57
  function _ldapBind(dn, password, starttls, ldapOpts) {
@@ -142,7 +182,7 @@ async function _searchUserGroups(
142
182
  return
143
183
  }
144
184
  res.on('searchEntry', function (entry) {
145
- groups.push(entry.pojo)
185
+ groups.push(_recursiveParseHexString(entry.pojo))
146
186
  })
147
187
  res.on('searchReference', function (referral) {})
148
188
  res.on('error', function (err) {
@@ -366,7 +406,8 @@ async function authenticate(options) {
366
406
  options.groupsSearchBase,
367
407
  options.groupClass,
368
408
  options.groupMemberAttribute,
369
- options.groupMemberUserAttribute
409
+ options.groupMemberUserAttribute,
410
+ options.attributes
370
411
  )
371
412
  }
372
413
  assert(options.userPassword, 'userPassword must be provided')
@@ -422,3 +463,9 @@ class LdapAuthenticationError extends Error {
422
463
 
423
464
  module.exports.authenticate = authenticate
424
465
  module.exports.LdapAuthenticationError = LdapAuthenticationError
466
+
467
+ module.exports.exportForTesting = {
468
+ _isHex,
469
+ _parseEscapedHexToUtf8,
470
+ _recursiveParseHexString,
471
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-authentication",
3
- "version": "3.2.1",
3
+ "version": "3.2.4",
4
4
  "description": "A simple async nodejs library for LDAP user authentication",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
@@ -39,6 +39,6 @@
39
39
  "ldapjs": "^3.0.7"
40
40
  },
41
41
  "devDependencies": {
42
- "jasmine": "^5.1.0"
42
+ "jasmine": "^5.3.0"
43
43
  }
44
44
  }
@@ -0,0 +1,43 @@
1
+ const { exportForTesting } = require('../index.js')
2
+ const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString } =
3
+ exportForTesting
4
+
5
+ describe('string conversion test', () => {
6
+ it('unescape string test', () => {
7
+ let s =
8
+ 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
9
+ let us = _parseEscapedHexToUtf8(s)
10
+ expect(us).toEqual('cn=研发A部,ou=users,dc=example,dc=com')
11
+ })
12
+ it('unescape string test2', () => {
13
+ let s =
14
+ 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9,ou=users,dc=example,dc=com'
15
+ let us = _parseEscapedHexToUtf8(s)
16
+ expect(us).toEqual('cn=研发A部©,ou=users,dc=example,dc=com')
17
+ })
18
+ it('unescape string test3', () => {
19
+ let s = 'cn=ABC,ou=users,dc=example,dc=com'
20
+ let us = _parseEscapedHexToUtf8(s)
21
+ expect(us).toEqual('cn=ABC,ou=users,dc=example,dc=com')
22
+ })
23
+ it('convert obj', () => {
24
+ let target = {
25
+ a: ['研发A部©', 'abc'],
26
+ b: 'xyz',
27
+ c: true,
28
+ d: null,
29
+ e: '研发A部©',
30
+ f: 1000,
31
+ }
32
+ let obj = {
33
+ a: ['\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9', 'abc'],
34
+ b: 'xyz',
35
+ c: true,
36
+ d: null,
37
+ e: '\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9',
38
+ f: 1000,
39
+ }
40
+ let converted = _recursiveParseHexString(obj)
41
+ expect(converted).toEqual(target)
42
+ })
43
+ })
package/test/test.spec.js CHANGED
@@ -20,6 +20,26 @@ describe('ldap-authentication test', () => {
20
20
  expect(user).toBeTruthy()
21
21
  expect(user.uid).toEqual('gauss')
22
22
  })
23
+ it('Use an admin user to check if user exists and return attributes', async () => {
24
+ let options = {
25
+ ldapOpts: {
26
+ url: url,
27
+ },
28
+ adminDn: 'cn=read-only-admin,dc=example,dc=com',
29
+ adminPassword: 'password',
30
+ verifyUserExists: true,
31
+ userSearchBase: 'dc=example,dc=com',
32
+ usernameAttribute: 'uid',
33
+ username: 'gauss',
34
+ attributes: ['uid', 'sn'],
35
+ }
36
+
37
+ let user = await authenticate(options)
38
+ expect(user).toBeTruthy()
39
+ expect(user.uid).toEqual('gauss')
40
+ expect(user.sn).toEqual('Bar1')
41
+ expect(user.cn).toBeUndefined()
42
+ })
23
43
  it('Use an admin user to authenticate a regular user', async () => {
24
44
  let options = {
25
45
  ldapOpts: {
@@ -37,7 +57,7 @@ describe('ldap-authentication test', () => {
37
57
  expect(user).toBeTruthy()
38
58
  expect(user.uid).toEqual('gauss')
39
59
  })
40
- it('Use an admin user to authenticate a regular user and return attrubutes', async () => {
60
+ it('Use an admin user to authenticate a regular user and return attributes', async () => {
41
61
  let options = {
42
62
  ldapOpts: {
43
63
  url: url,
@@ -124,6 +144,9 @@ describe('ldap-authentication test', () => {
124
144
  let user = await authenticate(options)
125
145
  expect(user).toBeTruthy()
126
146
  expect(user.groups.length).toBeGreaterThan(0)
147
+ expect(user.groups[0].objectName).toEqual(
148
+ 'cn=科学A部,ou=users,dc=example,dc=com'
149
+ )
127
150
  })
128
151
  it('Use regular user to authenticate and fetch user group information', async () => {
129
152
  let options = {
@@ -144,6 +167,9 @@ describe('ldap-authentication test', () => {
144
167
  let user = await authenticate(options)
145
168
  expect(user).toBeTruthy()
146
169
  expect(user.groups.length).toBeGreaterThan(0)
170
+ expect(user.groups[0].objectName).toEqual(
171
+ 'cn=科学A部,ou=users,dc=example,dc=com'
172
+ )
147
173
  })
148
174
  it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
149
175
  let options = {