ldap-authentication 3.3.1 → 3.3.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.
package/README.md CHANGED
@@ -10,7 +10,7 @@ Make authentication with an LDAP server easy.
10
10
 
11
11
  ## Description
12
12
 
13
- This library use `ldapjs` as the underneath library. It has three modes of authentications:
13
+ This library use `ldapts` as the underneath library. It has three modes of authentications:
14
14
 
15
15
  1. **Admin authenticate mode**. If an admin user is provided, the library will login (ldap bind) with the admin user,
16
16
  then search for the user to be authenticated, get its DN (distinguish name), then use
@@ -149,7 +149,7 @@ auth()
149
149
 
150
150
  ## Parameters
151
151
 
152
- - `ldapOpts`: This is passed to `ldapjs` client directly
152
+ - `ldapOpts`: This is passed to `ldapts` client directly
153
153
  - `url`: url of the ldap server. Example: `ldap://ldap.forumsys.com`
154
154
  - `tlsOptions`: options to pass to node tls. Example: `{ rejectUnauthorized: false }`
155
155
  - `connectTimeout`: Int. Default: `5000`. Connect timeout in ms
@@ -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,${profilePhoto}`} />
213
+ <img src={`data:image/*;base64,${profilePhoto}`} />
214
214
  */
215
215
  return { user: ldapUser };
216
216
  }
