ldap-authentication 3.3.3 → 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 +58 -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
|
|
@@ -98,6 +120,22 @@ async function _searchUser(
|
|
|
98
120
|
user = searchEntries[0]
|
|
99
121
|
}
|
|
100
122
|
|
|
123
|
+
// when attribute endwith ;binary, ldapts returns Buffer, we convert them into base64 string
|
|
124
|
+
if (user != null && attributes != null) {
|
|
125
|
+
for (let attr of attributes) {
|
|
126
|
+
if (attr.endsWith(';binary') && Buffer.isBuffer(user[attr])) {
|
|
127
|
+
user[attr] = user[attr].toString('base64')
|
|
128
|
+
}
|
|
129
|
+
}
|
|
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
|
+
}
|
|
101
139
|
return user
|
|
102
140
|
}
|
|
103
141
|
|
|
@@ -162,7 +200,8 @@ async function authenticateWithAdmin(
|
|
|
162
200
|
groupClass,
|
|
163
201
|
groupMemberAttribute = 'member',
|
|
164
202
|
groupMemberUserAttribute = 'dn',
|
|
165
|
-
attributes = null
|
|
203
|
+
attributes = null,
|
|
204
|
+
explicitBufferAttributes = null
|
|
166
205
|
) {
|
|
167
206
|
let ldapAdminClient
|
|
168
207
|
try {
|
|
@@ -183,7 +222,8 @@ async function authenticateWithAdmin(
|
|
|
183
222
|
userSearchBase,
|
|
184
223
|
usernameAttribute,
|
|
185
224
|
username,
|
|
186
|
-
attributes
|
|
225
|
+
attributes,
|
|
226
|
+
explicitBufferAttributes
|
|
187
227
|
)
|
|
188
228
|
if (!user || !user.dn) {
|
|
189
229
|
ldapOpts.log &&
|
|
@@ -233,7 +273,8 @@ async function authenticateWithUser(
|
|
|
233
273
|
groupClass,
|
|
234
274
|
groupMemberAttribute = 'member',
|
|
235
275
|
groupMemberUserAttribute = 'dn',
|
|
236
|
-
attributes = null
|
|
276
|
+
attributes = null,
|
|
277
|
+
explicitBufferAttributes = null
|
|
237
278
|
) {
|
|
238
279
|
let ldapUserClient
|
|
239
280
|
try {
|
|
@@ -254,7 +295,8 @@ async function authenticateWithUser(
|
|
|
254
295
|
userSearchBase,
|
|
255
296
|
usernameAttribute,
|
|
256
297
|
username,
|
|
257
|
-
attributes
|
|
298
|
+
attributes,
|
|
299
|
+
explicitBufferAttributes
|
|
258
300
|
)
|
|
259
301
|
if (!user || !user.dn) {
|
|
260
302
|
ldapOpts.log &&
|
|
@@ -293,7 +335,8 @@ async function verifyUserExists(
|
|
|
293
335
|
groupClass,
|
|
294
336
|
groupMemberAttribute = 'member',
|
|
295
337
|
groupMemberUserAttribute = 'dn',
|
|
296
|
-
attributes = null
|
|
338
|
+
attributes = null,
|
|
339
|
+
explicitBufferAttributes = null
|
|
297
340
|
) {
|
|
298
341
|
let ldapAdminClient
|
|
299
342
|
try {
|
|
@@ -314,7 +357,8 @@ async function verifyUserExists(
|
|
|
314
357
|
userSearchBase,
|
|
315
358
|
usernameAttribute,
|
|
316
359
|
username,
|
|
317
|
-
attributes
|
|
360
|
+
attributes,
|
|
361
|
+
explicitBufferAttributes
|
|
318
362
|
)
|
|
319
363
|
if (!user || !user.dn) {
|
|
320
364
|
ldapOpts.log &&
|
|
@@ -376,7 +420,8 @@ async function authenticate(options) {
|
|
|
376
420
|
options.groupClass,
|
|
377
421
|
options.groupMemberAttribute,
|
|
378
422
|
options.groupMemberUserAttribute,
|
|
379
|
-
options.attributes
|
|
423
|
+
options.attributes,
|
|
424
|
+
options.explicitBufferAttributes
|
|
380
425
|
)
|
|
381
426
|
}
|
|
382
427
|
assert(options.userPassword, 'userPassword must be provided')
|
|
@@ -398,7 +443,8 @@ async function authenticate(options) {
|
|
|
398
443
|
options.groupClass,
|
|
399
444
|
options.groupMemberAttribute,
|
|
400
445
|
options.groupMemberUserAttribute,
|
|
401
|
-
options.attributes
|
|
446
|
+
options.attributes,
|
|
447
|
+
options.explicitBufferAttributes
|
|
402
448
|
)
|
|
403
449
|
}
|
|
404
450
|
assert(options.userDn, 'adminDn/adminPassword OR userDn must be provided')
|
|
@@ -414,7 +460,8 @@ async function authenticate(options) {
|
|
|
414
460
|
options.groupClass,
|
|
415
461
|
options.groupMemberAttribute,
|
|
416
462
|
options.groupMemberUserAttribute,
|
|
417
|
-
options.attributes
|
|
463
|
+
options.attributes,
|
|
464
|
+
options.explicitBufferAttributes
|
|
418
465
|
)
|
|
419
466
|
}
|
|
420
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 () => {
|