ldap-authentication 4.3.0 → 4.4.0
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/.github/workflows/codeql-analysis.yml +12 -10
- package/AGENTS.md +8 -6
- package/README.md +7 -0
- package/docker/ldap/10-ldap-test-data.ldif +14 -0
- package/index.d.ts +18 -3
- package/index.js +330 -469
- package/package.json +1 -1
- package/test/authentication-result.spec.js +82 -41
- package/test/binary.spec.js +11 -10
- package/test/config.js +9 -0
- package/test/fetch-users.spec.js +23 -32
- package/test/fixtures/jpeg-photo.b64 +1 -0
- package/test/starttls.spec.js +12 -12
- package/test/test.spec.js +123 -41
- package/.github/workflows/publish.yml +0 -53
package/test/test.spec.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const {
|
|
2
|
+
authenticate,
|
|
3
|
+
LdapAuthenticationError,
|
|
4
|
+
AUTH_RESULT_FAILURE,
|
|
5
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
6
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
7
|
+
} = require('../index.js')
|
|
8
|
+
const { url, adminDn, adminPassword, userSearchBase } = require('./config')
|
|
4
9
|
|
|
5
10
|
describe('ldap-authentication test', () => {
|
|
6
11
|
it('Use an admin user to check if user exists', async () => {
|
|
@@ -8,10 +13,10 @@ describe('ldap-authentication test', () => {
|
|
|
8
13
|
ldapOpts: {
|
|
9
14
|
url: url,
|
|
10
15
|
},
|
|
11
|
-
adminDn:
|
|
12
|
-
adminPassword:
|
|
16
|
+
adminDn: adminDn,
|
|
17
|
+
adminPassword: adminPassword,
|
|
13
18
|
verifyUserExists: true,
|
|
14
|
-
userSearchBase:
|
|
19
|
+
userSearchBase: userSearchBase,
|
|
15
20
|
usernameAttribute: 'uid',
|
|
16
21
|
username: 'gauss',
|
|
17
22
|
}
|
|
@@ -25,10 +30,10 @@ describe('ldap-authentication test', () => {
|
|
|
25
30
|
ldapOpts: {
|
|
26
31
|
url: url,
|
|
27
32
|
},
|
|
28
|
-
adminDn:
|
|
29
|
-
adminPassword:
|
|
33
|
+
adminDn: adminDn,
|
|
34
|
+
adminPassword: adminPassword,
|
|
30
35
|
verifyUserExists: true,
|
|
31
|
-
userSearchBase:
|
|
36
|
+
userSearchBase: userSearchBase,
|
|
32
37
|
usernameAttribute: 'uid',
|
|
33
38
|
username: 'gauss',
|
|
34
39
|
attributes: ['uid', 'sn'],
|
|
@@ -45,10 +50,10 @@ describe('ldap-authentication test', () => {
|
|
|
45
50
|
ldapOpts: {
|
|
46
51
|
url: url,
|
|
47
52
|
},
|
|
48
|
-
adminDn:
|
|
49
|
-
adminPassword:
|
|
53
|
+
adminDn: adminDn,
|
|
54
|
+
adminPassword: adminPassword,
|
|
50
55
|
userPassword: 'password',
|
|
51
|
-
userSearchBase:
|
|
56
|
+
userSearchBase: userSearchBase,
|
|
52
57
|
usernameAttribute: 'uid',
|
|
53
58
|
username: 'gauss',
|
|
54
59
|
}
|
|
@@ -62,10 +67,10 @@ describe('ldap-authentication test', () => {
|
|
|
62
67
|
ldapOpts: {
|
|
63
68
|
url: url,
|
|
64
69
|
},
|
|
65
|
-
adminDn:
|
|
66
|
-
adminPassword:
|
|
70
|
+
adminDn: adminDn,
|
|
71
|
+
adminPassword: adminPassword,
|
|
67
72
|
userPassword: 'password',
|
|
68
|
-
userSearchBase:
|
|
73
|
+
userSearchBase: userSearchBase,
|
|
69
74
|
usernameAttribute: 'uid',
|
|
70
75
|
username: 'gauss',
|
|
71
76
|
attributes: ['uid', 'sn'],
|
|
@@ -77,6 +82,28 @@ describe('ldap-authentication test', () => {
|
|
|
77
82
|
expect(user.sn).toEqual('Bar1')
|
|
78
83
|
expect(user.cn).toBeUndefined()
|
|
79
84
|
})
|
|
85
|
+
it('Use an admin user to authenticate a user with a comma in the CN (e2e of the DN escaping)', async () => {
|
|
86
|
+
let options = {
|
|
87
|
+
ldapOpts: {
|
|
88
|
+
url: url,
|
|
89
|
+
},
|
|
90
|
+
adminDn: adminDn,
|
|
91
|
+
adminPassword: adminPassword,
|
|
92
|
+
userPassword: 'password',
|
|
93
|
+
userSearchBase: userSearchBase,
|
|
94
|
+
usernameAttribute: 'uid',
|
|
95
|
+
username: 'doe',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let user = await authenticate(options)
|
|
99
|
+
expect(user).toBeTruthy()
|
|
100
|
+
expect(user.uid).toEqual('doe')
|
|
101
|
+
// the admin finds `cn=Doe, John,ou=users,...` and must bind with the
|
|
102
|
+
// comma-escaped DN to succeed
|
|
103
|
+
expect(user.cn).toEqual('Doe, John')
|
|
104
|
+
expect(user.dn).toContain('cn=Doe')
|
|
105
|
+
expect(user.dn).toContain('ou=users,dc=example,dc=com')
|
|
106
|
+
})
|
|
80
107
|
it('Use an regular user to authenticate iteself', async () => {
|
|
81
108
|
let options = {
|
|
82
109
|
ldapOpts: {
|
|
@@ -84,7 +111,7 @@ describe('ldap-authentication test', () => {
|
|
|
84
111
|
},
|
|
85
112
|
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
86
113
|
userPassword: 'password',
|
|
87
|
-
userSearchBase:
|
|
114
|
+
userSearchBase: userSearchBase,
|
|
88
115
|
usernameAttribute: 'uid',
|
|
89
116
|
username: 'einstein',
|
|
90
117
|
}
|
|
@@ -100,7 +127,7 @@ describe('ldap-authentication test', () => {
|
|
|
100
127
|
},
|
|
101
128
|
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
102
129
|
userPassword: 'password',
|
|
103
|
-
userSearchBase:
|
|
130
|
+
userSearchBase: userSearchBase,
|
|
104
131
|
usernameAttribute: 'uid',
|
|
105
132
|
username: 'einstein',
|
|
106
133
|
attributes: ['uid', 'sn'],
|
|
@@ -129,13 +156,13 @@ describe('ldap-authentication test', () => {
|
|
|
129
156
|
ldapOpts: {
|
|
130
157
|
url: url,
|
|
131
158
|
},
|
|
132
|
-
adminDn:
|
|
133
|
-
adminPassword:
|
|
159
|
+
adminDn: adminDn,
|
|
160
|
+
adminPassword: adminPassword,
|
|
134
161
|
userPassword: 'password',
|
|
135
|
-
userSearchBase:
|
|
162
|
+
userSearchBase: userSearchBase,
|
|
136
163
|
usernameAttribute: 'uid',
|
|
137
164
|
username: 'gauss',
|
|
138
|
-
groupsSearchBase:
|
|
165
|
+
groupsSearchBase: userSearchBase,
|
|
139
166
|
groupClass: 'groupOfNames',
|
|
140
167
|
groupMemberAttribute: 'member',
|
|
141
168
|
groupMemberUserAttribute: 'dn',
|
|
@@ -153,10 +180,10 @@ describe('ldap-authentication test', () => {
|
|
|
153
180
|
},
|
|
154
181
|
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
155
182
|
userPassword: 'password',
|
|
156
|
-
userSearchBase:
|
|
183
|
+
userSearchBase: userSearchBase,
|
|
157
184
|
usernameAttribute: 'uid',
|
|
158
185
|
username: 'gauss',
|
|
159
|
-
groupsSearchBase:
|
|
186
|
+
groupsSearchBase: userSearchBase,
|
|
160
187
|
groupClass: 'groupOfNames',
|
|
161
188
|
groupMemberAttribute: 'member',
|
|
162
189
|
groupMemberUserAttribute: 'dn',
|
|
@@ -178,10 +205,10 @@ describe('ldap-authentication test', () => {
|
|
|
178
205
|
},
|
|
179
206
|
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
180
207
|
userPassword: 'password',
|
|
181
|
-
userSearchBase:
|
|
208
|
+
userSearchBase: userSearchBase,
|
|
182
209
|
usernameAttribute: 'uid',
|
|
183
210
|
username: 'gauss',
|
|
184
|
-
groupsSearchBase:
|
|
211
|
+
groupsSearchBase: userSearchBase,
|
|
185
212
|
groupClass: 'groupOfUniqueNames',
|
|
186
213
|
}
|
|
187
214
|
|
|
@@ -200,7 +227,7 @@ describe('ldap-authentication negative test', () => {
|
|
|
200
227
|
adminDn: 'cn=not-exist,dc=example,dc=com',
|
|
201
228
|
adminPassword: 'password',
|
|
202
229
|
userPassword: 'password',
|
|
203
|
-
userSearchBase:
|
|
230
|
+
userSearchBase: userSearchBase,
|
|
204
231
|
usernameAttribute: 'uid',
|
|
205
232
|
username: 'gauss',
|
|
206
233
|
}
|
|
@@ -213,20 +240,23 @@ describe('ldap-authentication negative test', () => {
|
|
|
213
240
|
}
|
|
214
241
|
|
|
215
242
|
expect(e).toBeTruthy()
|
|
243
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
244
|
+
expect(e.code).toEqual(AUTH_RESULT_FAILURE)
|
|
216
245
|
})
|
|
217
246
|
it('wrong admin password should fail', async () => {
|
|
218
247
|
let options = {
|
|
219
248
|
ldapOpts: {
|
|
220
249
|
url: url,
|
|
221
250
|
},
|
|
222
|
-
adminDn:
|
|
251
|
+
adminDn: adminDn,
|
|
223
252
|
adminPassword: '',
|
|
224
253
|
userPassword: 'password',
|
|
225
|
-
userSearchBase:
|
|
254
|
+
userSearchBase: userSearchBase,
|
|
226
255
|
usernameAttribute: 'uid',
|
|
227
256
|
username: 'gauss',
|
|
228
257
|
}
|
|
229
258
|
|
|
259
|
+
// '' is a falsy value, so this exercises the options validation, not the server
|
|
230
260
|
let e = null
|
|
231
261
|
try {
|
|
232
262
|
await authenticate(options)
|
|
@@ -235,16 +265,17 @@ describe('ldap-authentication negative test', () => {
|
|
|
235
265
|
}
|
|
236
266
|
|
|
237
267
|
expect(e).toBeTruthy()
|
|
268
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
238
269
|
})
|
|
239
270
|
it('admin auth wrong username should fail', async () => {
|
|
240
271
|
let options = {
|
|
241
272
|
ldapOpts: {
|
|
242
273
|
url: url,
|
|
243
274
|
},
|
|
244
|
-
adminDn:
|
|
245
|
-
adminPassword:
|
|
275
|
+
adminDn: adminDn,
|
|
276
|
+
adminPassword: adminPassword,
|
|
246
277
|
userPassword: 'password',
|
|
247
|
-
userSearchBase:
|
|
278
|
+
userSearchBase: userSearchBase,
|
|
248
279
|
usernameAttribute: 'uid',
|
|
249
280
|
username: 'wrong',
|
|
250
281
|
}
|
|
@@ -257,16 +288,18 @@ describe('ldap-authentication negative test', () => {
|
|
|
257
288
|
}
|
|
258
289
|
|
|
259
290
|
expect(e).toBeTruthy()
|
|
291
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
292
|
+
expect(e.code).toEqual(AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND)
|
|
260
293
|
})
|
|
261
294
|
it('admin auth wrong user password should fail', async () => {
|
|
262
295
|
let options = {
|
|
263
296
|
ldapOpts: {
|
|
264
297
|
url: url,
|
|
265
298
|
},
|
|
266
|
-
adminDn:
|
|
267
|
-
adminPassword:
|
|
299
|
+
adminDn: adminDn,
|
|
300
|
+
adminPassword: adminPassword,
|
|
268
301
|
userPassword: 'wrongpassword',
|
|
269
|
-
userSearchBase:
|
|
302
|
+
userSearchBase: userSearchBase,
|
|
270
303
|
usernameAttribute: 'uid',
|
|
271
304
|
username: 'gauss',
|
|
272
305
|
}
|
|
@@ -279,6 +312,8 @@ describe('ldap-authentication negative test', () => {
|
|
|
279
312
|
}
|
|
280
313
|
|
|
281
314
|
expect(e).toBeTruthy()
|
|
315
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
316
|
+
expect(e.code).toEqual(AUTH_RESULT_FAILURE_CREDENTIAL_INVALID)
|
|
282
317
|
})
|
|
283
318
|
it('user auth wrong username should fail', async () => {
|
|
284
319
|
let options = {
|
|
@@ -287,7 +322,7 @@ describe('ldap-authentication negative test', () => {
|
|
|
287
322
|
},
|
|
288
323
|
userDn: 'cn=not-exist,dc=example,dc=com',
|
|
289
324
|
userPassword: 'password',
|
|
290
|
-
userSearchBase:
|
|
325
|
+
userSearchBase: userSearchBase,
|
|
291
326
|
usernameAttribute: 'uid',
|
|
292
327
|
username: 'gauss',
|
|
293
328
|
}
|
|
@@ -300,6 +335,7 @@ describe('ldap-authentication negative test', () => {
|
|
|
300
335
|
}
|
|
301
336
|
|
|
302
337
|
expect(e).toBeTruthy()
|
|
338
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
303
339
|
})
|
|
304
340
|
it('user auth wrong user password should fail', async () => {
|
|
305
341
|
let options = {
|
|
@@ -308,7 +344,7 @@ describe('ldap-authentication negative test', () => {
|
|
|
308
344
|
},
|
|
309
345
|
userDn: 'cn=gauss,dc=example,dc=com',
|
|
310
346
|
userPassword: 'wrongpassword',
|
|
311
|
-
userSearchBase:
|
|
347
|
+
userSearchBase: userSearchBase,
|
|
312
348
|
usernameAttribute: 'uid',
|
|
313
349
|
username: 'gauss',
|
|
314
350
|
}
|
|
@@ -321,6 +357,7 @@ describe('ldap-authentication negative test', () => {
|
|
|
321
357
|
}
|
|
322
358
|
|
|
323
359
|
expect(e).toBeTruthy()
|
|
360
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
324
361
|
})
|
|
325
362
|
it('Use an regular user to authenticate iteself without search with wrong password should fail', async () => {
|
|
326
363
|
let options = {
|
|
@@ -331,13 +368,16 @@ describe('ldap-authentication negative test', () => {
|
|
|
331
368
|
userPassword: '',
|
|
332
369
|
}
|
|
333
370
|
|
|
371
|
+
let e = null
|
|
334
372
|
try {
|
|
335
373
|
await authenticate(options)
|
|
336
374
|
} catch (error) {
|
|
337
375
|
e = error
|
|
338
376
|
}
|
|
339
377
|
|
|
378
|
+
// '' is a falsy value, so this exercises the options validation, not the server
|
|
340
379
|
expect(e).toBeTruthy()
|
|
380
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
341
381
|
})
|
|
342
382
|
it('Wrong options give LdapAuthenticationError', async () => {
|
|
343
383
|
let options = {
|
|
@@ -347,10 +387,11 @@ describe('ldap-authentication negative test', () => {
|
|
|
347
387
|
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
348
388
|
userPassword: 'password',
|
|
349
389
|
usernameAttribute: 'wrongattribute',
|
|
350
|
-
userSearchBase:
|
|
390
|
+
userSearchBase: userSearchBase,
|
|
351
391
|
username: 'einstein',
|
|
352
392
|
}
|
|
353
393
|
|
|
394
|
+
let e = null
|
|
354
395
|
try {
|
|
355
396
|
await authenticate(options)
|
|
356
397
|
} catch (error) {
|
|
@@ -369,10 +410,11 @@ describe('ldap-authentication negative test', () => {
|
|
|
369
410
|
userDn: 'uid=einstein,dc=example,dc=com',
|
|
370
411
|
userPassword: 'password',
|
|
371
412
|
usernameAttribute: 'cn',
|
|
372
|
-
userSearchBase:
|
|
413
|
+
userSearchBase: userSearchBase,
|
|
373
414
|
username: 'einstein',
|
|
374
415
|
}
|
|
375
416
|
|
|
417
|
+
let e = null
|
|
376
418
|
try {
|
|
377
419
|
await authenticate(options)
|
|
378
420
|
} catch (error) {
|
|
@@ -390,11 +432,12 @@ describe('ldap-authentication negative test', () => {
|
|
|
390
432
|
userDn: 'uid=einstein,dc=example,dc=com',
|
|
391
433
|
userPassword: 'password',
|
|
392
434
|
usernameAttribute: 'cn',
|
|
393
|
-
userSearchBase:
|
|
435
|
+
userSearchBase: userSearchBase,
|
|
394
436
|
username: 'einstein',
|
|
395
437
|
starttls: true,
|
|
396
438
|
}
|
|
397
439
|
|
|
440
|
+
let e = null
|
|
398
441
|
try {
|
|
399
442
|
await authenticate(options)
|
|
400
443
|
} catch (error) {
|
|
@@ -410,10 +453,10 @@ describe('ldap-authentication negative test', () => {
|
|
|
410
453
|
},
|
|
411
454
|
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
412
455
|
userPassword: 'password',
|
|
413
|
-
userSearchBase:
|
|
456
|
+
userSearchBase: userSearchBase,
|
|
414
457
|
usernameAttribute: 'uid',
|
|
415
458
|
username: 'gauss',
|
|
416
|
-
groupsSearchBase:
|
|
459
|
+
groupsSearchBase: userSearchBase,
|
|
417
460
|
groupClass: 'groupOfNames',
|
|
418
461
|
groupMemberAttribute: 'member',
|
|
419
462
|
groupMemberUserAttribute: 'dnWRONG',
|
|
@@ -423,4 +466,43 @@ describe('ldap-authentication negative test', () => {
|
|
|
423
466
|
expect(user).toBeTruthy()
|
|
424
467
|
expect(user.groups.length).toBeLessThan(1)
|
|
425
468
|
})
|
|
469
|
+
it('Missing required options should throw LdapAuthenticationError listing all of them', async () => {
|
|
470
|
+
let e = null
|
|
471
|
+
try {
|
|
472
|
+
await authenticate({
|
|
473
|
+
ldapOpts: {
|
|
474
|
+
url: url,
|
|
475
|
+
},
|
|
476
|
+
adminDn: adminDn,
|
|
477
|
+
usernameAttribute: 'uid',
|
|
478
|
+
username: 'gauss',
|
|
479
|
+
})
|
|
480
|
+
} catch (error) {
|
|
481
|
+
e = error
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
expect(e).toBeTruthy()
|
|
485
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
486
|
+
// all missing fields are reported at once, not one assert at a time
|
|
487
|
+
expect(e.message).toContain('adminPassword')
|
|
488
|
+
expect(e.message).toContain('userSearchBase')
|
|
489
|
+
expect(e.message).toContain('userPassword')
|
|
490
|
+
})
|
|
491
|
+
it('No adminDn and no userDn should throw LdapAuthenticationError', async () => {
|
|
492
|
+
let e = null
|
|
493
|
+
try {
|
|
494
|
+
await authenticate({
|
|
495
|
+
ldapOpts: {
|
|
496
|
+
url: url,
|
|
497
|
+
},
|
|
498
|
+
userPassword: 'password',
|
|
499
|
+
})
|
|
500
|
+
} catch (error) {
|
|
501
|
+
e = error
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
expect(e).toBeTruthy()
|
|
505
|
+
expect(e).toBeInstanceOf(LdapAuthenticationError)
|
|
506
|
+
expect(e.message).toContain('adminDn or userDn')
|
|
507
|
+
})
|
|
426
508
|
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
name: 'Publish to npm'
|
|
2
|
-
|
|
3
|
-
env:
|
|
4
|
-
INGITHUB: true
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
publish:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
name: 'Publish ${{ github.ref_name }}'
|
|
14
|
-
|
|
15
|
-
steps:
|
|
16
|
-
- uses: actions/checkout@v3
|
|
17
|
-
|
|
18
|
-
- name: Install LDAP tools
|
|
19
|
-
run: sudo apt-get install -y ldap-utils
|
|
20
|
-
|
|
21
|
-
- name: Start containers
|
|
22
|
-
run: docker compose -f "docker-compose.yml" up -d
|
|
23
|
-
|
|
24
|
-
- name: Wait for LDAP server to become available
|
|
25
|
-
uses: nick-fields/retry@v2
|
|
26
|
-
with:
|
|
27
|
-
timeout_minutes: 5
|
|
28
|
-
max_attempts: 10
|
|
29
|
-
polling_interval_seconds: 3
|
|
30
|
-
command: |
|
|
31
|
-
ldapsearch -LLL -o 'ldif-wrap=no' \
|
|
32
|
-
-H ldap://localhost:1389 \
|
|
33
|
-
-D 'cn=read-only-admin,dc=example,dc=com' \
|
|
34
|
-
-w password \
|
|
35
|
-
-b 'cn=einstein,ou=users,dc=example,dc=com' DN
|
|
36
|
-
|
|
37
|
-
- name: Use Node.js 22.x
|
|
38
|
-
uses: actions/setup-node@v3
|
|
39
|
-
with:
|
|
40
|
-
node-version: 22.x
|
|
41
|
-
registry-url: 'https://registry.npmjs.org'
|
|
42
|
-
|
|
43
|
-
- run: npm ci
|
|
44
|
-
- run: npm run test
|
|
45
|
-
|
|
46
|
-
- name: Stop containers
|
|
47
|
-
if: always()
|
|
48
|
-
run: docker compose -f "docker-compose.yml" down
|
|
49
|
-
|
|
50
|
-
- name: Publish to npm
|
|
51
|
-
run: npm publish
|
|
52
|
-
env:
|
|
53
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|