ldap-authentication 3.1.0 → 3.2.2

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部
@@ -16,7 +16,7 @@ jobs:
16
16
  runs-on: ubuntu-latest
17
17
  strategy:
18
18
  matrix:
19
- node-version: [16.x, 17.x, 18.x, 20.x]
19
+ node-version: [16.x, 17.x, 18.x, 20.x, 22.x]
20
20
 
21
21
  name: 'Integration Node v${{ matrix.node-version }}'
22
22
 
@@ -10,6 +10,13 @@
10
10
  "name": "Debug Example",
11
11
  "skipFiles": ["<node_internals>/**"],
12
12
  "program": "${workspaceFolder}/example/index.js"
13
+ },
14
+ {
15
+ "type": "node",
16
+ "request": "launch",
17
+ "name": "Debug Test",
18
+ "skipFiles": ["<node_internals>/**"],
19
+ "program": "${workspaceFolder}/test/jasmine.js"
13
20
  }
14
21
  ]
15
22
  }
package/README.md CHANGED
@@ -221,4 +221,4 @@ export async function verifyLogin(email: string, password: string) {
221
221
 
222
222
  Version 2 supports Node version 12, 14, 15, 16, 17 and 18.
223
223
 
224
- Version 3 supports Node version 15, 16, 17 and 18
224
+ Version 3 supports Node version 16, 17, 18, 20 and 22,
@@ -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,13 +51,13 @@ 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) {
18
58
  return new Promise(function (resolve, reject) {
19
59
  ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
20
- var client = ldap.createClient(ldapOpts)
60
+ let client = ldap.createClient(ldapOpts)
21
61
 
22
62
  client.on('connect', function () {
23
63
  if (starttls) {
@@ -29,7 +69,6 @@ function _ldapBind(dn, password, starttls, ldapOpts) {
29
69
  client.bind(dn, password, function (err) {
30
70
  if (err) {
31
71
  reject(err)
32
- client.unbind()
33
72
  return
34
73
  }
35
74
  ldapOpts.log && ldapOpts.log.trace('bind success!')
@@ -40,7 +79,6 @@ function _ldapBind(dn, password, starttls, ldapOpts) {
40
79
  client.bind(dn, password, function (err) {
41
80
  if (err) {
42
81
  reject(err)
43
- client.unbind()
44
82
  return
45
83
  }
46
84
  ldapOpts.log && ldapOpts.log.trace('bind success!')
@@ -78,7 +116,7 @@ async function _searchUser(
78
116
  attributes = null
79
117
  ) {
80
118
  return new Promise(function (resolve, reject) {
81
- var filter = new ldap.filters.EqualityFilter({
119
+ let filter = new ldap.filters.EqualityFilter({
82
120
  attribute: usernameAttribute,
83
121
  value: username,
84
122
  })
@@ -91,10 +129,9 @@ async function _searchUser(
91
129
  searchOptions.attributes = attributes
92
130
  }
93
131
  ldapClient.search(searchBase, searchOptions, function (err, res) {
94
- var user = null
132
+ let user = null
95
133
  if (err) {
96
134
  reject(err)
97
- ldapClient.unbind()
98
135
  return
99
136
  }
100
137
  res.on('searchEntry', function (entry) {
@@ -110,7 +147,6 @@ async function _searchUser(
110
147
  })
111
148
  res.on('error', function (err) {
112
149
  reject(err)
113
- ldapClient.unbind()
114
150
  })
115
151
  res.on('end', function (result) {
116
152
  if (result.status != 0) {
@@ -118,7 +154,6 @@ async function _searchUser(
118
154
  } else {
119
155
  resolve(user)
120
156
  }
121
- ldapClient.unbind()
122
157
  })
123
158
  })
124
159
  })
@@ -141,19 +176,17 @@ async function _searchUserGroups(
141
176
  scope: 'sub',
142
177
  },
143
178
  function (err, res) {
144
- var groups = []
179
+ let groups = []
145
180
  if (err) {
146
181
  reject(err)
147
- ldapClient.unbind()
148
182
  return
149
183
  }
150
184
  res.on('searchEntry', function (entry) {
151
- groups.push(entry.pojo)
185
+ groups.push(_recursiveParseHexString(entry.pojo))
152
186
  })
153
187
  res.on('searchReference', function (referral) {})
154
188
  res.on('error', function (err) {
155
189
  reject(err)
156
- ldapClient.unbind()
157
190
  })
158
191
  res.on('end', function (result) {
159
192
  if (result.status != 0) {
@@ -161,7 +194,6 @@ async function _searchUserGroups(
161
194
  } else {
162
195
  resolve(groups)
163
196
  }
164
- ldapClient.unbind()
165
197
  })
166
198
  }
167
199
  )
@@ -183,7 +215,7 @@ async function authenticateWithAdmin(
183
215
  groupMemberUserAttribute = 'dn',
184
216
  attributes = null
185
217
  ) {
186
- var ldapAdminClient
218
+ let ldapAdminClient
187
219
  try {
188
220
  ldapAdminClient = await _ldapBind(
189
221
  adminDn,
@@ -194,14 +226,13 @@ async function authenticateWithAdmin(
194
226
  } catch (error) {
195
227
  throw { admin: error }
196
228
  }
197
- var user = await _searchUser(
229
+ let user = await _searchUser(
198
230
  ldapAdminClient,
199
231
  userSearchBase,
200
232
  usernameAttribute,
201
233
  username,
202
234
  attributes
203
235
  )
204
- ldapAdminClient.unbind()
205
236
  if (!user || !user.dn) {
206
237
  ldapOpts.log &&
207
238
  ldapOpts.log.trace(
@@ -211,26 +242,15 @@ async function authenticateWithAdmin(
211
242
  'user not found or usernameAttribute is wrong'
212
243
  )
213
244
  }
214
- var userDn = user.dn
245
+ let userDn = user.dn
215
246
  let ldapUserClient
216
247
  try {
217
248
  ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
218
249
  } catch (error) {
219
250
  throw error
220
251
  }
221
- ldapUserClient.unbind()
222
252
  if (groupsSearchBase && groupClass && groupMemberAttribute) {
223
- try {
224
- ldapAdminClient = await _ldapBind(
225
- adminDn,
226
- adminPassword,
227
- starttls,
228
- ldapOpts
229
- )
230
- } catch (error) {
231
- throw error
232
- }
233
- var groups = await _searchUserGroups(
253
+ let groups = await _searchUserGroups(
234
254
  ldapAdminClient,
235
255
  groupsSearchBase,
236
256
  user,
@@ -239,7 +259,6 @@ async function authenticateWithAdmin(
239
259
  groupMemberUserAttribute
240
260
  )
241
261
  user.groups = groups
242
- ldapAdminClient.unbind()
243
262
  }
244
263
  return user
245
264
  }
@@ -266,10 +285,9 @@ async function authenticateWithUser(
266
285
  }
267
286
  if (!usernameAttribute || !userSearchBase) {
268
287
  // if usernameAttribute is not provided, no user detail is needed.
269
- ldapUserClient.unbind()
270
288
  return true
271
289
  }
272
- var user = await _searchUser(
290
+ let user = await _searchUser(
273
291
  ldapUserClient,
274
292
  userSearchBase,
275
293
  usernameAttribute,
@@ -285,14 +303,8 @@ async function authenticateWithUser(
285
303
  'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?'
286
304
  )
287
305
  }
288
- ldapUserClient.unbind()
289
306
  if (groupsSearchBase && groupClass && groupMemberAttribute) {
290
- try {
291
- ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
292
- } catch (error) {
293
- throw error
294
- }
295
- var groups = await _searchUserGroups(
307
+ let groups = await _searchUserGroups(
296
308
  ldapUserClient,
297
309
  groupsSearchBase,
298
310
  user,
@@ -301,7 +313,6 @@ async function authenticateWithUser(
301
313
  groupMemberUserAttribute
302
314
  )
303
315
  user.groups = groups
304
- ldapUserClient.unbind()
305
316
  }
306
317
  return user
307
318
  }
@@ -320,7 +331,7 @@ async function verifyUserExists(
320
331
  groupMemberUserAttribute = 'dn',
321
332
  attributes = null
322
333
  ) {
323
- var ldapAdminClient
334
+ let ldapAdminClient
324
335
  try {
325
336
  ldapAdminClient = await _ldapBind(
326
337
  adminDn,
@@ -331,14 +342,13 @@ async function verifyUserExists(
331
342
  } catch (error) {
332
343
  throw { admin: error }
333
344
  }
334
- var user = await _searchUser(
345
+ let user = await _searchUser(
335
346
  ldapAdminClient,
336
347
  userSearchBase,
337
348
  usernameAttribute,
338
349
  username,
339
350
  attributes
340
351
  )
341
- ldapAdminClient.unbind()
342
352
  if (!user || !user.dn) {
343
353
  ldapOpts.log &&
344
354
  ldapOpts.log.trace(
@@ -349,17 +359,7 @@ async function verifyUserExists(
349
359
  )
350
360
  }
351
361
  if (groupsSearchBase && groupClass && groupMemberAttribute) {
352
- try {
353
- ldapAdminClient = await _ldapBind(
354
- adminDn,
355
- adminPassword,
356
- starttls,
357
- ldapOpts
358
- )
359
- } catch (error) {
360
- throw error
361
- }
362
- var groups = await _searchUserGroups(
362
+ let groups = await _searchUserGroups(
363
363
  ldapAdminClient,
364
364
  groupsSearchBase,
365
365
  user,
@@ -368,7 +368,6 @@ async function verifyUserExists(
368
368
  groupMemberUserAttribute
369
369
  )
370
370
  user.groups = groups
371
- ldapAdminClient.unbind()
372
371
  }
373
372
  return user
374
373
  }
@@ -463,3 +462,9 @@ class LdapAuthenticationError extends Error {
463
462
 
464
463
  module.exports.authenticate = authenticate
465
464
  module.exports.LdapAuthenticationError = LdapAuthenticationError
465
+
466
+ module.exports.exportForTesting = {
467
+ _isHex,
468
+ _parseEscapedHexToUtf8,
469
+ _recursiveParseHexString,
470
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-authentication",
3
- "version": "3.1.0",
3
+ "version": "3.2.2",
4
4
  "description": "A simple async nodejs library for LDAP user authentication",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
package/test/jasmine.js CHANGED
@@ -3,7 +3,7 @@ var jasmine = new Jasmine()
3
3
 
4
4
  jasmine.loadConfig({
5
5
  spec_dir: 'test',
6
- spec_files: ['test.js'],
6
+ spec_files: ['**/*[sS]pec.?(m)js'],
7
7
  random: false,
8
8
  seed: null,
9
9
  stopSpecOnExpectationFailure: false,
@@ -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
+ })
@@ -124,6 +124,9 @@ describe('ldap-authentication test', () => {
124
124
  let user = await authenticate(options)
125
125
  expect(user).toBeTruthy()
126
126
  expect(user.groups.length).toBeGreaterThan(0)
127
+ expect(user.groups[0].objectName).toEqual(
128
+ 'cn=科学A部,ou=users,dc=example,dc=com'
129
+ )
127
130
  })
128
131
  it('Use regular user to authenticate and fetch user group information', async () => {
129
132
  let options = {
@@ -144,6 +147,9 @@ describe('ldap-authentication test', () => {
144
147
  let user = await authenticate(options)
145
148
  expect(user).toBeTruthy()
146
149
  expect(user.groups.length).toBeGreaterThan(0)
150
+ expect(user.groups[0].objectName).toEqual(
151
+ 'cn=科学A部,ou=users,dc=example,dc=com'
152
+ )
147
153
  })
148
154
  it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
149
155
  let options = {