ldap-authentication 3.3.4 → 3.3.6
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/.devcontainer/docker-compose.yml +1 -1
- package/README.md +40 -1
- package/docker-compose.yml +1 -1
- package/index.d.ts +1 -0
- package/index.js +50 -11
- package/package.json +1 -1
- package/test/binary.spec.js +115 -0
- package/test/starttls.spec.js +126 -0
- package/test/test.spec.js +3 -3
package/README.md
CHANGED
|
@@ -147,6 +147,43 @@ async function auth() {
|
|
|
147
147
|
auth()
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
### Example with StartTLS
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
import { authenticate } from 'ldap-authentication'
|
|
154
|
+
|
|
155
|
+
async function auth() {
|
|
156
|
+
// auth with admin
|
|
157
|
+
let options = {
|
|
158
|
+
ldapOpts: {
|
|
159
|
+
url: 'ldap://ldap.example.com',
|
|
160
|
+
tlsOptions: {
|
|
161
|
+
rejectUnauthorized: false, // For self-signed certificates
|
|
162
|
+
minVersion: 'TLSv1.2',
|
|
163
|
+
servername: 'ldap.example.com' // For SNI (Server Name Indication)
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
starttls: true, // Enable StartTLS
|
|
167
|
+
adminDn: 'cn=admin,dc=example,dc=com',
|
|
168
|
+
adminPassword: 'password',
|
|
169
|
+
userPassword: 'password',
|
|
170
|
+
userSearchBase: 'dc=example,dc=com',
|
|
171
|
+
usernameAttribute: 'uid',
|
|
172
|
+
username: 'testuser'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let user = await authenticate(options)
|
|
176
|
+
console.log(user)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
auth()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Important Notes for StartTLS:**
|
|
183
|
+
- Use `ldap://` URLs with `starttls: true` (not `ldaps://`)
|
|
184
|
+
- For `ldaps://` URLs, omit `starttls` and the connection will use TLS from the start
|
|
185
|
+
- TLS options like `rejectUnauthorized`, `minVersion`, and `servername` can be specified in `ldapOpts.tlsOptions`
|
|
186
|
+
|
|
150
187
|
## Parameters
|
|
151
188
|
|
|
152
189
|
- `ldapOpts`: This is passed to `ldapts` client directly
|
|
@@ -172,7 +209,9 @@ auth()
|
|
|
172
209
|
to find the user and get user details in LDAP. Example: `some user input`
|
|
173
210
|
- `attributes`: A list of attributes of a user details to be returned from the LDAP server.
|
|
174
211
|
If is set to `[]` or ommited, all details will be returned. Example: `['sn', 'cn']`
|
|
175
|
-
- `starttls`: Boolean. Use `STARTTLS` or not
|
|
212
|
+
- `starttls`: Boolean. Use `STARTTLS` or not. When `true`, the connection will be upgraded to TLS
|
|
213
|
+
using the STARTTLS extended operation. TLS options can be specified in `ldapOpts.tlsOptions`.
|
|
214
|
+
Note: Use `starttls: true` with `ldap://` URLs, not `ldaps://` URLs
|
|
176
215
|
- `groupsSearchBase`: if specified with groupClass, will serve as search base for authenticated user groups
|
|
177
216
|
- `groupClass`: if specified with groupsSearchBase, will be used as objectClass in search filter for authenticated user groups
|
|
178
217
|
- `groupMemberAttribute`: if specified with groupClass and groupsSearchBase, will be used as member name (if not specified this defaults to `member`) in search filter for authenticated user groups
|
package/docker-compose.yml
CHANGED
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -43,7 +43,25 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
43
43
|
// TODO: check if ldapts expects escaped dn or not (possible double escaping problems?)
|
|
44
44
|
dn = _ldapEscapeDN(dn)
|
|
45
45
|
ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
// When using StartTLS, we need to exclude tlsOptions from the Client constructor
|
|
48
|
+
// and only pass them to the startTLS() method to avoid connection conflicts.
|
|
49
|
+
// According to ldapts documentation:
|
|
50
|
+
// - For LDAPS (ldaps://): pass tlsOptions to Client constructor
|
|
51
|
+
// - For StartTLS (ldap://): do NOT pass tlsOptions to Client constructor, only to startTLS()
|
|
52
|
+
// - For plain LDAP (ldap://): do NOT pass tlsOptions to Client constructor
|
|
53
|
+
let clientOpts = ldapOpts
|
|
54
|
+
const isLdaps = ldapOpts.url && ldapOpts.url.startsWith('ldaps://')
|
|
55
|
+
|
|
56
|
+
// Only pass tlsOptions to Client constructor if using ldaps:// protocol
|
|
57
|
+
// For ldap:// protocol (plain or StartTLS), exclude tlsOptions from constructor
|
|
58
|
+
if (!isLdaps && ldapOpts.tlsOptions) {
|
|
59
|
+
// Create a shallow copy of ldapOpts without tlsOptions for the Client constructor
|
|
60
|
+
const { tlsOptions, ...optsWithoutTls } = ldapOpts
|
|
61
|
+
clientOpts = optsWithoutTls
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let client = new ldapts.Client(clientOpts)
|
|
47
65
|
|
|
48
66
|
if (starttls) {
|
|
49
67
|
await client.startTLS(ldapOpts.tlsOptions)
|
|
@@ -60,7 +78,8 @@ async function _searchUser(
|
|
|
60
78
|
searchBase,
|
|
61
79
|
usernameAttribute,
|
|
62
80
|
username,
|
|
63
|
-
attributes = null
|
|
81
|
+
attributes = null,
|
|
82
|
+
explicitBufferAttributes = null
|
|
64
83
|
) {
|
|
65
84
|
let filter = new ldapts.EqualityFilter({
|
|
66
85
|
attribute: usernameAttribute,
|
|
@@ -74,6 +93,9 @@ async function _searchUser(
|
|
|
74
93
|
if (attributes) {
|
|
75
94
|
searchOptions.attributes = attributes
|
|
76
95
|
}
|
|
96
|
+
if(explicitBufferAttributes) {
|
|
97
|
+
searchOptions.explicitBufferAttributes = explicitBufferAttributes
|
|
98
|
+
}
|
|
77
99
|
|
|
78
100
|
// TODO: we don't support reference yet
|
|
79
101
|
// If the server was able to locate the entry referred to by the baseObject
|
|
@@ -106,6 +128,14 @@ async function _searchUser(
|
|
|
106
128
|
}
|
|
107
129
|
}
|
|
108
130
|
}
|
|
131
|
+
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
132
|
+
if (user != null && explicitBufferAttributes != null) {
|
|
133
|
+
for (let attr of explicitBufferAttributes) {
|
|
134
|
+
if (Buffer.isBuffer(user[attr])) {
|
|
135
|
+
user[attr] = user[attr].toString('base64')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
109
139
|
return user
|
|
110
140
|
}
|
|
111
141
|
|
|
@@ -170,7 +200,8 @@ async function authenticateWithAdmin(
|
|
|
170
200
|
groupClass,
|
|
171
201
|
groupMemberAttribute = 'member',
|
|
172
202
|
groupMemberUserAttribute = 'dn',
|
|
173
|
-
attributes = null
|
|
203
|
+
attributes = null,
|
|
204
|
+
explicitBufferAttributes = null
|
|
174
205
|
) {
|
|
175
206
|
let ldapAdminClient
|
|
176
207
|
try {
|
|
@@ -191,7 +222,8 @@ async function authenticateWithAdmin(
|
|
|
191
222
|
userSearchBase,
|
|
192
223
|
usernameAttribute,
|
|
193
224
|
username,
|
|
194
|
-
attributes
|
|
225
|
+
attributes,
|
|
226
|
+
explicitBufferAttributes
|
|
195
227
|
)
|
|
196
228
|
if (!user || !user.dn) {
|
|
197
229
|
ldapOpts.log &&
|
|
@@ -241,7 +273,8 @@ async function authenticateWithUser(
|
|
|
241
273
|
groupClass,
|
|
242
274
|
groupMemberAttribute = 'member',
|
|
243
275
|
groupMemberUserAttribute = 'dn',
|
|
244
|
-
attributes = null
|
|
276
|
+
attributes = null,
|
|
277
|
+
explicitBufferAttributes = null
|
|
245
278
|
) {
|
|
246
279
|
let ldapUserClient
|
|
247
280
|
try {
|
|
@@ -262,7 +295,8 @@ async function authenticateWithUser(
|
|
|
262
295
|
userSearchBase,
|
|
263
296
|
usernameAttribute,
|
|
264
297
|
username,
|
|
265
|
-
attributes
|
|
298
|
+
attributes,
|
|
299
|
+
explicitBufferAttributes
|
|
266
300
|
)
|
|
267
301
|
if (!user || !user.dn) {
|
|
268
302
|
ldapOpts.log &&
|
|
@@ -301,7 +335,8 @@ async function verifyUserExists(
|
|
|
301
335
|
groupClass,
|
|
302
336
|
groupMemberAttribute = 'member',
|
|
303
337
|
groupMemberUserAttribute = 'dn',
|
|
304
|
-
attributes = null
|
|
338
|
+
attributes = null,
|
|
339
|
+
explicitBufferAttributes = null
|
|
305
340
|
) {
|
|
306
341
|
let ldapAdminClient
|
|
307
342
|
try {
|
|
@@ -322,7 +357,8 @@ async function verifyUserExists(
|
|
|
322
357
|
userSearchBase,
|
|
323
358
|
usernameAttribute,
|
|
324
359
|
username,
|
|
325
|
-
attributes
|
|
360
|
+
attributes,
|
|
361
|
+
explicitBufferAttributes
|
|
326
362
|
)
|
|
327
363
|
if (!user || !user.dn) {
|
|
328
364
|
ldapOpts.log &&
|
|
@@ -384,7 +420,8 @@ async function authenticate(options) {
|
|
|
384
420
|
options.groupClass,
|
|
385
421
|
options.groupMemberAttribute,
|
|
386
422
|
options.groupMemberUserAttribute,
|
|
387
|
-
options.attributes
|
|
423
|
+
options.attributes,
|
|
424
|
+
options.explicitBufferAttributes
|
|
388
425
|
)
|
|
389
426
|
}
|
|
390
427
|
assert(options.userPassword, 'userPassword must be provided')
|
|
@@ -406,7 +443,8 @@ async function authenticate(options) {
|
|
|
406
443
|
options.groupClass,
|
|
407
444
|
options.groupMemberAttribute,
|
|
408
445
|
options.groupMemberUserAttribute,
|
|
409
|
-
options.attributes
|
|
446
|
+
options.attributes,
|
|
447
|
+
options.explicitBufferAttributes
|
|
410
448
|
)
|
|
411
449
|
}
|
|
412
450
|
assert(options.userDn, 'adminDn/adminPassword OR userDn must be provided')
|
|
@@ -422,7 +460,8 @@ async function authenticate(options) {
|
|
|
422
460
|
options.groupClass,
|
|
423
461
|
options.groupMemberAttribute,
|
|
424
462
|
options.groupMemberUserAttribute,
|
|
425
|
-
options.attributes
|
|
463
|
+
options.attributes,
|
|
464
|
+
options.explicitBufferAttributes
|
|
426
465
|
)
|
|
427
466
|
}
|
|
428
467
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const { Change } = require('ldapts')
|
|
2
|
+
const { authenticate, LdapAuthenticationError } = require('../index.js')
|
|
3
|
+
const ldapts = require('ldapts')
|
|
4
|
+
const { Attribute } = require('ldapts')
|
|
5
|
+
|
|
6
|
+
const url = process.env.INGITHUB ? 'ldap://localhost:1389' : 'ldap://ldap:1389'
|
|
7
|
+
|
|
8
|
+
describe('ldap-authentication binary attributes test', () => {
|
|
9
|
+
const jpegPhotoBase64 =
|
|
10
|
+
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCACBAGQDAREAAhEBAxEB/8QAGQAAAwEBAQAAAAAAAAAAAAAAAAECAwQF/8QALRAAAgEDAwMCBQQDAAAAAAAAAAECAxEhBBIxMkFRInEFE2GBsRU0QlJikaH/xAAXAQEBAQEAAAAAAAAAAAAAAAAAAQID/8QAGxEBAQEAAwEBAAAAAAAAAAAAAAERAhIhMUH/2gAMAwEAAhEDEQA/APMIAAKAgChp2AdwAAACBlAQUkmu5RkAAACuAAADsAZQDWQKRA7AL7gAEFCbAQDAYAAXAdwBYAogAAAAgokBgADAEr8Ab09JWqZcdq8yJpjb9Pa5qr/ROy4l6Ga6ZxfvgdoYiemqw6o49yoyAAACCiQKa4AAN9PpnWy3aP5Jbiya9ClRhBJRjby/Ji1qRqRQBMkuwMZSk2trdiys2PPqR2zaNspAAIKE0A+yA201H51TPSuSW4smvTilFJJWSOdbi0RTZQghPgK56nNwzXLXtvx4NxhiUAEABQ0nJpJcgejpkoR2rtyznWo3uRpE5S4i8hfRCrPKm0wLlUUUrgZSruXTD/oROZPISuat1s3GGRQgIKADp0kFNy8pYM2rJrshBQ4M1qNErojTOdFyjZTcfqhKWaqFPObuyAdeKlBfRhcZrT01JzXdWsXWc/U7XF5IVlqWm1waYczKhXKIKEB0aOW2uvDwZvxZ9ejbBlq+CLsZaWrMAckvSgpSW6DQVNKW6ObXRUTVd5BmuKp1M05s2USBmaABpS60Qeg41rqUXeMcszjW61auroliyi7sZaFk1YKirVnSVopyXkqajTNtSv3YouXU/oWM8nHLLZWENFCsBiaAB1aCj86tnpjlko9javBlUVY29SWO4VizNaiVBJcu/m5FJwvyFTGmqct+Como3Juz5EZrnZplLKhWAw5NDZxisW47gen8NpqOncsepmarrIBZwwOWunSf+L4ZmxqVnvI1puoU1nObs347BLS7FZLZ8x27sIwqQcJuMuUaRAGdKN5X8ZKHJu+Qrq02venpbNilnGbDB3UNXHUJ7dqmv4szfA46luVnTbf0ZNFyqUpxcKnpv2kUclWiqT9LvF8O5mtRNnZO+ChSTccAOzsGShVhSlullrhFHPObnNyfLKiAHQW2lKfngqxk3dgSyhwm4TUk3dPDIPV0OrhWltmlGr2fkxYrqrUVVg0+ez8GdHPGhLa0/uaRMFeW1K9wrd6e7vJ2XhFRjKnGqtlNtPyyTB5MrqV28nRFxnf3JgogSqp0FBKzRVZlAEKKyFCbjO6eUQexotaq6UKjtUXf+xz5ccWOmas91u2RKVjHZSlaTe210yoipqHNbY3UfyTVEI7nbyUc+r0vLiuFc1Kjz7WNItSxkmCShAAAgCeXewURbTusNBHs6LWRrpQqO1T8nOzGmtamnG1unK9gM4UknfgqNYxSzZsBuTtiOL2ZKPJ1lFU6zt0yymblRhYonuQD4KEAAOXIUkEa6f8AcQ90Sq92r2+5zioXWvY0jQqJkRXn/EeiHuywcS4NI//Z'
|
|
11
|
+
|
|
12
|
+
const baseOptions = {
|
|
13
|
+
ldapOpts: {
|
|
14
|
+
url: url,
|
|
15
|
+
},
|
|
16
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
17
|
+
adminPassword: 'password',
|
|
18
|
+
verifyUserExists: true,
|
|
19
|
+
userSearchBase: 'dc=example,dc=com',
|
|
20
|
+
usernameAttribute: 'uid',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
it('Add jpegPhoto attribute', async () => {
|
|
24
|
+
let client = new ldapts.Client({
|
|
25
|
+
...baseOptions.ldapOpts,
|
|
26
|
+
})
|
|
27
|
+
try {
|
|
28
|
+
await client.bind(baseOptions.adminDn, baseOptions.adminPassword)
|
|
29
|
+
|
|
30
|
+
// https://github.com/ldapts/ldapts/issues/12
|
|
31
|
+
await client.modify(
|
|
32
|
+
'cn=gauss,ou=users,dc=example,dc=com',
|
|
33
|
+
new Change({
|
|
34
|
+
operation: 'replace',
|
|
35
|
+
modification: new Attribute({
|
|
36
|
+
type: 'jpegPhoto',
|
|
37
|
+
values: [Buffer.from(jpegPhotoBase64, 'base64')],
|
|
38
|
+
}),
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
} finally {
|
|
42
|
+
await client.unbind()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('Should return broken jpegPhoto attribute (no attribute selection nor ;binary) - But it really depends on LDAP server, it is not always true. Sometimes a buffer is returned directly.', async () => {
|
|
47
|
+
let user = await authenticate({
|
|
48
|
+
...baseOptions,
|
|
49
|
+
username: 'gauss',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
expect(user).toBeTruthy()
|
|
53
|
+
expect(user.uid).toEqual('gauss')
|
|
54
|
+
expect(user.sn).toEqual('Bar1')
|
|
55
|
+
expect(typeof user.uidNumber === 'string').toBe(true)
|
|
56
|
+
expect(user.uidNumber).toEqual('1000')
|
|
57
|
+
|
|
58
|
+
expect(user.jpegPhoto).toBeDefined()
|
|
59
|
+
// some ldap server returns a string, some ldap server returns a buffer
|
|
60
|
+
expect(
|
|
61
|
+
typeof user.jpegPhoto === 'string' || Buffer.isBuffer(user.jpegPhoto)
|
|
62
|
+
).toBe(true)
|
|
63
|
+
if (typeof user.jpegPhoto === 'string') {
|
|
64
|
+
expect(user.jpegPhoto).not.toEqual(jpegPhotoBase64)
|
|
65
|
+
}
|
|
66
|
+
if (Buffer.isBuffer(user.jpegPhoto)) {
|
|
67
|
+
expect(
|
|
68
|
+
user.jpegPhoto.equals(Buffer.from(jpegPhotoBase64, 'base64'))
|
|
69
|
+
).toBeTrue()
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('Should return nothing in the base64 jpegPhoto (using ;binary)', async () => {
|
|
74
|
+
let user = await authenticate({
|
|
75
|
+
...baseOptions,
|
|
76
|
+
username: 'gauss',
|
|
77
|
+
attributes: ['uid', 'sn', 'jpegPhoto;binary'],
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(user).toBeTruthy()
|
|
81
|
+
expect(user.uid).toEqual('gauss')
|
|
82
|
+
expect(user.sn).toEqual('Bar1')
|
|
83
|
+
expect(user.cn).toBeUndefined()
|
|
84
|
+
|
|
85
|
+
expect(user.jpegPhoto).toBeUndefined()
|
|
86
|
+
|
|
87
|
+
expect(user['jpegPhoto;binary']).toBeDefined()
|
|
88
|
+
expect(Array.isArray(user['jpegPhoto;binary'])).toBe(true)
|
|
89
|
+
expect(user['jpegPhoto;binary'].length).toBe(0)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('Should return base64 jpegPhoto (using explicitBufferAttributes)', async () => {
|
|
93
|
+
let user = await authenticate({
|
|
94
|
+
...baseOptions,
|
|
95
|
+
username: 'gauss',
|
|
96
|
+
attributes: ['uid', 'sn', 'jpegPhoto'],
|
|
97
|
+
explicitBufferAttributes: ['jpegPhoto'],
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(user).toBeTruthy()
|
|
101
|
+
expect(user.uid).toEqual('gauss')
|
|
102
|
+
expect(user.sn).toEqual('Bar1')
|
|
103
|
+
expect(user.cn).toBeUndefined()
|
|
104
|
+
|
|
105
|
+
expect(user['jpegPhoto;binary']).toBeUndefined()
|
|
106
|
+
|
|
107
|
+
expect(user.jpegPhoto).toBeDefined()
|
|
108
|
+
expect(typeof user.jpegPhoto === 'string').toBe(true)
|
|
109
|
+
expect(user.jpegPhoto).toEqual(jpegPhotoBase64)
|
|
110
|
+
|
|
111
|
+
const buffer = Buffer.from(user.jpegPhoto, 'base64')
|
|
112
|
+
expect(buffer).toBeDefined()
|
|
113
|
+
expect(buffer.length).toBeGreaterThan(0)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const { authenticate, LdapAuthenticationError } = require('../index.js')
|
|
2
|
+
|
|
3
|
+
const url = process.env.INGITHUB ? 'ldap://localhost:1389' : 'ldap://ldap:1389'
|
|
4
|
+
|
|
5
|
+
describe('ldap-authentication StartTLS and TLS options test', () => {
|
|
6
|
+
it('Plain LDAP with tlsOptions in ldapOpts should work (ldap:// protocol)', async () => {
|
|
7
|
+
// Regression test: Before fix, having tlsOptions with ldap:// URL caused issues
|
|
8
|
+
// After fix: tlsOptions are properly excluded from Client constructor for ldap:// URLs
|
|
9
|
+
let options = {
|
|
10
|
+
ldapOpts: {
|
|
11
|
+
url: url,
|
|
12
|
+
tlsOptions: {
|
|
13
|
+
rejectUnauthorized: false,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
17
|
+
adminPassword: 'password',
|
|
18
|
+
userPassword: 'password',
|
|
19
|
+
userSearchBase: 'dc=example,dc=com',
|
|
20
|
+
usernameAttribute: 'uid',
|
|
21
|
+
username: 'gauss',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let user = await authenticate(options)
|
|
25
|
+
expect(user).toBeTruthy()
|
|
26
|
+
expect(user.uid).toEqual('gauss')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('Use an admin user to authenticate with StartTLS (may skip if TLS not configured)', async () => {
|
|
30
|
+
// Note: This test may not fully succeed if the LDAP server lacks TLS certificates
|
|
31
|
+
// However, it should NOT fail with the original ECONNRESET bug
|
|
32
|
+
let options = {
|
|
33
|
+
ldapOpts: {
|
|
34
|
+
url: url,
|
|
35
|
+
tlsOptions: {
|
|
36
|
+
rejectUnauthorized: false,
|
|
37
|
+
minVersion: 'TLSv1.2',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
starttls: true,
|
|
41
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
42
|
+
adminPassword: 'password',
|
|
43
|
+
userPassword: 'password',
|
|
44
|
+
userSearchBase: 'dc=example,dc=com',
|
|
45
|
+
usernameAttribute: 'uid',
|
|
46
|
+
username: 'gauss',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
let user = await authenticate(options)
|
|
51
|
+
// If this succeeds, StartTLS is fully working!
|
|
52
|
+
expect(user).toBeTruthy()
|
|
53
|
+
expect(user.uid).toEqual('gauss')
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Expected if StartTLS is not configured on the server
|
|
56
|
+
// The critical check: should NOT be the original ECONNRESET bug
|
|
57
|
+
if (error.code === 'ECONNRESET' &&
|
|
58
|
+
error.message && error.message.includes('Client network socket disconnected before secure TLS connection')) {
|
|
59
|
+
fail('ECONNRESET bug detected: tlsOptions should NOT be passed to Client constructor when using ldap:// URL')
|
|
60
|
+
}
|
|
61
|
+
// Other errors are acceptable (e.g., server doesn't support StartTLS)
|
|
62
|
+
expect(error).toBeTruthy()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('Use a regular user to authenticate with StartTLS (self mode)', async () => {
|
|
67
|
+
let options = {
|
|
68
|
+
ldapOpts: {
|
|
69
|
+
url: url,
|
|
70
|
+
tlsOptions: {
|
|
71
|
+
rejectUnauthorized: false,
|
|
72
|
+
minVersion: 'TLSv1.2',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
starttls: true,
|
|
76
|
+
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
77
|
+
userPassword: 'password',
|
|
78
|
+
userSearchBase: 'dc=example,dc=com',
|
|
79
|
+
usernameAttribute: 'uid',
|
|
80
|
+
username: 'einstein',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
let user = await authenticate(options)
|
|
85
|
+
expect(user).toBeTruthy()
|
|
86
|
+
expect(user.uid).toEqual('einstein')
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error.code === 'ECONNRESET' &&
|
|
89
|
+
error.message && error.message.includes('Client network socket disconnected before secure TLS connection')) {
|
|
90
|
+
fail('ECONNRESET bug detected: tlsOptions should NOT be passed to Client constructor when using ldap:// URL')
|
|
91
|
+
}
|
|
92
|
+
expect(error).toBeTruthy()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('Verify user exists with StartTLS', async () => {
|
|
97
|
+
let options = {
|
|
98
|
+
ldapOpts: {
|
|
99
|
+
url: url,
|
|
100
|
+
tlsOptions: {
|
|
101
|
+
rejectUnauthorized: false,
|
|
102
|
+
minVersion: 'TLSv1.2',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
starttls: true,
|
|
106
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
107
|
+
adminPassword: 'password',
|
|
108
|
+
verifyUserExists: true,
|
|
109
|
+
userSearchBase: 'dc=example,dc=com',
|
|
110
|
+
usernameAttribute: 'uid',
|
|
111
|
+
username: 'gauss',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
let user = await authenticate(options)
|
|
116
|
+
expect(user).toBeTruthy()
|
|
117
|
+
expect(user.uid).toEqual('gauss')
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.code === 'ECONNRESET' &&
|
|
120
|
+
error.message && error.message.includes('Client network socket disconnected before secure TLS connection')) {
|
|
121
|
+
fail('ECONNRESET bug detected: tlsOptions should NOT be passed to Client constructor when using ldap:// URL')
|
|
122
|
+
}
|
|
123
|
+
expect(error).toBeTruthy()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
})
|
package/test/test.spec.js
CHANGED
|
@@ -144,7 +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].dn).toEqual('cn=科学A部,ou=
|
|
147
|
+
expect(user.groups[0].dn).toEqual('cn=科学A部,ou=groups,dc=example,dc=com')
|
|
148
148
|
})
|
|
149
149
|
it('Use regular user to authenticate and fetch user group information', async () => {
|
|
150
150
|
let options = {
|
|
@@ -165,10 +165,10 @@ describe('ldap-authentication test', () => {
|
|
|
165
165
|
let user = await authenticate(options)
|
|
166
166
|
expect(user).toBeTruthy()
|
|
167
167
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
168
|
-
expect(user.groups[0].dn).toEqual('cn=科学A部,ou=
|
|
168
|
+
expect(user.groups[0].dn).toEqual('cn=科学A部,ou=groups,dc=example,dc=com')
|
|
169
169
|
// backward compatible with 3.2
|
|
170
170
|
expect(user.groups[0].objectName).toEqual(
|
|
171
|
-
'cn=科学A部,ou=
|
|
171
|
+
'cn=科学A部,ou=groups,dc=example,dc=com'
|
|
172
172
|
)
|
|
173
173
|
})
|
|
174
174
|
it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
|