package/example/index.js CHANGED
@@ -4,7 +4,7 @@ async function auth() {
4
4
  // auth with admin
5
5
  let options = {
6
6
  ldapOpts: {
7
- url: 'ldap://ldap.forumsys.com',
7
+ url: 'ldap://localhost:1389',
8
8
  // tlsOptions: { rejectUnauthorized: false }
9
9
  },
10
10
  adminDn: 'cn=read-only-admin,dc=example,dc=com',
@@ -55,4 +55,4 @@ async function auth() {
55
55
  console.log(`user = ${JSON.stringify(user, null, 2)}`)
56
56
  }
57
57
 
58
- auth()
58
+ auth().then()
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ClientOptions } from 'ldapjs'
1
+ import { ClientOptions } from 'ldapts'
2
2
 
3
3
  declare module 'ldap-authentication' {
4
4
  export interface AuthenticationOptions {
package/index.js CHANGED
@@ -2,97 +2,42 @@ const assert = require('assert')
2
2
  const ldapts = require('ldapts')
3
3
  // escape the , in CN in DN
4
4
  function _ldapEscapeDN(s) {
5
- let ret = "";
6
- let comaPositions = [];
7
- let done = false;
8
- let countEq = 0;
5
+ let ret = ''
6
+ let comaPositions = []
7
+ let done = false
8
+ let countEq = 0
9
9
  for (let i = 0; !done && i < s.length; i++) {
10
10
  switch (s[i]) {
11
- case "\\":
11
+ case '\\':
12
12
  // user already escapped, continue
13
- i++;
14
- break;
15
- case ",":
13
+ i++
14
+ break
15
+ case ',':
16
16
  if (countEq == 1) {
17
- comaPositions.push(i);
17
+ comaPositions.push(i)
18
18
  }
19
- break;
20
- case "=":
21
- countEq++;
19
+ break
20
+ case '=':
21
+ countEq++
22
22
  if (countEq == 2) {
23
- done = true;
23
+ done = true
24
24
  }
25
- break;
25
+ break
26
26
  }
27
27
  }
28
28
  if (done) {
29
- comaPositions.pop();
29
+ comaPositions.pop()
30
30
  }
31
- let lastIndex = 0;
31
+ let lastIndex = 0
32
32
  for (let i of comaPositions) {
33
- ret += s.substring(lastIndex, i);
34
- ret += "\\,";
35
- lastIndex = i + 1;
33
+ ret += s.substring(lastIndex, i)
34
+ ret += '\\,'
35
+ lastIndex = i + 1
36
36
  }
37
- ret += s.substring(lastIndex);
38
- return ret;
37
+ ret += s.substring(lastIndex)
38
+ return ret
39
39
  }
40
40
 
41
- // convert an escaped utf8 string returned from ldapjs
42
- function _isHex(c) {
43
- return (
44
- (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
45
- )
46
- }
47
- function _parseEscapedHexToUtf8(s) {
48
- // convert 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
49
- // to 'cn=研发A部,ou=users,dc=example,dc=com'
50
- let ret = Buffer.alloc(0)
51
- let len = s.length
52
- for (let i = 0; i < len; i++) {
53
- let c = s[i]
54
- let item
55
- if (c == '\\' && i < len - 2 && _isHex(s[i + 1]) && _isHex(s[i + 2])) {
56
- item = Buffer.from(s.substring(i + 1, i + 3), 'hex')
57
- i += 2
58
- } else {
59
- item = Buffer.from(c)
60
- }
61
- ret = Buffer.concat([ret, item])
62
- }
63
- return ret.toString()
64
- }
65
-
66
- function _recursiveParseHexString(obj) {
67
- if (Array.isArray(obj)) {
68
- return obj.map((ele) => _recursiveParseHexString(ele))
69
- }
70
- if (typeof obj == 'string') {
71
- return _parseEscapedHexToUtf8(obj)
72
- }
73
- if (typeof obj == 'object') {
74
- for (let key in obj) {
75
- obj[key] = _recursiveParseHexString(obj[key])
76
- }
77
- return obj
78
- }
79
- return obj
80
- }
81
- /*
82
- // UPDATE: This function is not used in this version, because ldapts library already does the conversion
83
- // convert a SearchResultEntry object in ldapjs 3.0
84
- // to a user object to maintain backward compatibility
85
-
86
- function _searchResultToUser(pojo) {
87
- assert(pojo.type == 'SearchResultEntry')
88
- let user = { dn: pojo.objectName }
89
- pojo.attributes.forEach((attribute) => {
90
- user[attribute.type] =
91
- attribute.values.length == 1 ? attribute.values[0] : attribute.values
92
- })
93
- return _recursiveParseHexString(user)
94
- }
95
- */
96
41
  // bind and return the ldap client
97
42
  async function _ldapBind(dn, password, starttls, ldapOpts) {
98
43
  // TODO: check if ldapts expects escaped dn or not (possible double escaping problems?)
@@ -100,7 +45,7 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
100
45
  ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
101
46
  let client = new ldapts.Client(ldapOpts)
102
47
 
103
- if(starttls) {
48
+ if (starttls) {
104
49
  await client.startTLS(ldapOpts.tlsOptions)
105
50
  }
106
51
 
@@ -117,18 +62,17 @@ async function _searchUser(
117
62
  username,
118
63
  attributes = null
119
64
  ) {
120
-
121
65
  let filter = new ldapts.EqualityFilter({
122
66
  attribute: usernameAttribute,
123
67
  value: username,
124
- });
68
+ })
125
69
  let searchOptions = {
126
70
  filter: filter,
127
71
  scope: 'sub',
128
72
  attributes: attributes,
129
- };
73
+ }
130
74
  if (attributes) {
131
- searchOptions.attributes = attributes;
75
+ searchOptions.attributes = attributes
132
76
  }
133
77
 
134
78
  // TODO: we don't support reference yet
@@ -137,16 +81,24 @@ async function _searchUser(
137
81
  // the server may return one or more SearchResultReference messages,
138
82
  // each containing a reference to another set of servers for continuing the operation.
139
83
  // referral.uris
140
- const { searchEntries, searchReferences } = await ldapClient.search(searchBase, searchOptions);
84
+ const { searchEntries, searchReferences } = await ldapClient.search(
85
+ searchBase,
86
+ searchOptions
87
+ )
141
88
 
142
- let user;
143
- if(!searchEntries || searchEntries.length < 1 || !searchEntries[0] || !searchEntries[0].dn) {
144
- user = null;
89
+ let user
90
+ if (
91
+ !searchEntries ||
92
+ searchEntries.length < 1 ||
93
+ !searchEntries[0] ||
94
+ !searchEntries[0].dn
95
+ ) {
96
+ user = null
145
97
  } else {
146
- user = searchEntries[0];
98
+ user = searchEntries[0]
147
99
  }
148
100
 
149
- return user;
101
+ return user
150
102
  }
151
103
 
152
104
  // search a groups which user is member
@@ -162,10 +114,16 @@ async function _searchUserGroups(
162
114
  // const filter = `(&(objectclass=${groupClass})(${groupMemberAttribute}=${user[groupMemberUserAttribute]}))`
163
115
  const filter = new ldapts.AndFilter({
164
116
  filters: [
165
- new ldapts.EqualityFilter({ attribute: 'objectclass', value: groupClass }),
166
- new ldapts.EqualityFilter({ attribute: groupMemberAttribute, value: user[groupMemberUserAttribute] }),
117
+ new ldapts.EqualityFilter({
118
+ attribute: 'objectclass',
119
+ value: groupClass,
120
+ }),
121
+ new ldapts.EqualityFilter({
122
+ attribute: groupMemberAttribute,
123
+ value: user[groupMemberUserAttribute],
124
+ }),
167
125
  ],
168
- });
126
+ })
169
127
 
170
128
  const { searchEntries, searchReferences } = await ldapClient.search(
171
129
  searchBase,
@@ -173,13 +131,13 @@ async function _searchUserGroups(
173
131
  filter: filter,
174
132
  scope: 'sub',
175
133
  }
176
- );
134
+ )
177
135
 
178
- let groups;
179
- if(!searchEntries || searchEntries.length < 1) {
180
- groups = [];
136
+ let groups
137
+ if (!searchEntries || searchEntries.length < 1) {
138
+ groups = []
181
139
  } else {
182
- groups = searchEntries;
140
+ groups = searchEntries
183
141
  }
184
142
  // ldapjs has group.objectName, ldapts does not have it. instead, use dn
185
143
  // add objectName back for backward compatibility
@@ -188,7 +146,7 @@ async function _searchUserGroups(
188
146
  group.objectName = group.dn
189
147
  }
190
148
  }
191
- return groups;
149
+ return groups
192
150
  }
193
151
 
194
152
  async function authenticateWithAdmin(
@@ -215,6 +173,9 @@ async function authenticateWithAdmin(
215
173
  ldapOpts
216
174
  )
217
175
  } catch (error) {
176
+ if (ldapAdminClient.isConnected) {
177
+ await ldapAdminClient.unbind()
178
+ }
218
179
  throw { admin: error }
219
180
  }
220
181
  let user = await _searchUser(
@@ -229,6 +190,7 @@ async function authenticateWithAdmin(
229
190
  ldapOpts.log.trace(
230
191
  `admin did not find user! (${usernameAttribute}=${username})`
231
192
  )
193
+ await ldapAdminClient.unbind()
232
194
  throw new LdapAuthenticationError(
233
195
  'user not found or usernameAttribute is wrong'
234
196
  )
@@ -238,6 +200,9 @@ async function authenticateWithAdmin(
238
200
  try {
239
201
  ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
240
202
  } catch (error) {
203
+ if (ldapUserClient.isConnected) {
204
+ await ldapUserClient.unbind()
205
+ }
241
206
  throw error
242
207
  }
243
208
  if (groupsSearchBase && groupClass && groupMemberAttribute) {
@@ -251,6 +216,8 @@ async function authenticateWithAdmin(
251
216
  )
252
217
  user.groups = groups
253
218
  }
219
+ await ldapAdminClient.unbind()
220
+ await ldapUserClient.unbind()
254
221
  return user
255
222
  }
256
223
 
@@ -272,10 +239,14 @@ async function authenticateWithUser(
272
239
  try {
273
240
  ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
274
241
  } catch (error) {
242
+ if (ldapUserClient.isConnected) {
243
+ await ldapUserClient.unbind()
244
+ }
275
245
  throw error
276
246
  }
277
247
  if (!usernameAttribute || !userSearchBase) {
278
248
  // if usernameAttribute is not provided, no user detail is needed.
249
+ await ldapUserClient.unbind()
279
250
  return true
280
251
  }
281
252
  let user = await _searchUser(
@@ -290,6 +261,7 @@ async function authenticateWithUser(
290
261
  ldapOpts.log.trace(
291
262
  `user logged in, but user details could not be found. (${usernameAttribute}=${username}). Probabaly wrong attribute or searchBase?`
292
263
  )
264
+ await ldapUserClient.unbind()
293
265
  throw new LdapAuthenticationError(
294
266
  'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?'
295
267
  )
@@ -305,6 +277,7 @@ async function authenticateWithUser(
305
277
  )
306
278
  user.groups = groups
307
279
  }
280
+ await ldapUserClient.unbind()
308
281
  return user
309
282
  }
310
283
 
@@ -331,6 +304,9 @@ async function verifyUserExists(
331
304
  ldapOpts
332
305
  )
333
306
  } catch (error) {
307
+ if (ldapAdminClient.isConnected) {
308
+ await ldapAdminClient.unbind()
309
+ }
334
310
  throw { admin: error }
335
311
  }
336
312
  let user = await _searchUser(
@@ -345,6 +321,7 @@ async function verifyUserExists(
345
321
  ldapOpts.log.trace(
346
322
  `admin did not find user! (${usernameAttribute}=${username})`
347
323
  )
324
+ await ldapAdminClient.unbind()
348
325
  throw new LdapAuthenticationError(
349
326
  'user not found or usernameAttribute is wrong'
350
327
  )
@@ -360,6 +337,7 @@ async function verifyUserExists(
360
337
  )
361
338
  user.groups = groups
362
339
  }
340
+ await ldapAdminClient.unbind()
363
341
  return user
364
342
  }
365
343
 
@@ -456,8 +434,5 @@ module.exports.authenticate = authenticate
456
434
  module.exports.LdapAuthenticationError = LdapAuthenticationError
457
435
 
458
436
  module.exports.exportForTesting = {
459
- _isHex,
460
- _parseEscapedHexToUtf8,
461
- _recursiveParseHexString,
462
- _ldapEscapeDN
437
+ _ldapEscapeDN,
463
438
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ldap-authentication",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "A simple async nodejs library for LDAP user authentication",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
@@ -16,6 +16,7 @@
16
16
  "authenticate",
17
17
  "authentication",
18
18
  "ldapjs",
19
+ "ldapts",
19
20
  "security",
20
21
  "simple",
21
22
  "lightweight",
@@ -1,62 +1,21 @@
1
1
  const { exportForTesting } = require('../index.js')
2
- const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString, _ldapEscapeDN } =
3
- exportForTesting
2
+ const { _ldapEscapeDN } = exportForTesting
4
3
 
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
- })
44
-
45
- describe('escape , in the DN test', ()=>{
4
+ describe('escape , in the DN test', () => {
46
5
  let cases = [
47
- { s: "a", want: "a" },
48
- { s: "", want: "" },
49
- { s: "CN=a,DN=b", want: "CN=a,DN=b" },
50
- { s: "CN=a, c,DN=b", want: "CN=a\\, c,DN=b" },
51
- { s: "CN=a\\, c,DN=b", want: "CN=a\\, c,DN=b" },
52
- { s: "CN=a, b, c,DN=b", want: "CN=a\\, b\\, c,DN=b" },
53
- { s: "CN=a, c", want: "CN=a\\, c" },
6
+ { s: 'a', want: 'a' },
7
+ { s: '', want: '' },
8
+ { s: 'CN=a,DN=b', want: 'CN=a,DN=b' },
9
+ { s: 'CN=a, c,DN=b', want: 'CN=a\\, c,DN=b' },
10
+ { s: 'CN=a\\, c,DN=b', want: 'CN=a\\, c,DN=b' },
11
+ { s: 'CN=a, b, c,DN=b', want: 'CN=a\\, b\\, c,DN=b' },
12
+ { s: 'CN=a, c', want: 'CN=a\\, c' },
54
13
  ]
55
14
  for (let c of cases) {
56
- it("escape "+c.s, ()=>{
15
+ it('escape ' + c.s, () => {
57
16
  let got = _ldapEscapeDN(c.s)
58
17
  expect(got).toEqual(c.want)
59
18
  })
60
-
61
19
  }
62
- })
20
+ })
21
+