ldap-authentication 3.2.6 → 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 +3 -3
- package/example/index.js +2 -2
- package/index.d.ts +1 -1
- package/index.js +125 -197
- package/package.json +4 -3
- package/test/string.spec.js +12 -53
- package/test/test.spec.js +3 -3
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 `
|
|
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 `
|
|
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
|
-
|
|
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://
|
|
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
package/index.js
CHANGED
|
@@ -1,148 +1,57 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
|
-
const
|
|
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
|
-
// convert a SearchResultEntry object in ldapjs 3.0
|
|
82
|
-
// to a user object to maintain backward compatibility
|
|
83
|
-
|
|
84
|
-
function _searchResultToUser(pojo) {
|
|
85
|
-
assert(pojo.type == 'SearchResultEntry')
|
|
86
|
-
let user = { dn: pojo.objectName }
|
|
87
|
-
pojo.attributes.forEach((attribute) => {
|
|
88
|
-
user[attribute.type] =
|
|
89
|
-
attribute.values.length == 1 ? attribute.values[0] : attribute.values
|
|
90
|
-
})
|
|
91
|
-
return _recursiveParseHexString(user)
|
|
92
|
-
}
|
|
93
41
|
// bind and return the ldap client
|
|
94
|
-
function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
42
|
+
async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
43
|
+
// TODO: check if ldapts expects escaped dn or not (possible double escaping problems?)
|
|
95
44
|
dn = _ldapEscapeDN(dn)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let client = ldap.createClient(ldapOpts)
|
|
99
|
-
|
|
100
|
-
client.on('connect', function () {
|
|
101
|
-
if (starttls) {
|
|
102
|
-
client.starttls(ldapOpts.tlsOptions, null, function (error) {
|
|
103
|
-
if (error) {
|
|
104
|
-
reject(error)
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
client.bind(dn, password, function (err) {
|
|
108
|
-
if (err) {
|
|
109
|
-
reject(err)
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
113
|
-
resolve(client)
|
|
114
|
-
})
|
|
115
|
-
})
|
|
116
|
-
} else {
|
|
117
|
-
client.bind(dn, password, function (err) {
|
|
118
|
-
if (err) {
|
|
119
|
-
reject(err)
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
123
|
-
resolve(client)
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
})
|
|
45
|
+
ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
46
|
+
let client = new ldapts.Client(ldapOpts)
|
|
127
47
|
|
|
128
|
-
|
|
129
|
-
client.
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
client.on('connectTimeout', (err) => {
|
|
133
|
-
reject(err)
|
|
134
|
-
})
|
|
135
|
-
client.on('error', (err) => {
|
|
136
|
-
reject(err)
|
|
137
|
-
})
|
|
48
|
+
if (starttls) {
|
|
49
|
+
await client.startTLS(ldapOpts.tlsOptions)
|
|
50
|
+
}
|
|
138
51
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
})
|
|
52
|
+
await client.bind(dn, password)
|
|
53
|
+
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
54
|
+
return client
|
|
146
55
|
}
|
|
147
56
|
|
|
148
57
|
// search a user and return the object
|
|
@@ -153,48 +62,43 @@ async function _searchUser(
|
|
|
153
62
|
username,
|
|
154
63
|
attributes = null
|
|
155
64
|
) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
value: username,
|
|
160
|
-
})
|
|
161
|
-
let searchOptions = {
|
|
162
|
-
filter: filter,
|
|
163
|
-
scope: 'sub',
|
|
164
|
-
attributes: attributes,
|
|
165
|
-
}
|
|
166
|
-
if (attributes) {
|
|
167
|
-
searchOptions.attributes = attributes
|
|
168
|
-
}
|
|
169
|
-
ldapClient.search(searchBase, searchOptions, function (err, res) {
|
|
170
|
-
let user = null
|
|
171
|
-
if (err) {
|
|
172
|
-
reject(err)
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
res.on('searchEntry', function (entry) {
|
|
176
|
-
user = _searchResultToUser(entry.pojo)
|
|
177
|
-
})
|
|
178
|
-
res.on('searchReference', function (referral) {
|
|
179
|
-
// TODO: we don't support reference yet
|
|
180
|
-
// If the server was able to locate the entry referred to by the baseObject
|
|
181
|
-
// but could not search one or more non-local entries,
|
|
182
|
-
// the server may return one or more SearchResultReference messages,
|
|
183
|
-
// each containing a reference to another set of servers for continuing the operation.
|
|
184
|
-
// referral.uris
|
|
185
|
-
})
|
|
186
|
-
res.on('error', function (err) {
|
|
187
|
-
reject(err)
|
|
188
|
-
})
|
|
189
|
-
res.on('end', function (result) {
|
|
190
|
-
if (result.status != 0) {
|
|
191
|
-
reject(new Error('ldap search status is not 0, search failed'))
|
|
192
|
-
} else {
|
|
193
|
-
resolve(user)
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
})
|
|
65
|
+
let filter = new ldapts.EqualityFilter({
|
|
66
|
+
attribute: usernameAttribute,
|
|
67
|
+
value: username,
|
|
197
68
|
})
|
|
69
|
+
let searchOptions = {
|
|
70
|
+
filter: filter,
|
|
71
|
+
scope: 'sub',
|
|
72
|
+
attributes: attributes,
|
|
73
|
+
}
|
|
74
|
+
if (attributes) {
|
|
75
|
+
searchOptions.attributes = attributes
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// TODO: we don't support reference yet
|
|
79
|
+
// If the server was able to locate the entry referred to by the baseObject
|
|
80
|
+
// but could not search one or more non-local entries,
|
|
81
|
+
// the server may return one or more SearchResultReference messages,
|
|
82
|
+
// each containing a reference to another set of servers for continuing the operation.
|
|
83
|
+
// referral.uris
|
|
84
|
+
const { searchEntries, searchReferences } = await ldapClient.search(
|
|
85
|
+
searchBase,
|
|
86
|
+
searchOptions
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
let user
|
|
90
|
+
if (
|
|
91
|
+
!searchEntries ||
|
|
92
|
+
searchEntries.length < 1 ||
|
|
93
|
+
!searchEntries[0] ||
|
|
94
|
+
!searchEntries[0].dn
|
|
95
|
+
) {
|
|
96
|
+
user = null
|
|
97
|
+
} else {
|
|
98
|
+
user = searchEntries[0]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return user
|
|
198
102
|
}
|
|
199
103
|
|
|
200
104
|
// search a groups which user is member
|
|
@@ -206,36 +110,43 @@ async function _searchUserGroups(
|
|
|
206
110
|
groupMemberAttribute = 'member',
|
|
207
111
|
groupMemberUserAttribute = 'dn'
|
|
208
112
|
) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
res.on('searchEntry', function (entry) {
|
|
223
|
-
groups.push(_recursiveParseHexString(entry.pojo))
|
|
224
|
-
})
|
|
225
|
-
res.on('searchReference', function (referral) {})
|
|
226
|
-
res.on('error', function (err) {
|
|
227
|
-
reject(err)
|
|
228
|
-
})
|
|
229
|
-
res.on('end', function (result) {
|
|
230
|
-
if (result.status != 0) {
|
|
231
|
-
reject(new Error('ldap search status is not 0, search failed'))
|
|
232
|
-
} else {
|
|
233
|
-
resolve(groups)
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
}
|
|
237
|
-
)
|
|
113
|
+
// Below works, but prefer using ldapts Filter subclasses to build this search, so that correct escaping is done
|
|
114
|
+
// const filter = `(&(objectclass=${groupClass})(${groupMemberAttribute}=${user[groupMemberUserAttribute]}))`
|
|
115
|
+
const filter = new ldapts.AndFilter({
|
|
116
|
+
filters: [
|
|
117
|
+
new ldapts.EqualityFilter({
|
|
118
|
+
attribute: 'objectclass',
|
|
119
|
+
value: groupClass,
|
|
120
|
+
}),
|
|
121
|
+
new ldapts.EqualityFilter({
|
|
122
|
+
attribute: groupMemberAttribute,
|
|
123
|
+
value: user[groupMemberUserAttribute],
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
238
126
|
})
|
|
127
|
+
|
|
128
|
+
const { searchEntries, searchReferences } = await ldapClient.search(
|
|
129
|
+
searchBase,
|
|
130
|
+
{
|
|
131
|
+
filter: filter,
|
|
132
|
+
scope: 'sub',
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
let groups
|
|
137
|
+
if (!searchEntries || searchEntries.length < 1) {
|
|
138
|
+
groups = []
|
|
139
|
+
} else {
|
|
140
|
+
groups = searchEntries
|
|
141
|
+
}
|
|
142
|
+
// ldapjs has group.objectName, ldapts does not have it. instead, use dn
|
|
143
|
+
// add objectName back for backward compatibility
|
|
144
|
+
for (let group of groups) {
|
|
145
|
+
if (typeof group.objectName === 'undefined') {
|
|
146
|
+
group.objectName = group.dn
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return groups
|
|
239
150
|
}
|
|
240
151
|
|
|
241
152
|
async function authenticateWithAdmin(
|
|
@@ -262,6 +173,9 @@ async function authenticateWithAdmin(
|
|
|
262
173
|
ldapOpts
|
|
263
174
|
)
|
|
264
175
|
} catch (error) {
|
|
176
|
+
if (ldapAdminClient.isConnected) {
|
|
177
|
+
await ldapAdminClient.unbind()
|
|
178
|
+
}
|
|
265
179
|
throw { admin: error }
|
|
266
180
|
}
|
|
267
181
|
let user = await _searchUser(
|
|
@@ -276,6 +190,7 @@ async function authenticateWithAdmin(
|
|
|
276
190
|
ldapOpts.log.trace(
|
|
277
191
|
`admin did not find user! (${usernameAttribute}=${username})`
|
|
278
192
|
)
|
|
193
|
+
await ldapAdminClient.unbind()
|
|
279
194
|
throw new LdapAuthenticationError(
|
|
280
195
|
'user not found or usernameAttribute is wrong'
|
|
281
196
|
)
|
|
@@ -285,6 +200,9 @@ async function authenticateWithAdmin(
|
|
|
285
200
|
try {
|
|
286
201
|
ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
|
|
287
202
|
} catch (error) {
|
|
203
|
+
if (ldapUserClient.isConnected) {
|
|
204
|
+
await ldapUserClient.unbind()
|
|
205
|
+
}
|
|
288
206
|
throw error
|
|
289
207
|
}
|
|
290
208
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
@@ -298,6 +216,8 @@ async function authenticateWithAdmin(
|
|
|
298
216
|
)
|
|
299
217
|
user.groups = groups
|
|
300
218
|
}
|
|
219
|
+
await ldapAdminClient.unbind()
|
|
220
|
+
await ldapUserClient.unbind()
|
|
301
221
|
return user
|
|
302
222
|
}
|
|
303
223
|
|
|
@@ -319,10 +239,14 @@ async function authenticateWithUser(
|
|
|
319
239
|
try {
|
|
320
240
|
ldapUserClient = await _ldapBind(userDn, userPassword, starttls, ldapOpts)
|
|
321
241
|
} catch (error) {
|
|
242
|
+
if (ldapUserClient.isConnected) {
|
|
243
|
+
await ldapUserClient.unbind()
|
|
244
|
+
}
|
|
322
245
|
throw error
|
|
323
246
|
}
|
|
324
247
|
if (!usernameAttribute || !userSearchBase) {
|
|
325
248
|
// if usernameAttribute is not provided, no user detail is needed.
|
|
249
|
+
await ldapUserClient.unbind()
|
|
326
250
|
return true
|
|
327
251
|
}
|
|
328
252
|
let user = await _searchUser(
|
|
@@ -337,6 +261,7 @@ async function authenticateWithUser(
|
|
|
337
261
|
ldapOpts.log.trace(
|
|
338
262
|
`user logged in, but user details could not be found. (${usernameAttribute}=${username}). Probabaly wrong attribute or searchBase?`
|
|
339
263
|
)
|
|
264
|
+
await ldapUserClient.unbind()
|
|
340
265
|
throw new LdapAuthenticationError(
|
|
341
266
|
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?'
|
|
342
267
|
)
|
|
@@ -352,6 +277,7 @@ async function authenticateWithUser(
|
|
|
352
277
|
)
|
|
353
278
|
user.groups = groups
|
|
354
279
|
}
|
|
280
|
+
await ldapUserClient.unbind()
|
|
355
281
|
return user
|
|
356
282
|
}
|
|
357
283
|
|
|
@@ -378,6 +304,9 @@ async function verifyUserExists(
|
|
|
378
304
|
ldapOpts
|
|
379
305
|
)
|
|
380
306
|
} catch (error) {
|
|
307
|
+
if (ldapAdminClient.isConnected) {
|
|
308
|
+
await ldapAdminClient.unbind()
|
|
309
|
+
}
|
|
381
310
|
throw { admin: error }
|
|
382
311
|
}
|
|
383
312
|
let user = await _searchUser(
|
|
@@ -392,6 +321,7 @@ async function verifyUserExists(
|
|
|
392
321
|
ldapOpts.log.trace(
|
|
393
322
|
`admin did not find user! (${usernameAttribute}=${username})`
|
|
394
323
|
)
|
|
324
|
+
await ldapAdminClient.unbind()
|
|
395
325
|
throw new LdapAuthenticationError(
|
|
396
326
|
'user not found or usernameAttribute is wrong'
|
|
397
327
|
)
|
|
@@ -407,6 +337,7 @@ async function verifyUserExists(
|
|
|
407
337
|
)
|
|
408
338
|
user.groups = groups
|
|
409
339
|
}
|
|
340
|
+
await ldapAdminClient.unbind()
|
|
410
341
|
return user
|
|
411
342
|
}
|
|
412
343
|
|
|
@@ -503,8 +434,5 @@ module.exports.authenticate = authenticate
|
|
|
503
434
|
module.exports.LdapAuthenticationError = LdapAuthenticationError
|
|
504
435
|
|
|
505
436
|
module.exports.exportForTesting = {
|
|
506
|
-
|
|
507
|
-
_parseEscapedHexToUtf8,
|
|
508
|
-
_recursiveParseHexString,
|
|
509
|
-
_ldapEscapeDN
|
|
437
|
+
_ldapEscapeDN,
|
|
510
438
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-authentication",
|
|
3
|
-
"version": "3.2
|
|
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",
|
|
@@ -36,9 +37,9 @@
|
|
|
36
37
|
},
|
|
37
38
|
"homepage": "https://github.com/shaozi/ldap-authentication#readme",
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
+
"ldapts": "^7.3.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"jasmine": "^5.
|
|
43
|
+
"jasmine": "^5.6.0"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/test/string.spec.js
CHANGED
|
@@ -1,62 +1,21 @@
|
|
|
1
1
|
const { exportForTesting } = require('../index.js')
|
|
2
|
-
const {
|
|
3
|
-
exportForTesting
|
|
2
|
+
const { _ldapEscapeDN } = exportForTesting
|
|
4
3
|
|
|
5
|
-
describe('
|
|
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:
|
|
48
|
-
{ s:
|
|
49
|
-
{ s:
|
|
50
|
-
{ s:
|
|
51
|
-
{ s:
|
|
52
|
-
{ s:
|
|
53
|
-
{ s:
|
|
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(
|
|
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
|
+
|
package/test/test.spec.js
CHANGED
|
@@ -144,9 +144,7 @@ describe('ldap-authentication test', () => {
|
|
|
144
144
|
let user = await authenticate(options)
|
|
145
145
|
expect(user).toBeTruthy()
|
|
146
146
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
147
|
-
expect(user.groups[0].
|
|
148
|
-
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
149
|
-
)
|
|
147
|
+
expect(user.groups[0].dn).toEqual('cn=科学A部,ou=users,dc=example,dc=com')
|
|
150
148
|
})
|
|
151
149
|
it('Use regular user to authenticate and fetch user group information', async () => {
|
|
152
150
|
let options = {
|
|
@@ -167,6 +165,8 @@ describe('ldap-authentication test', () => {
|
|
|
167
165
|
let user = await authenticate(options)
|
|
168
166
|
expect(user).toBeTruthy()
|
|
169
167
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
168
|
+
expect(user.groups[0].dn).toEqual('cn=科学A部,ou=users,dc=example,dc=com')
|
|
169
|
+
// backward compatible with 3.2
|
|
170
170
|
expect(user.groups[0].objectName).toEqual(
|
|
171
171
|
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
172
172
|
)
|